Isn't it fun to find a comment in code that's completely out of place and useless?
It's an easy mistake to make: you change some code, and forget to remove or update the comment. A bad comment won't break your code, but imagine what would happen when debugging. You read the comment. It says one thing, while the code does another. You'll probably end up wasting time figuring it out, and in the worst case, it might even mislead you!
But writing code with zero comments isn't an option. In my more than 15 years of programming experience, I've never seen a codebase where comments were completely unnecessary.
However, there are ways to reduce the need for comments. We can make use of certain coding techniques to clarify our code, simply by using the programming language's features to our advantage.
Not only does this help make our code easier to understand, it can also help improve the design of the program overall!
This type of code is often called self documenting. Let me show you how you can take this approach to coding right now. While the examples I'll present here are in JavaScript, you can apply most of the techniques in other languages as well.
Overview of Techniques
Some programmers include comments as part of self-documenting code. In this article, we'll only focus on code. Comments are important, but they're a large topic to be covered separately.
We can split the techniques for self-documenting code into three broad categories:
- structural, where the structure of code or directories is used to clarify the purpose
- naming related, such as function or variable naming
- syntax related, where we make use of (or avoid using) features of the language to clarify code.
Many of these are simple on paper. The challenge comes from knowing when to use what technique. I'll show you some practical examples as we tackle each one.
Structural
First, let's look at the structural category. Structural changes refer to shifting code around for enhanced clarity.
Move code into a function
This is the same as the "extract function" refactoring --- meaning that we take existing code and move it into a new function: we "extract" the code out into a new function.
For example, try to guess what the following line does:
var width = (value - 0.5) * 16;
Not very clear; a comment here could be quite useful. Or, we could extract a function to make it self documenting:
var width = emToPixels(value);
function emToPixels(ems) {
return (ems - 0.5) * 16;
}
The only change was I moved the calculation into a function. The function's name is descriptive of what it does, so the code no longer needs clarification. As an additional benefit, we now have a useful helper function that you can use elsewhere, so this method also helps reduce duplication.
Replace conditional expression with function
If clauses with multiple operands can often be hard to understand without a comment. We can apply a similar method as above to clarify them:
if(!el.offsetWidth || !el.offsetHeight) {
}
What is the purpose of the above condition?
function isVisible(el) {
return el.offsetWidth && el.offsetHeight;
}
if(!isVisible(el)) {
}
Again, we moved the code into a function and the code is immediately much easier to understand.
Replace expression with variable
Replacing something with a variable is similar to moving code into a function, but instead of a function, we simply use a variable.
Let's take a look at the example with if clauses again:
if(!el.offsetWidth || !el.offsetHeight) {
}
Instead of extracting a function, we can also clarify this by introducing a variable:
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}
This can be a better choice than extracting a function --- for example, when the logic you want to clarify is very specific to a certain algorithm used only in one place.
The most common use for this method is mathematical expressions:
return a * b + (c / d);
We can clarify the above by splitting the calculation:
var multiplier = a * b;
var divisor = c / d;
return multiplier + divisor;
Because I'm terrible at math, imagine the above example has some meaningful algorithm. In any case, the point is that you can move complex expressions into variables that add meaning to otherwise hard-to-understand code.
Class and module interfaces
The interface --- that is, the public methods and properties --- of a class or module can act as documentation on its usage.
Let's look at an example:
class Box {
setState(state) {
this.state = state;
}
getState() {
return this.state;
}
}
This class could contain some other code in it as well. I purposely kept the example simple, to illustrate how the public interface is documentation
Can you tell how this class should be used? Maybe with a little bit of work, but it isn't very obvious.
Both of the functions have reasonable names: what they do is clear from their name. But despite this, it's not very clear how you should be using them. Most likely you would need to read more code or the documentation for the class to figure it out.
What if we changed it to something like this:
class Box {
open() {
this.state = 'open';
}
close() {
this.state = 'closed';
}
isOpen() {
return this.state === 'open';
}
}
Much easier to see the usage, don't you think? Notice that we only changed the public interface; the internal representation is still the same with the this.state
property.
Now you can tell at a glance how the Box
class is used. This shows that even though the first version had good names in the functions, the complete package was still confusing, and how, with simple decisions like this, you can have a very big impact. You always have to think of the big picture.
Code grouping
Grouping different parts of code can also act as a form of documentation.
For example, you should always aim to declare your variables as close to where they are being used as possible, and try to group variable uses together.
This can be used to indicate a relationship between the different parts of the code, so that anyone changing it in the future has an easier time finding which parts they may also need to touch.
Consider the following example:
var foo = 1;
blah()
xyz();
bar(foo);
baz(1337);
quux(foo);
Can you see at a glance how many times foo
was used? Compare it to this:
var foo = 1;
bar(foo);
quux(foo);
blah()
xyz();
baz(1337);
With all the uses of foo
grouped together, we can easily see which parts of the code depend on it.
Use pure functions
Pure functions are much easier to understand than functions that rely on state.
What is a pure function? When calling a function with the same parameters, if it always produces the same output, it's most likely a so-called "pure" function. This means the function should not have any side effects or rely on state --- such as time, object properties, Ajax, etc.
These types of functions are easier to understand, as any values affecting their output are passed in explicitly. You won't have to dig around to figure out where something comes from, or what affects the result, as it's all in plain sight.
Another reason these types of functions make for more self-documenting code is you can trust their output. No matter what, the function will always return output only based on what parameters you give it. It also won't affect anything external, so you can trust it won't cause an unexpected side effect.
A good example of where this goes wrong is document.write()
. Experienced JS developers know you shouldn't use it, but many beginners stumble with it. Sometimes it works well --- but other times, in certain circumstances, it can wipe the whole page clean. Talk about a side effect!
For a better overview of what a pure function is, see the article Functional Programming: Pure Functions.
Directory and file structure
When naming files or directories, follow the same naming convention as used in the project. If there's no clear convention in the project, follow the standard for your language of choice.
For example, if you're adding new UI-related code, find where similar functionality is in the project. If UI-related code is placed in src/ui/
, you should do the same.
This makes it easier to find the code and shows its purpose, based on what you already know about the other pieces of code in the project. All UI code is in the same place, after all, so it must be UI related.
Continue reading %15 Ways to Write Self-documenting JavaScript%
by Jani Hartikainen via SitePoint