The steps to better code quality are ordered by importance - don’t try to implement every step at once - but do it gradually and depending on the needs of your business. Don’t forget: Getting things done is more important than high code quality!
Step 1: Use a linter
A linter helps developers discover problems with their code without executing it - problems like the missing bracket mentioned before can be easily found and highlighted. But it doesn’t stop there!
Then, it can highlight stylistic issues like indentation and therefore help you maintain a consistent code formatting:
My favorite feature is the suggestion of Best Practices. ESLint actively helps you learn better ways of doing something.
!= comparison operators, e.g.
a == b. What many developers coming from languages like Java don’t know: These comparisons are not type-safe. If
Would you have thought that
3 == "03" results in
true? Therefore, you should always use the strict equality operators
!== - ESLint reminds you of that.
ESLint supports a lot of rules, but doesn’t force any of them on you by default. Instead, during the initialization process, you can either activate a recommended set of rules or use one of the style guides provided by the community. I prefer the one by Airbnb - it leads to a modern code style & promotes new powerful language features.
For getting problems in your code highlighted in your favorite code editor, you should install a package, e.g. SublimeLinter for Sublime Text 3. Also, you should know about the nice
--fix option in the ESLint command line interface, which can fix problems for you, mainly of stylistic nature.
Step 2: Make your editor help you more
If you come from Java, you will most likely have worked with an IDE like IntelliJ or Eclipse - they provide useful powerful auto-complete suggestions while you type, e.g. displaying the parameters a method needs or the properties an object has.
The two main competitors for web development are Sublime Text and Atom. There are valid reasons for still using them, e.g. the speed of Sublime and the extendability for Atom. Getting good auto-complete support is just a bit harder.
For Atom, install the packages autocomplete-plus and atom-ternjs. For Sublime, I use tern_for_sublime. After installation, I recommend to modify your configuration a bit, especially activating
tern_argument_completion and extending your Sublime
Step 3: Use a language reference
MDN is awesome - use it with an API documentation browser app to search through it more easily, faster and with offline-support. I use Dash (paid, macOS only), a free alternative would be the DevDocs web app. In both apps, you then then e.g. just type “array.” and instantly get a list of available methods on array objects.
Step 4: Write tests
I would argue that frontend testing is less important, at least as long as you don’t have much logic in your frontend. Testing your code logic is important, testing your UI with tools like the browser-automation tool Selenium is almost always an overkill.
Some general words about testing:
In my opinion, the two greatest advantages of writing tests are:
- Confidence in code: After refactoring your code or adding new features, you still know that your old code is working as expected
- Perspective-switch: You are forced to take the perspective of a user of your code - this will enable you to rethink your structure and make life better for other developers depending on your code
When writing tests, remember that you can test on different layers of your application, from lower levels (testing methods) to higher layers (testing server responses). If you are of the opinion that testing your code is not possible at all, this is usually a sign that your method or feature should be refactored.
Testing individual methods corresponds to Unit Testing. Writing unit tests for every method is tedious and often not worth it, e.g. if a method is really short and simple (which should be the goal for every method). That said, every method should still be written in a style that adding tests for it would be easy.
It is more important to test the upper layers of your applications, e.g. if your backend responds correctly to certain queries, where the response to the query is generated by multiple methods working together. This type of test is called Integration Testing.
Integration tests give you information about the big picture and typically don’t need to be maintained as much as unit tests. Focus on writing integration tests, write unit tests only for your most critical methods.
Step 5: Strive for pure methods & functional programming
One of the most difficult and crucial tasks in developing an application is managing the application’s state. One typical anti-pattern that complicates state management: When a method doesn’t return the same value, although the same input was provided.
To illustrate this scenario, imagine the following code:
Now, when called,
getResult(100) will mostly return
100. But when another part of the code sets
x to a different value than
3, the exact same call will throw an exception.
Especially annoying: Although the exception gets thrown when
getResult gets called again, but
x could have been modified a long time before that. Therefore, it is quite hard to find out who caused
x to change. Testing and debugging the flow of your application becomes very hard.
That’s why, in your code, you should strive for pure functions. Pure functions have two characteristics:
- With the same input, a pure function always returns the same output. From this follows: A pure function’s output value shouldn’t depend on anything that may change during the programs execution. It should just depend on its input and the program’s constants.
- A pure function doesn’t have any side effects, e.g. it doesn’t change any of the program’s variables.
The scenario from before wouldn’t have been possible with
getResult being a pure function. Pure functions make reasoning about a program’s state easier.
In general, pure functions are one of the key concepts in functional programming. As a developer, it is very beneficial to know about this style of programming and get familiar with pure functional programming languages like Haskell or Elm. Why? By applying functional programming principles, certain types of errors just disappear or become much easier to test and debug.
Step 6: Disallow deployment on Linter or Test errors
This step acts like a thumbscrew for steps 1 (Use a linter) and 4 (Write tests). If you use software like Continuous Integration (CI) or Continuous Deployment (CD), of which you should probably use at least CI at some point, you can let your server run linter checks & tests automatically for you.
In case there are linter errors in some of the files you have changed, or some of the tests are now breaking, the server could then disallow your changes to go into the main branch of your application development. This really reminds & forces you to keep a high level of code quality.
But don’t implement this step too early - a pedantic server like this will make you move a bit slower. This change really is just needed for larger teams where not everyone might have a high incentive to keep a high code quality standard or breaks stuff out of laziness without running the tests locally.
Step 7: Add static typing
On the one hand, your editor can give you even better suggestions, e.g. it won’t suggest a variable holding a string as a parameter for a method requiring a number, and uncovers many errors - think of the ESLint “possible errors” category on steroids.
Here an example of an error that can be prevented by using type annotations:
Normally, here, you wouldn’t expect
multiplyByTwo to be able to handle any strings at all - but now, it returns a “correct” value for some strings, while, for others, it returns
NaN. With a type annotation, you can explicitly define
number as your expected input type and trigger an error if the type is violated:
On the other hand, your editor can offer you more powerful refactoring tools and makes you more confident that a refactoring was successful. This makes it especially useful for larger projects.
TypeScript and Flow are quite similar in features and syntax. I have used both - TypeScript in a university project and Flow at Facebook - and cannot say anything bad about either of them. I got the feeling that TypeScript has gained more traction; it is used in Angular, and has prominent users such as Slack for their desktop app. That’s why I would probably use TypeScript if I were to start a new project now.
Both TypeScript and Flow don’t require your whole codebase to be typed. Theoretically, you can just plug them in and your code should still work as before. You can then gradually add type annotations and enjoy the improved editor capabilities.
Again, think twice before going all-in with TypeScript or Flow - they are most useful with larger projects. For smaller projects, especially if you are not familiar with the tool yet, it could make you loose focus of developing your actual application and slow you down.
That’s all? Of course it isn’t!
Of course these steps neither will wonderfully make your actual product appear out of nowhere nor will you automatically have top-notch software quality. There are many more areas to focus and improve on.
Besides many more things on the technical side (e.g. learn to use the debugger, learn to split up your code into smaller easy-to-understand methods, learn to document your code in an appropriate way), many words could be spent on the non-technical, soft skill side of the game: Learn how to identify and prioritize your product’s customer requirements, estimate your tasks, and set up a development process where every developer in your team can be productive and continuously improve.
- PowerPoint: Inserting Source Code with Syntax Highlighting
- Recommendation: Deep dive guide on web server fundamentals
- Where are globally-installed Go tools located & how to execute them (when using Go similarly to 'npm install -g')
- Compilation of modern CLI Tools (Unix, Shell, Terminal)
- (Linux) file system basics
- How-to: Enter your SSH key passphrase only once per terminal session
- Awesome CSS Transitions Tutorial
- Calculating an SSH key fingerprint
- Parcel.js Web Application Bundler: Enabling Tree Shaking for much smaller builds
- Wagtail: Open Source Django CMS