8

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?

3
  • 1
    i do not see what the issue with detailed multi-line comments is? and if you rathr have concise comments then after the fn decleration add a //<String>,<String> or few words that you think will help you 6 months later
    – tgkprog
    Apr 25, 2013 at 13:14
  • That works to some degree, but requires additional work on the programmer's behalf to make sure that the multi-line comment blob and the code are always in sync. You're breaking DRY (not the end of the world) and creating more maintenance work for yourself. Ideally the code would be self documenting. I'm trying to see if there's a way getting pretty close to that state. Apr 25, 2013 at 18:12
  • 1
    The best practice in dealing with JavaScript is using TypeScript :)
    – Den
    Apr 26, 2013 at 9:15

3 Answers 3

1

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 :)

5
  • 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
  • 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
  • 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
  • @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.
    – pllee
    Apr 26, 2013 at 21:50
  • @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.
    – pllee
    Apr 26, 2013 at 21:53
1

Maybe try to write it more in OOP style. For example, instead of:

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

why can't you do

session.crunchData(options)

where session is object you have created. You will be able to take a look at constructor to know exactly how session is created and all your problems should go away

1

Any code should be documented with enough information to understand it. That includes information about the variable types if necessary (and even in statically typed languages, you may need more information than is supplied by just the type keyword).

When you get used to working with a dynamically-typed language, you will find that the type of a variable is often self-explanatory based on its use. But if not, add comments to clarify it.

As for your concern that such comments add duplication (and possibly break a principle such as DRY), that concern would apply to any comment. Any documentation inherently adds a certain amount of duplication, and must be maintained so that it matches the code. But when it helps make the code understandable, the benefit outweighs the cost.

Also, note that most of your other solutions involve creating just as much (or more) extra stuff. A class would need a class definition that is as least as large as the comments. (Of course, making a class might be beneficial for other reasons. But don't avoid solving this with comments just because they add extra lines to your code.)

Commenting should only be done when it is necessary to understand the code, however. I hate seeing code like this, which really is useless duplication:

//crunchSomeSessionData - A function that crunches some session data.
//
//sessionsMap - A map containing the sessions and their data that we want to crunch.
//user - The user for whose sessions we want to crunch data
function crunchSomeSessionData(sessionsMap, user) {
    [...]
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.