24
Write Readable & Consistent Functions
p.s I also publish my articles on my blog because I get to decide the style :)
I try to watch coding-related conference talks once in a while and thought that my recent pick of Design Strategies for JavaScript API by Ariya resonate with me. Here's a summary and discussion on the topic of code quality based on ideas from the talk.
While the talk focuses on API design, it speaks to all programmers as writing functions that are used across classes, modules, and files is a common task. What's worse than inconveniencing others is the fact that some functions are misleading even to the author. When we do write functions, we should strive to achieve the following:
- Readable
- Consistent
If you can't pronounce or easily spell out the function name, it deserves a better name.
Often the first toolkit that we get hold of when we start to modify a function to meet the new requirements is "Boolean parameter". We add a true/false value at the end of the existing parameter list. It won't be long before our list of parameters grows out of control and we can't pinpoint which parameter is responsible for what anymore.
One potential fix is to use an option object:
person.turn("left", true) // turn left and take one step forward
person.turn("left", false) // turn left and stay at the same place
// change to
person.turn("left", {"stepForward": true})
person.turn("left", {"stepForward": false})
Another refactoring idea is to abstract out the commonly used function into a separate function, so perhaps:
person.turn("left", true) // turn left and take one step forward
person.turn("left", false) // turn left and stay at the same place
// change to
person.turnAndStepForward("left") // if this combination is often used
Do not jump into abstractions too quickly though.
This might appear to be a glass-half-full or glass-half-empty subjectivity point of view. However, the talk gave by Ariya suggests that we should avoid double negatives such as x.setDisabled(true)
and use x.setEnabled(true)
instead. This is to help with understanding statements more intuitively. It is also important to use one over the other consistently.
I think this is one of the main takeaways I gathered from the talk. While I try my best to write immutable functions, some level of mutability is hard to avoid. When we do have functions that can either be mutable or immutable, it might be beneficial to indicate that in the function name. For example:
aString.trim() // modify the existing string
aString.trimmed() // only return a modified string
To be consistent is to be predictable. This relies on making smart observations about the existing norm and agreed-upon conventions. With the knowledge of what we believe all programmers should know, which can be patterns and structures that are familiar, best-practices, or stood the test of time, we can write functions that will turn out to be unsurprising to potential readers.
On a smaller scale, if two functions do similar things, they ought to be named similarly. This is an extension of the idea of polymorphism.
For example:
person.turn("left")
car.steer("left")
Perhaps a better way to name the functions will be to use turn
for both.
person.turn("left")
car.turn("left")
In the same vein, having consistent parameters will help to reduce mistakes. For example:
person.rotate(1, 2) // first horizontally, second vertically
rectangle.rotate(1, 2) // first vertically, second horizontally
Suppose that both objects have a method called rotate
but the parameters are two different ordered pairs of the same values. That is a disaster in the making.
With the help of powerful IDEs, we now enjoy the convenience of having documentation of functions available as we write code. This may make recognizing what a function is doing or what each parameter means easier, but it should not be an encouragement to write bad functions. Also, if someone is already making a mess writing code, it may not be wise to trust his/her documentations, if there is any...
24