Climbing The Code Quality Ladder
We’ve all heard the term “high quality code”, but what does it really mean?
I’ve been writing software in a professional setting for a long time. Thirteen+ years and four companies (Microsoft, Google, Sumo Logic, and now Leap.ai) later, I’ve summarized the Code Quality Ladder. The Code Quality Ladder is a simple framework to determine whether a piece of code is high quality.
I hope my Code Quality Ladder will help early career software engineers reach the top of their game (couldn’t resist the pun).
Code Quality Level I: Correctness
Writing code that’s correct is the most basic requirement for a software engineer. While this might sound obvious and silly, it’s easier said than done and actually quite hard to accomplish in real life.
Let me use one of my favorite interview questions as an example. Many popular spreadsheet products organize their columns from A to Z, followed by AA, AB, etc,. Write a function to convert a column name to its corresponding column number. That is, A becomes 1, B becomes 2, C becomes 3, and so on.
Nearly everyone I’ve interviewed with this question quickly points out that the function is a base-26 conversion. They then immediately start to code it up. However, the percentage of people who can actually code the function correctly is alarmingly low. Don’t believe me? Let’s take a look at the following implementation, written in C++.
Can you identify any bugs in this code? If you can’t, try harder.
There are several issues in this code:
const char*
is a C-style string. It can be dangerous to use a C-style string in C++ code because a C-style string is assumed to be NULL terminated, but a C++ string isn't necessarily so. Try usingstd::string
instead.- Pointers/references can be NULL, and this code will crash when a NULL pointer is passed in. Good luck debugging a server crash due to a NULL pointer at 3 a.m. while your users can’t access your website.
- You might say, “but I will validate the input to make sure I don’t call it with NULL pointers”.
- Sorry, you can’t in real life. Once your code is written, you won’t know who’s calling it and under what conditions.
- Does this code work if the input is a lowercase string?
- What about non-alphabet characters in the input?
- What happens if the input is a Unicode string?
- What would happen if the input string has seven As in it? That’s right, integer overflow — your code needs to deal with that too…
When it comes to code, it’s important to remember Murphy’s Law: anything that can go wrong will go wrong. That means it’s imperative to write code that can handle any input, no matter what it is. If you can do that, you will avoid giving the caller of your code any unwanted surprises.
To achieve this level of coding “correctness”, you need to have a good understanding of code testability. You must also have extensive unit tests for your code.
Code Quality Level II: Efficiency
Okay, now you’ve completed Level I, and let’s say that your code is mostly correct. By the way, there’s no such thing as bug-free code, so “mostly correct” is already a very high bar.
Imagine you push your code into production, and ta-da! Your website goes down. You trace through lots of server logs while panicking and sweating. You finally narrow the problem down to a function call that was overwhelmed by the requests, couldn’t finish, and timed out.
Welcome to the Efficiency Land!
What is “efficiency,” you ask? “Is efficiency the same as Big-O notation?”
The answer is: efficiency and Big-O are close, but not the same.
Here’s an example to illustrate what I’m talking about: what is the fastest sorting algorithm? You might say that quick-sort is the fastest sorting algorithm and it’s O(n*log n)
on average.
Before we dig in, let’s have a quick review. “Big-O notation” describes the maximum time an algorithm will take to complete a function. So if an algorithm is O(n*log n)
, it means for whatever value n
, the run-time is <k*n*(log n)
for some constant k
. When n
is large enough, we know that O(n*log n)
will be more efficient than O(n^2)
.
In reality, however, there are two conditions that need consideration when it comes to algorithm speed:
- The size
n
is important. For example, if we are sorting a 6-entry array, anO(n^2)
bubble sort is likely much faster thanO(n*log n)
quick-sort. - The constant
k
is also very important. In Computer Science classes, we always ignorek
in algorithm complexity analysis, but in real life,k
is super critical. AnO(n^2)
solution withk=1
will be faster than anO(n)
solution withk=1000
for anyn<1000
. To put it a different way, an in-memory bubble sort might be faster than an on-disk mergesort.
So far we’ve only covered efficiency as it relates to time. But there are also other types of efficiencies to consider. For instance, space efficiency, power efficiency, algorithm convergence speed, user interaction efficiency, etc,. I won’t go into details in all these dimensions, but suffice it to say that code efficiency is very context-dependent. The goal when writing any code is to be as efficient as possible under the given constraints.
One last note about efficiency: overdoing it is bad too. This is known as “premature optimization” and should be avoided. Only optimize what you need to optimize. When unsure, use the simplest solution first, and then do performance testing to decide if you need to improve the code further.
Code Quality Level III: Readability
Okay, now that you know how to write code that’s both correct and efficient. The next challenge is to make sure the code is also easy for others to read and understand.
Modern software engineering is always about teamwork. The code you write is not yours. It belongs to your team (or even other teams). Let that sink in…It is, then, in your best interest to set your teammates up for success. Enable them to maintain your code. You wouldn’t want to get a phone call on vacation telling you to get online and fix your code because no one else on the team can understand it, right? It happens to a lot of engineers. A company once asked me for help trying to understand a piece of code I wrote a year ago — after I’d left that company! Trust me, not a good experience.
A common industry practice used to get teams on the same page is called a coding style guide. In a coding style guide, a set of agreed-upon coding styles are shared by an entire team or organization. I recommend the Google coding style guide, which is (and sometimes too strictly) followed by the entire Google company. It is also now publicly accepted by many organizations. It is thorough and covers many common coding languages.
The Google coding style guide is very well written, so I won’t repeat the content here. There is one thing from it I want to emphasize. Always follow your team’s existing convention. Do this even if it’s slightly different from other commonly accepted conventions. The most important thing is for your team’s code to be consistent.
Adhering to a coding style is a good start, but other practices are also needed to make the code readable. For example, comments should not simply duplicate the code as written. They should explain the thought behind the code and give context to the code. Make them useful by being descriptive, specific.
Here, I picked a very trivial example to illustrate this philosophy. Compare the following two ways of commenting:
int index = -1; // init to -1; useless comment
vs.
int index = -1; // init to invalid value; useful comment
(Update: this is only an example to illustrate commenting, but not an example of best way to write code. As pointed out in a response, a better way to write this piece of code isint index = INVALID_VALUE; // no comment needed
)
Still feel this example is too trivial, and want a more meaty instance? Please take a look at the Google open source LevelDB header file. It is an excellent example of good commenting, and originally written by Jeff Dean and Sanjay Ghemawat (the two Senior Fellows at Google).
So the key takeaway about readability is this: your teammates will be the judge of your code’s readability. The goal is for your colleagues to easily understand your code. You have much better things to do on vacation!
Code Quality Level IV: Extensibility
If you’re writing code that’s correct, efficient and readable, that puts you in the top 10% of coders at any organization. The next challenge — extensibility — is how you ascend to the summit.
Problems change. Code evolves. A good programmer can foresee changes that will come, take them into account, and write code that’s future-compatible. This is the magic of “extensibility”.
Let’s use MapReduce as an example. MapReduce came out of the Google Search team where tons of engineers were going through similar tasks to analyze data. Jeff Dean and Sanjay Ghemawat saw a need to standardize the framework to make engineers’ lives easier.
To be honest, there are many ways to standardize such a framework. There are also criticisms that the concept of Map and Reduce isn’t new. However, the power of MapReduce is that it standardizes to the right level to make using it easy. That means it’s specific enough for use by Google Search engineers and also applicable to other use cases beyond just Google Search. As we all know it, MapReduce has been so widely adopted inside and outside of Google, and its creation became the cornerstone of the Web’s Big Data era.
This is the key of extensibility: knowing what level is the right level. Answering this question is strangely more art than science. If you make your code too specific, the use case will be limited and every time the requirement changes, the code will need to be modified. If you make your code too generic, your clients need to do a lot to build on top of your code for their specific needs, and then it’s too difficult to use.
It takes a lot of experience and intelligence to master extensibility. For most, the way to improve extensibility skills is to learn from our mistakes — try, fail, rinse, and repeat. Even at Google, there are at least four or five frameworks that are built on top of MapReduce to make chained MapReduces easier. Even with Jeff and Sanjay around, it still took Google more than one try to get that right.
Summary
Correctness, efficiency, readability, and extensibility are not independent of each other. Readable code is more likely to be correct; a low-efficiency code is hard to extend. For each software engineer, the ultimate goal is to do well in all these areas. But at any given moment, each person is at a certain level. My suggestion is to assess your own level of expertise. Consider where you spend most of your time and effort, and then focus on mastery in that area before moving onto the next level. Avoid tackling a level before you are really ready. Keep this in mind — you cannot build excellent skills or tackle more challenging problems with a weak foundation.
I wish everyone a successful climb to the code quality summit.
Thanks for reading. If you have thoughts on this, be sure to leave a comment.
If you’re interested in job opportunities at tech companies, give Leap.ai a try.
My feeling readability can easily be #2 ahead of efficiency. Basically, before you optimise the code better be well understandable/readable.