Every programming language has one or more coding style guidelines. All developers know that but some of them don’t care. They practice bad programming habits for years without realizing the importance of the issues they caused.
As one of the hiring managers at Zuhlke Hong Kong, I’ve seen a lot of code smells during coding interviews. Here are the top 16 bad coding practices organized into 6 categories: coding style, dirty code, testing, error handling, code complexity, and optimization.
We cover the first 8 code smells in Part 1 and the rest in Part 2.
Ignoring the importance of coding styles usually indicates the lack of experience in creating software and reading other people’s code. It’s hard for inexperienced developers to understand the benefits of using a consistent coding style.
Consistency is one of the most dominant attitudes that good developers have. Being consistent is a result of logical thinking, which is a core element in software development. Every action that a developer chooses to take is because of some reasoning that the developer believes that it is true. Given the same environment, with the same input, we expect the same output. This is the same for coding style.
Let’s compare the following snippets. Only one of them I consider as consistent.
Snippet 1:
if(document.ownerId == user.id) {
doSomething();
}
Snippet 2:
if (document.ownerId == user.id){
doSomething();
}
Snippet 3:
if (document.ownerId == user.id) {
doSomething();
}
Snippet 4:
if(document.ownerId== user.id) {
doSomething();
}
Snippet 5:
if(document.ownerId==user.id){
doSomething();
}
Answer: Snippet 3
Compilers ignore most of the spaces we used in our code, so what are they for? Readability.
If you use a space between )
and {
for better readability, then why wouldn’t you also use a space between if
and (
as illustrated in snippet 1?
Finding good names is not easy. It usually takes me a considerable amount of time. It’s part of the process to create good software. Some people don’t agree and they name their variables or methods like this:
let value = 1;
let curValue = 0;
let previousVal = -1;
“Current” is represented in its short form in curValue
, but this is not the case for “previous”. Instead, “value” is shorten as val
in previousVal
. The flow of the mind of these people is so random that no one can explain the logic behind such naming style.
This is the exact opposite of clean code. When you see dirty code, you can feel its ugliness and want to look away immediately. You feel so uncomfortable that it makes you hard to breath. It stinks and lingers for at least an hour.
Duplicated code almost always indicates a failure in software design. So why does duplication ever exist? Some reasons I’ve heard from coding interviews are:
All of the above are the result of incompetence in software design.
Who needs comments when names are self-explanatory and logic is clear?
Examples of bad names I saw in coding interviews:
retVal
: I cannot find an even less meaningful name for this. All values returned by functions are called return-value. So what is this particular retVal referring to?tmp
: Almost every variables within a function scope is short-lived and temporary. So how temporary should one consider it as temporary? Even a temporary variable deserves a more meaningful name.There are two kinds of dead code:
Unreachable code is less frequently seen during coding interviews because the problem to be solved isn’t that complicated.
One the other hand, commented code is a bigger issue that usually indicates a lack of understanding of source control.
If you ever want to comment code and think that you may need it at a later time, a better alternative is to use Git. For some IDEs, such as those made by IntelliJ, they provide local source control by default and the full history of your code is committed locally automatically. No one ever needs to comment any code.
Every piece of sufficiently complicated code needs proper tests. Some people test manually, some test their code by logging, and good engineers write unit tests.
More than half of the candidates I’ve met in coding interviews didn’t write any unit tests. Some refused to write unit tests even after they are requested to do so. One simply cannot be sure their code works without any tests. Even if their code appears to work, there is no way to be sure it still works after refactoring.
Writing unit tests is the first step to become better engineers. The next step is to write meaningful tests. Having tests that cover every single possible outcome is not meaningful and wastes time. Having tests that cover a few random cases is not any wiser.
Good tests should cover a few cases of happy paths and all edge cases. The ability to design good tests is one of the assessments for our technical interviews.
It is also called Caveman debugging. It is a popular debugging technique decades ago and is the preferred way of debugging in extreme situation such as kernel programming where you don’t have any better alternatives.
Those candidates who print intermediate values to console would argue that console logging is an easier and faster way to debug. It is almost certain that they didn’t know how to write unit tests and it would take longer time to complete the coding test during technical interviews.
Writing unit tests is as easy as printing to console, if not easier, once you learned how to do it. Additionally, unit tests are needed for refactoring in which console logging is often removed because it reduces code readability.
If someone would consider printing anything to console, that means there exists some code that is hard to understand. This is the case when unit tests are exactly for.
Continue with numbers 9 to 16 code smells in Part 2.
The important thing is to recognize code smells, especially in your own code. These are some of the very best books on how to write code that rocks: