Have you ever reflected on your code and wondered if you could have made it better? I’m here to tell you that you can write high-quality code, and I can show you how to do it, too.
Before we get started, I want to define what exactly I mean when I say “high-quality code.” When I think of high-quality Code, I think of code that:
-You can read once and understand
-Has minimal bugs
-Follows the standards of the language it’s written in
-Does what you expect it to do, and has proof of its success
Now that we know what we’re aiming for, we need to learn how to write clean code. But how does one do that? In the Simple Programmer post How To Write Clean Code, John recommends reading two books: Code Complete and Clean Code. I highly encourage you to take his advice. However, I know you’re busy, and you might not have the time to read both of those books. Let me break it down for you by sharing the most important things I’ve learned about producing quality code during my years in the field.
I’ve had the pleasure of working on a lot of projects over the years. I’ll be the first to admit that they haven’t always turned out the greatest, but they have always helped me learn how to become better. Today, I am confident in my ability to write quality code. Without further ado, the following is a list of tips and tricks I’ve compiled that have helped me to really improve the quality of my own code.
1. Use a Linter
So many problems can be solved by using a code linter. For anyone who’s unfamiliar with linters, a code linter reads your code and outputs errors and warnings if your code is not compliant with the specifications and standards of a language. These warnings may seem insignificant at the time, but, like the lint in your house, they add up over time. To get the most out of using a linter, I would suggest the following tricks:
Enforce the Language or Framework Standard
I highly recommend running a linter on your code to enforce the most widely accepted set of standards for the language or framework you’re working in. For example, if you’re writing Python, I would suggest you use the pylint package, which enforces rules defined in PEP-8. If you’re writing an Angular app, I would suggest using a linter configuration that follows the Angular style guide.
Integrate Your Linter With CI
A linter really isn’t going to help you at all unless you’re using it regularly, and from my experience, developers don’t typically do this unless they’re forced to. It only takes minutes to learn how to use a linter properly, so why do so many developers neglect to do so? I think it’s because we as developers are often overconfident in our code and we hate adding additional steps to our development process.
For this reason, I suggest you set up a continuous integration build pipeline with a service like Atlassian’s Bamboo, Jenkins, or Travis CI. In your build pipeline, you should make linting the first step. That way, if your code doesn’t pass the lint test, it doesn’t get built, tested, or published.
Using a linter is all about making it as hard as possible for you to write crappy code.
2. Comment Your Code “Just Enough”
There are usually two types of developer when it comes to commenting code: the type that comments everything and the type that doesn’t comment anything. The truth is, there are benefits and drawbacks to both sides. One the one hand, leaving too many comments in your code can just make it more cluttered and harder to read. On the other hand, having no comments whatsoever can leave a future developer in a state of turmoil.
The secret is that you should add comments when they provide value. For me personally, I find the following tricks valuable for writing good, meaningful comments:
I find it helpful to write a 2–3 line comment at the top of a file to give you a brief overview of the goal and the scope of the code within. I shouldn’t have to spend more than 10 seconds looking at a file to know what its primary focus is.
However, I don’t recommend following the standard of putting your name, email address, date, filename, license, etc. in the “top-of-file” comment. All of this information should be available elsewhere, and it will just clutter your file.
I feel the same way about leaving class-level comments. I think it’s useful to leave a brief comment at the top of every class explaining the primary goals and scope of the class.
However, if your class name is “CarObject,” please don’t leave a comment saying, “An object to represent Cars.” Leave meaningful comments or don’t leave any at all. If it were me, I would say something more along the lines of, “Represents Cars and contains references to their parts. Mostly used by X to do Y.”
Functions are a little trickier to determine the value of adding comments to them. On one hand, I believe long functions with complex logic should have comments, if only to save me the time it takes to read the code to understand what it does. On the other hand, I don’t believe simple get/set logic requires a comment. For example, if I have a class level variable named “x,” I would not comment the function entitled “get_x().” This produces clutter with no value.
However, if I had a 100-line function called “calculate_xyz(),” I would leave a comment at the top of that function to let the developer know exactly what the function does. This means describing the inputs, the logic performed, and the outputs. At a minimum, this comment saves the developer from having to read the code in the function to know what it does. It could be the difference between reading 2–3 lines to get a basic understanding and reading 100+ lines for a deeper, but often unnecessary, level of understanding (especially when the developer is only concerned with the inputs/outputs of the function).
I try to use comments inside my code as infrequently as I can. If code is so complex that after reading the file, class, function comment, and the code itself you still don’t understand it, it probably needs to be refactored. The main point is this: try your best to write code that you can understand without having to re-read. If your code can be read once and understood, you won’t need comments cluttering up the innards of your code, and your co-workers will appreciate how comprehensive and concise it is.
3. Writing Legible Code
One of the most important parts of writing quality code is writing legible code. After you write some code, take a short break for however long it takes you to clear it out of your head (this break could be hours, days, or weeks). When you return to the code, if you can’t understand it by reading through it once, it should be refactored in some way, shape, or form.
The following tricks have helped me to write more legible code. These might seem like they don’t help all that much, but making your code as legible as possible can make a world of difference to someone who has to maintain it in the future.
Use Well-Formed Names
Use well-named functions, classes, and variables. A name should tell you the microscopic story of how a thing is used and what it represents. It shouldn’t be a single letter, like “x,” and it shouldn’t be a 70-character-long sentence full of underscores.
Suffix Variables and Functions With Units
For variables and functions dealing with units, append the unit to the end of them. For example, if I have variable called “width” that represents the width of an element in pixels, it would be better named “width_px.” Using this naming scheme ensures the person using the variable cannot overlook the units it is in. A simple thing like using a variable’s unit in its name can really help to mitigate critical bugs in production.
Stick to the Standards
Use the language standards for variables, functions, strings, comments, etc. It’s not necessarily bad to do things differently, but it can throw people off—especially when they’re trying to remember what a function/variable was named, going to search for it, and struggling to remember the right nomenclature.
If you’re using Python, name your classes with Title case and your functions/variables in snake case.
Have a Healthy Grouping of Common Ideas in Files
Don’t put too much information in a single file. I like to stick to a “one class per file” rule. And if the code I’m writing doesn’t necessarily use classes, I try to group the functions by their core goal and separate files that way.
Good separation of common code into separate files can help developers find the code they’re looking for sooner, as well as help them digest the information they’re looking at within the files faster. For example, if all I care about right now is X, and X, Y, Z are in a file together, it’s going to be harder for me to digest the information pertaining to X compared to if X were in a separate file.
Use of Single Quotes vs. Double Quotes
If your language supports representing strings with both single quotes and double quotes, consider using double quotes for strings that could potentially be displayed to the end-user, and single quotes for internal-only strings. Distinctly identifying strings used internally and strings displayed to the user makes you aware of a piece of code’s impact at a glance, should you modify its value.
4. Testing Your Code
In the beginning of this article, I mentioned that in order for code to be considered high quality, you should be able to prove it functions properly. Well, the proof is in the pudding, and the pudding is this: you need to provide a sufficient amount of testing to prove to an outsider that your code works the way you intended it to.
I may seem like just another person preaching good software testing—and that’s because software testing truly is so important to writing quality code. You need to test the expected cases, the edge cases, and the failure cases of your code. If you can be certain that your code will only do exactly what you’ve seen it do in your tests, then you’ve made a great leap towards high-quality code.
Below are a few tricks that will help you best utilize software testing to boost the quality of your code:
Yes, the fabled TDD. If you strictly adhere to the process of writing good, quality tests first, it can do wonders for your code. This is for a number of reasons.
First, if you’re thinking about your test cases before writing your code, then you start developing with a clear idea of what your code needs as input and what it produces as output. Oftentimes, we don’t think about our test cases upfront. As a result, we don’t have the foresight about our program that we would have otherwise had. I’ve found that if I don’t think about my test cases upfront, I often have to rewrite parts of my code, sometimes several times, before calling it done.
It also forces you to write the tests, obviously. Sometimes after we write code, we look at it and say, “That’s really simple; it’d be a waste of time to test that.” If you’re writing tests first, you don’t give yourself the chance to cop-out later on. If you do forsake testing, you won’t be able to truly prove that your code does what you intend it to. As a result, you’ll be more likely to find bugs in your code.
Another good reason I like this approach is that if you write good enough test cases upfront, you can instantly tell when your code crosses the line from incomplete to complete. Furthermore, once you have good test coverage and all the tests pass, you have the proof I was talking about that your code works the way you expect it to.
It’s important to automate your tests as much as possible, so you don’t have an excuse not to execute them. Writing good tests is one great step forward. But to have truly autonomous testing, you should integrate your testing into a continuous integration or build pipeline, so that it happens automatically when you commit your code.
Utilize Different Tiers of Testing
If you’ve already got some tests, you’re on the right track. And frankly, way ahead of where I was on most of the projects I worked on in the past. But it’s not enough to build up a suite of unit tests. To cover all your bases and prove your program works through all of its layers, you need tests for each of those layers.
However, you have to find the right balance. Typically you want the most unit tests, fewer integration tests, and even fewer end-to-end tests. These tiers of tests help you to test your application in full and can also make it easier for future maintainers to discover where in your code any bugs might exist. Developing maintainable code is developing quality code.
5. Code Review
Code review is the ultimate test of how well your code can be understood by others. Take advantage of it whenever you can. Having someone else be able to read and understand your code in front of you is the ultimate test of whether your program is going to be maintainable.
Furthermore, our judgment can often become clouded after we’ve been working on the same project for months on end. It becomes necessary for someone other than yourself to review your code in order to get an unbiased opinion.
The following tricks will help you to utilize code reviews:
Utilize Team Code Reviews
If you’re working as part of a team, you’re in the perfect position to utilize code reviews. One way in which many teams utilize code reviews is that they conduct them after a significant amount of work has been completed, but not so far along the development cycle that they’re going to be releasing the software soon.
If you wait until right before a software release to have a code review, what’s going to happen is this: you will conduct the review, get a bunch of great feedback, deprioritize all of it to get your software released on schedule, then forget about the feedback.
It’s better to plan time into your schedule to make adjustments after conducting code reviews. After your code review is complete, you will actually have time in your schedule to make the necessary changes. This way, you can ensure the best quality software is released, that it is released on time, and that others can understand it better.
“The Buddy System”
If you’re working on your own, you might find it harder, or nearly impossible, to conduct code reviews. However, you can still conduct code reviews if you find a “buddy.” Consider making an arrangement with a colleague, fellow contractor, or just one of your developer friends in which you agree to review each other’s code at predetermined times.
If you utilize the buddy system, you can still gain the benefits of code reviews while working independently. Just make sure the arrangement is fair. Agree to review each other’s code an equal amount, and try to keep the duration of the code reviews the same amount of time. If you can keep the deal fair, it will be more likely to last longer.
Don’t Argue, Discuss
It’s important not to be overly critical of someone when conducting a code review. You want the conversation to be about the code, not the person who wrote it. Don’t get into arguments over varying opinions. However, the code should drive discussions.
If you’re the person reviewing the code, ask questions: Why did you do it this way? Why did you use this library? What does this function do? These types of questions help drive discussions. Conversely, if you start exclaiming things like, “I would have done it this way,” or simply, “This code is wrong,” then you’re probably not going to have as productive of a review.
Taking notes is the most important part of a code review, and of most productive meetings. Don’t solely rely on the reviewers marking up your code. Document the discussions that occur in the review session. Even if a consensus is reached that a particular chunk of code that was in question is actually good, document it. You may read that note later, and change your mind again. Try to capture as much valuable information as you can during the review. Then, reflect on your notes and decide what needs to be prioritized.
To summarize, I suggest emphasizing the following efforts to help you increase your code quality.
Use a linter when developing. Better yet, integrate a linter into your build pipeline.
Write meaningful comments. Don’t clutter your code with comments, but make sure there are good comments when warranted.
Write legible code. Make sure your code can be read and understood by someone who’s never seen it before.
Emphasize software testing. Start testing your software from the beginning, and don’t back off.
Conduct code reviews. Don’t turn good reviews into arguments. Question, discuss, and take notes.
This is by no means a complete list of all the ways you can improve the quality of your code. However, these are all important steps to take in order to improve the quality of your code.
Before I started doing these things, I didn’t write bad code, in my opinion. But these helped me take it to the next level. I can see this improvement when I reflect on my older projects and compare them to current ones. I hope these tips help you to see the same improvements, regardless of where you’re starting from.