Software Engineering
coding-style coding-standards dynamic-typing dynamic-languages
Updated Sun, 26 Jun 2022 14:21:57 GMT

Dynamic typing function arguments - how to keep readability high?


Dynamic typing newbie here, hoping for some wizened words of wisdom.

I'm curious if there is a set of best practices out there for dealing with function arguments (and let's be honest, variables in general) in dynamically typed languages such as Javascript. The issue I often run into is with regards to readability of code: I'm looking at a function I wrote a while ago and I have no clue what the structure of the argument variables actually is. It's usually ok at the moment of development of new code: everything's fresh in my head, every variable and parameter makes sense because I just wrote them. A week later? Not so much.

For example, say I'm trying to crunch a bunch of data about user sessions on a website and get something useful out of it:

var crunchSomeSessionData = function(sessionsMap, options) {
    [...]
}

Disregarding the fact that the function name isn't helpful - that obviously is a huge deal - I actually don't know anything at all about what the structure of sessionsMap or options is. Ok.. I have k/v pairs the sessionsMap object, since it's called Map, but are the value a primitive, an array, another hash of stuff? What is options? An array? A whitespace separated string?

I have a few options:

  • clarify the structure exactly in the comment header for the function. The problem is that now I have to maintain the code in two places.
  • have as useful of a name as possible. e.g. userIdToArrayOfTimestampsMap or even have some kind of pseudo-Hungarian dialect for variable naming that only I speak that explains what the types are and how they're nested. This leads to really verbose code, and I'm a fan of keeping stuff under 80 col.
  • break functions down until I'm only ever passing around primitives or collections of primitives. I imagine it might work, but then I'd likely end up with micro-functions that have one or two lines at most, functions that exist only for the purpose of readability. Now I have to jump all over the file and recompose the function in my head, which just made readability worse.
  • some languages offer destructuring, which to some extent can almost be thought of as extra documentation for what the argument type is going to contain.
  • could create a "class" for the specific type of object, even though it'd not make a huge difference in a prototypal language like JS, and would probably add more maintenance overhead than necessary. Alternatively, if available, one can try to use protocols, maybe something along the lines of Clojure's deftype/defrecord etc.

In the statically typed world this is not nearly as much of an issue. In C# for example you get a:

public void DoStuff(Dictionary<string, string> foo) {[...]};

Ok, easy peasy, I know exactly what I'm getting, no need to read the function header, or go back to the caller and figure out what it's concocting etc.

What's the solution here? Are all people developing in dynamically typed languages continuously boggled by what types their subroutines are getting? Are there mitigation strategies?




Solution

I think a lot of the problems that you are having can be solved with proper naming of variables and the contents of the method. For the most part it should be obvious what the parameter types are based on the names and the contents of the method. Documentation also helps.

For example:

function getSum(arr) {
    var sum = 0;
    arr.forEach(arr, function(item) {
        sum += item;
    });
    return sum;
}

Just from the name of the function and how it is written it should be implied that getSum takes an array of numbers and returns a number.

Lets look at another example that doesn't make any sense irl but it should make sense what it is doing.

var crunchSomeSessionData = function(sessionsMap) {
    var browsers = {};
    Object.keys(sessionsMap).forEach(function(key) {
        var session = sessionsMap[key],
            browser = session.getBrowser();
        if(!browsers[browser]) {
            browsers[browser] = [];
        }
        browsers[browser].push(session.getUserId());
    });
    return browsers;
}

Without proper documentation (irl this would be documented) you can still see that crunchSessionData takes an object of Session instances, those instances have a getBrowser and getUserId method. It returns an object keyed off by browser that has an array of user ids that are currently on that browser.

crunchSomeSessionData({
    213j123j123j123j13: {browser: 'chrome', userId: 'pllee'},
    fawefjioawejfwoeiw: {browser: 'ie', userId: 'grandpa'}
})
>> {chrome: ['pllee'], ie: ['grandpa']}

I would suggest that to get a good hang of how dynamic typing can be readable read some open source code that isn't heavily documented and see if you can follow what is going on. If so start using their design patterns. You can check out the simple performance timing library that I wrote https://github.com/pllee/vlug. It is very small and most of the methods aren't documented. The code is not perfect but it could be a good example. After not looking or using it for months the code still makes sense to me but I did write it :)





Comments (5)

  • +0 – My naming scheme generally works fine for the simple cases, but I've been recently working more on analytics products that require a ton of data massaging. Basically I have a blob of data in one form or another (perhaps an object of arrays of objects) and I'm doing several tranformational passes over it (think along the lines of a compiler), every form being slightly different in shape from another. These intermediate forms exist only briefly for one step of the algorithm, but generating them requires pretty beefy methods, which obviously need to know what shape they're getting that data in. — Apr 26, 2013 at 20:27  
  • +0 – So an object of arrays of objects can suddenly become an array of objects, which then can become an array of objects of a completely different kind, which then can become an array of html strings etc etc. These intermediate forms don't really have intuitive names, unless you go for sessionToArrOfBrowsersOf... etc. — Apr 26, 2013 at 20:30  
  • +0 – Not that I disagree with any of what you said above, that is all quite sensible. I'm going to accept that one and look for answers some more. — Apr 26, 2013 at 20:30  
  • +0 – @glitch I think you are getting closer. Some times it is hard to wrap your mind around after writing lots of statically typed code but there is nothing forcing arguments or members to be of a certain type. It for the most part should assume that the caller or user is following the proper api. — Apr 26, 2013 at 21:50  
  • +0 – @glitch Even though browsers is defined as an object a few lines later you can see that browsers is really {"browserKey" : UserIds[String] } . From Object.keys(sessionsMap).forEach(function(key) { you can determine that sessionsMap is an object. session.getBrowser(); and session.getUserId() should indicate that sessionMap has Session instances as its values. That being said proper documentation would make everything much clearer. — Apr 26, 2013 at 21:53  


External Links

External links referenced by this document: