blog

  • Home
  • blog
  • Untangling Spaghetti Code: How to Write Maintainable JavaScript

Untangling Spaghetti Code: How to Write Maintainable JavaScript

Almost every developer has had the experience of maintaining or taking over a legacy project. Or, maybe it’s an old project which got picked up again. Common first thoughts are to throw away the code base, and start from scratch. The code can be messy, undocumented and it can take days to fully understand everything. But, with proper planning, analysis, and a good workflow, it is possible to turn a spaghetti codebase into a clean, organised, and scalable one.

I’ve had to take over and clean-up a lot of projects. There haven’t been many I started from scratch. In fact, I am currently doing exact that. I’ve learned a lot regarding JavaScript, keeping a codebase organised and — most importantly — not being mad at the previous developer. In this article I want to show you my steps and tell you my experience.

Analyse the Project

The very first step is to get an overview of what is going on. If it’s a website, click your way through all the functionality: open modals, send forms and so on. While doing that, have the Developer Tools open, to see if any errors are popping up or anything is getting logged. If it’s a Node.js project, open the command line interface and go through the API. In the best case the project has an entry point (e.g. main.js, index.js, app.js, …) where either all modules are initialized or, in the worst case, the entire business logic is located.

Find out which tools are in use. jQuery? React? Express? Make a list of everything that is important to know. Let’s say the project is written in Angular 2 and you haven’t worked with that, go straight to the documentation and get a basic understanding. Search for best practices.

Understand the Project on a Higher Level

Knowing the technologies is a good start, but to get a real feel and understanding, it’s time to look into the unit tests. Unit testing is a way of testing functionality and the methods of your code to ensure your code behaves as intended. Reading — and running — unit tests gives you a much deeper understanding than reading only code. If they are no unit tests in your project, don’t worry, we will come to that.

Create a Baseline

This is all about establishing consistency. Now that you have all information about the projects toolchain, you know the structure and how the logic is connected, it’s time to create a baseline. I recommend adding an .editorconfig file to keep coding style guides consistent between different editors, IDE’s, and developers.

Coherent indentation

The famous question (it’s rather a war though), whether spaces or tabs should be used, doesn’t matter. Is the codebase written in spaces? Continue with spaces. With tabs? Use them. Only when the codebase has mixed indentation is it necessary to decide which to use. Opinions are fine, but a good project makes sure all developer can work without hassle.

Why is this even important? Everyone has it’s own way of using an editor or IDE. For instance, I am a huge fan of code folding. Without that feature, I am literally lost in a file. When the indentation isn’t coherent, this features fails. So every time I open a file, I would have to fix the indentation before I can even start working. This is a huge waste of time.

// While this is valid JavaScript, the block can't
// be properly folded due to its mixed indentation.
 function foo (data) {
  let property = String(data);

if (property === 'bar') {
   property = doSomething(property);
  }
  //... more logic.
 }

// Correct indentation makes the code block foldable,
// enabling a better experience and clean codebase.
function foo (data) {
 let property = String(data);

 if (property === 'bar') {
  property = doSomething(property);
 }
 //... more logic.
}

Naming

Make sure that the naming convention used in the project is respected. CamelCase is commonly used in JavaScript code, but I’ve seen mixed conventions a lot. For instance, jQuery projects often have mixed naming of jQuery object variables and other variables.

// Inconsistent naming makes it harder
// to scan and understand the code. It can also
// lead to false expectations.
const $element = $('.element');

function _privateMethod () {
  const self = $(this);
  const _internalElement = $('.internal-element');
  let $data = element.data('foo');
  //... more logic.
}

// This is much easier and faster to understand.
const $element = $('.element');

function _privateMethod () {
  const $this = $(this);
  const $internalElement = $('.internal-element');
  let elementData = $element.data('foo');
  //... more logic.
}

Linting Everything

While the previous steps were more cosmetic and mainly to help with scanning the code faster, here we introduce and ensure common best practices as well as code quality. ESLint, JSLint, and JSHint are the most popular JavaScript linters these days. Personally, I used to work with JSHint a lot, but ESLint has started to become my favorite, mainly because of its custom rules and early ES2015 support.

When you start linting, if a lot of errors pop up, fix them! Don’t continue with anything else before your linter is happy!

Updating Dependencies

Updating dependencies should be done carefully. It’s easy to introduce more errors when not paying attention to the changes your dependencies have gone through. Some projects might work with fixed versions (e.g. v1.12.5), while others use wildcard versions (e.g. v1.12.x). In case you need a quick update, a version number is constructed as follows: MAJOR.MINOR.PATCH. If you aren’t familiar with how semantic versioning works, I recommend reading this article by Tim Oxley.

There is no general rule for updating dependencies. Each project is different and should be handled as such. Updating the PATCH number of your dependencies shouldn’t be a problem at all, and MINOR is usually fine too. Only when you bump the MAJOR number of your dependencies, you should look up what exactly has changed. Maybe the API has changed entirely and you need to rewrite large parts of your application. If that’s not worth the effort, I would avoid updating to the next major version.

If your project uses npm as dependency manager (and there aren’t any competitors) you can check for any outdated dependencies with the handy npm outdated command from your CLI. Let me illustrate this with an example from one of my projects called FrontBook, where I frequently update all dependencies:

Image of npm outdated

As you can see I have a lot of major updates here. I wouldn’t update all of them at once, but one at a time. Granted, this will take up a lot of time, yet it is the only way to ensure nothing breaks (if the project doesn’t have any tests).

Let’s Get Our Hands Dirty

The main message I want you to take with you is that cleaning up doesn’t necessarily mean removing and rewriting large sections of code. Of course, this is sometimes the only solution but it shouldn’t be your first and only step. JavaScript can be an odd language, hence giving generic advice is usually not possible. You always have to evaluate your specific situation and figure out a working solution.

Establish Unit Tests

Having unit tests ensures you understand how the code is intended to work and you don’t accidentily break anything. JavaScript unit testing is worth its own articles, so I won’t be able to go much into detail here. Widely used frameworks are Karma, Jasmine, Mocha or Ava. If you also want to test your user interface, Nightwatch.js and DalekJS are recommended browser automation tools.

Continue reading %Untangling Spaghetti Code: How to Write Maintainable JavaScript%

LEAVE A REPLY