42

I've recently poured a couple of hours into JavaScript because I wanted to benefit from the massive userbase. Doing that I have noticed a pattern that most people attribute to dynamic languages. You get things working really quickly, but once your code reaches a certain size you waste much time with type, spelling and refactoring errors in general. Errors a compiler would normally spare me from. And not have me looking for errors in the logic when I just made typo in another module.

Considering the incredible following JavaScript and other dynamically typed languages have I am lead to believe that there's something wrong with my approach. Or is this just the price you have to pay?

To put it more concisely:

  • How do you approach a JavaScript (or any other dynamic language for that matter) project with ~2000 LOC?
  • Are there tools to prevent me from making those mistakes? I have tried flow by Facebook and JSHint which somewhat help, but don't catch typos.
TomTom
  • 531
  • 4
  • 6
  • 2
    Even though there are ways to mitigate the costs, [there *are* costs](http://programmers.stackexchange.com/a/221658/101308). – dcastro May 09 '16 at 13:51
  • 29
    I'd try writing your program in a statically typed language that compiles to javascript, like Typescript, Scala.js or Elm. – dcastro May 09 '16 at 13:52
  • In regards to typos specifically, Sublime Text and perhaps similar IDEs can help a bit. Inside of one file, it will treat each unique word as a symbol and auto-suggest it. So if you write `analyzeDependencies` once, it will suggest it after `analy`. Also, selecting one instance of the word will select other instances, but not misspellings. – Katana314 May 09 '16 at 13:59
  • 6
    testing, testing, more testing, and coverage reports. – njzk2 May 09 '16 at 18:33
  • Get better tooling. A good IDE that speaks JavaScript (e.g. IntelliJ IDEA Ultimate), etc. – OrangeDog May 09 '16 at 20:18
  • 1
    What's your definition of a typo if you think JSHint doesn't catch them? – OrangeDog May 09 '16 at 20:21
  • 1
    @OrangeDog Intellij Ultimate is proprietary and costs quite a bit. By typo I mean referencing a field, function or parameter that doesn't exit. – TomTom May 09 '16 at 21:12
  • 7
    ~2000 LOC is a small project. It should easily fit into what a dynamic language does easily and well. If you're struggling with that kind of size project you have a more fundamental issue with your programming skills than anything that is relevant to dynamic languages specifically. – Jack Aidley May 09 '16 at 21:24
  • 5
    @JackAidley Disagreed. OP is used to focus on high-level problems and not whether an identifier is spelled properly. That is programming skill. Ensuring correct spelling can be done by a middle grader and/or tool support. – mucaho May 09 '16 at 21:48
  • 1
    `===` is your friend. if you are new to javascript don't ever ever ever use `==`. And don't always assume that your variable has what you think it has, the web is a way different environment then any other and has some... unique timing qualities all it's own, so tend towards more checks then less. As for typos... get a friend, ***really***, get a friend. I once spent 2 hours looking for an error, gave up, asked my buddy to take a look at it while I took a walk, came back he said he fixed it, I had in one place written `funcion` instead of `function`. – Ryan May 09 '16 at 22:32
  • Define "much time". How much time are you actually spending on this? – jpmc26 May 10 '16 at 01:32
  • 1
    @njzk2: "testing, testing, more testing": True. In my experience I definitely need more unit tests when using a dynamic language. A static type system helps to put the pieces together in the right places. In a dynamic language you have to rely more on tests. – Giorgio May 10 '16 at 04:18
  • 2
    @TomTom JSHint will definitely catch referencing a field, function or parameter that doesn't exist, as will `"use strict";`. – OrangeDog May 10 '16 at 06:50
  • @jpmc26 approximately half of the runtime bugs are caused by slips a static language would have caught – TomTom May 10 '16 at 07:47
  • @OrangeDog That is valid code: `var a = { b:5 }; console.log(a.a);`. I am sure there are some use cases, but I think most people would like to get a warning when they are referencing the function rather than some field of the function. – TomTom May 10 '16 at 07:51
  • 1
    @TomTom IntelliJ gives me by default a weak warning "Unresolved variable a" for that, and will also autocomplete `a.b` ¯\_(ツ)_/¯ – OrangeDog May 10 '16 at 09:23
  • 1
    Make your editor point out errors using JSLint or JSHint. If you use vim check syntastic. – Filipe Giusti May 10 '16 at 19:32
  • @TomTom Many identifiers in any language are very similar -- purposely so since similarly is the only obvious way to remind us of relationships between them. Some use things like `var _myfunction` to hold the return value of a function named `myfunction` for example. That introduces the danger of leaving off the underscore prefix but that's programming. Single-character syntax errors are the most common there are -- and usually the easiest to find and fix. – DocSalvager May 12 '16 at 19:27

5 Answers5

37

Specifically speaking of JavaScript, you could use TypeScript instead. It offers some of the things you are referring to. Quoting the website:

Types enable JavaScript developers to use highly-productive development tools and practices like static checking and code refactoring when developing JavaScript applications.

And it is just a superset of JS, meaning some of your existing code will work with TS just fine:

TypeScript starts from the same syntax and semantics that millions of JavaScript developers know today. Use existing JavaScript code, incorporate popular JavaScript libraries, and call TypeScript code from JavaScript.

VinArrow
  • 484
  • 4
  • 6
  • 11
    ... and TypeScript is essentially Ecmascript 6. – Robert Harvey May 09 '16 at 14:26
  • 11
    Uh, that quote hurts. It just shows that Microsoft has always been a static languages company that simply doesn't understand dynamic languages. "Types enable … code refactoring"? Really? Does Microsoft's PR department realize that code refactoring as a practice was invented in Smalltalk (a dynamic language) and existed even before that in Forth (a typeless language)? That the very first automated refactoring tool was part of a Smalltalk IDE, before static languages even *had* IDEs? That modern Smalltalk IDEs have refactoring tools at least as powerful if not more as Java, C#, and C++? C'mon. – Jörg W Mittag May 09 '16 at 16:09
  • 5
    TypeScript is a great language on its own, why do you have to try and push it with such nonsense? – Jörg W Mittag May 09 '16 at 16:09
  • 29
    @Jörg I don't know enough about Smalltalk, but every single IDE for JavaScript or Python I have seen is *miles* behind what a good IDE for Java or C# can do. Also there are some things that are just plain impossible to do in a dynamic language (some of the most popular refactorings really): Say you have a public function `foo(x) { return x.bar;}` or anything like that. Since there is no type information and the function is public (hence you can't know all the callers) it is impossible for you to figure out whether bar should be renamed to baz if you rename some class. – Voo May 09 '16 at 17:01
  • 1
    @Voo: Yes, and every single IDE for Java or C♯ is *miles* behind what Smalltalk and Lisp IDEs could do even 10 years ago. That doesn't mean much. Most JavaScript and Python IDEs simply aren't very good. Note that there are IDEs for static languages as well that can't do refactoring, e.g. I have seen some Fortran or Cobol IDEs that didn't even have the simplest of refactorings. Smalltalk refactoring IDEs have been doing rename refactorings since before the Java community even knew what a "refactoring" was. A rename refactoring is trivial in Smalltalk precisely *because* of the dynamic nature … – Jörg W Mittag May 09 '16 at 17:14
  • … of the system: the rename simply gets applied dynamically reflectively at runtime, each callsite is patched if and when it calls the target. That way, it can *even* rename callsites in different modules, even outside of the developer's control! – Jörg W Mittag May 09 '16 at 17:16
  • 3
    @Jörg Interesting. How is that refactoring reflected in the source code? I mean that would mean that every name that was ever given to a method would have to be stored in the source file since you won't know which version of your library would be called (and I can't rename a method and then create a new method with the same name any more). That sounds more like a versioning mechanism than renaming. But if that's relatively opaque to the user, I can see that being a usable workaround. – Voo May 09 '16 at 17:27
  • 5
    @RobertHarvey TypeScript is so much more than ES6. TypeScript is much more C# than it is ES6. – Andy May 09 '16 at 19:50
  • 1
    TypeScript isn't C# and it's harmful to understanding of the language to say it is. TypeScript is ES6 + types. – Ryan Cavanaugh May 09 '16 at 23:40
  • 10
    This answer says that the solution to "dynamic language mistakes" is not to use a dynamic language at all. – bgusach May 10 '16 at 08:53
  • If OP can't manage a small (2000 LOC) project without prolific naming-based bugs, I'm not sure any alternate language can help. – OrangeDog May 10 '16 at 09:24
  • 3
    @JörgWMittag But how does an IDE know what to rename and what not to? The type systems help precisely with this: they make programmers put in the information regarding the messages that can be sent to an object of a given type. So even if you have two distinct types with a method sharing the same name, you can safely rename a method in one of the types and be sure that the other one will be left intact. I don't see how an IDE would understand that without the type system. – Malcolm May 10 '16 at 10:36
19

There are some approaches which can help:

Unit testing

Write unit tests where possible. Solely relying on manual testing or finding bugs in the wild is hit-and-miss.

Use frameworks

Rather than rolling your own and risking the introduction of bugs, use established frameworks where possible.

Prefer CSS/high-level languages

Where you can cede functionality to CSS or whatever high-level language you're writing in.

Refactor

Refactor to reduce the amount of code. Less code = less places for things to go wrong.

Reuse

Reuse existing code where you can. Even if code isn't an exact match, it can be better to copy, paste and modify rather than writing something afresh.

IDEs

Modern IDEs generally have at least some Javascript support. Some text editors are also Javascript aware.

Robbie Dee
  • 9,717
  • 2
  • 23
  • 53
  • 5
    While true, your advice applies basically to every programming language and mainly aims at fixing _logical_ mistakes rather than those that arise from dynamic languages. – edmz May 09 '16 at 17:40
  • 1
    *"your advice applies basically to every programming language"*. Very true - in a similar way to going thru the gears from hobby projects to full fat enterprise solutions, which requires an increasing amount of strictures, similarly - the more Javascript that is written, the more discipline it needs if the wheels aren't going to rapidly come off. [Eric Lippert](http://programmers.stackexchange.com/a/221658/73508) describes this very well. – Robbie Dee May 09 '16 at 17:48
  • 4
    *"Prefer CSS/high-level languages"* — I don't really understand what this bit means in relation to JavaScript: are you saying to move elements (like animation, perhaps?) into stylesheets rather than JS code? How does CSS relate to high-level languages? – anotherdave May 09 '16 at 18:18
  • @anotherdave A lot of what used to be firmly the domain of Javascript can now be achieved in CSS3. Some functionality could also possibly be moved to a higher level language which would be subject to more stringent controls. – Robbie Dee May 09 '16 at 20:15
  • 4
    @anotherdave Much of what people try to do with JavaScript is extraneous and inappropriate. Libraries that provide standard language tools, frameworks that provide standard HTML elements with little more, code that replicates basic functionality like anchors, MVC emulation, styling, DOM reimplementation, AJAX abstraction, rendering trivial objects (reimplementing SVG), polyfilling features that don't benefit the user… You should minimize the amount JS that you write. If you can do it without JS, do it without JS. – bjb568 May 10 '16 at 03:36
2

One tool that hasn't been yet mentioned is simple, file-local or project-wide text search.

It sounds simple, but when you include some regular expressions you can do some basic to advanced filtering, e.g. search for words located in documentation or source code.

It has been an effective tool for me (besides static analyzers), and given your project size of 2k LOC, which isn't particularly large in my opinion, should hopefully work wonders.

mucaho
  • 631
  • 1
  • 5
  • 10
  • 2
    `grep` goes a long way. Unless you don't do too weird dynamic things, it does the trick. However, it feels very manual if you are used to IDEs for static typed languages. – bgusach May 10 '16 at 08:52
1

I am currently refactoring several thousand lines of code on a large AngularJS project. One of the biggest hassles is to figure out the exact contract of a given function. I sometimes ended up reading API documentation because elements of the raw API response were assigned to variables that went through 6 layers of code before being modified and returned through 6 more layers of code.

My first advice is to design by contract. Take specific input, produce specific output, avoid side effects, and document those expectations using TypeScript or at least JSDoc.

My second advice is to implement as many checks as possible. We follow the AirBnB standard and use eslint on our entire code base. Commit hooks verify that we always follow the standard. We naturally have a battery of unit and acceptance tests, and all commits must be reviewed by a peer.

Switching from a text editor (Sublime Text) to a proper IDE (WebStorm) also made it much easier to work with code in general. WebStorm will use JSDoc to give hints about expected parameter types and raise error if you supply the wrong type or use the a return value in the wrong way.

In JavaScript, new features such as symbols and getter/setters can help enforce a certain level of quality by adding assertions to variable assignment (e.g. make sure the integer is within range, or that the data object has certain attributes).

Unfortunately, I don't think there's a true solution to prevent dynamic language mistakes, only a series of measures that can help reduce their frequency.

nicbou
  • 111
  • 4
0

My answer to the question “How do you approach a JavaScript (or any other dynamic language for that matter) project with ~2000 LOC?”

I develop PDF form applications. I approach my JavaScript software development project (regardless of source code size) using Petri’s net elements and annotations. The method is not tied to any particular programming language technology. Thus it may be used for other “programming languages”.

I create a diagram of the application logic. To keep the diagram uncluttered I add most of my annotations to a form that I use with the diagram. The entries in the form include references to properties or functions. Then I write out the source code based on the information in the diagram and entries in the form. The method is systematic because every source code written is directly mapped from the diagram and entries in the form. The source code can be easily checked because I also follow naming and coding conventions when I write the code.

For example, I have chosen a convention that all functions are prototypes. If performance become an issue then it can be improved by declaring the functions in the constructor. For some properties I use arrays. Again if performance becomes an issue then it can be improved by using direct references.

I also use eval. This can greatly reduce the size of source code. Because of performance issues, I use eval at the beginning or initialization part of my application; I never use it in the “runtime logic” – this is another coding convention that I follow.