As a developer, your goal should be to build things that work well and to build them right! Writing good code is simple and complicated at the same time. Even though common rules are easy to follow, it might not always be easy to keep a high level of consistency and quality in your codebase.
You may think “I’m the only developer here, I can do things my way” or “I don’t have time for that!”, however, I can promise you from experience that at some point, you are going to regret not taking the time to write good code.
This article doesn’t cover every possible rule out there, and naturally, everyone has their particular opinion about what good code is. Together, let’s go through a set of simple principles to follow that I want to share with you, ensuring your codebase stays consistent, comprehensive and well organized for the well-being and sanity of your team and yourself.
1. Meaningful names
When you are not the only one actively working on your code, it is crucial naming your variables, functions, classes, files, and anything else correctly.
You should use an easy-to-read notation such as camelCase, CamelCase or snake_case. You should use UPPER_CASE for your constants as it generally is more comfortable to spot them this way.
Specific languages are stricter than others when it comes to notation and naming conventions, so you may not even have a choice in specific cases.
A variable’s name should describe its content as accurately as possible to avoid confusion.
Good — “secondsSinceLastConnection”
Bad — “secs” or “lastConnection”
However, I recommend avoiding using an entire sentence as a variable name as it can make things harder to read.
Moreover, try not to use similar variable names like “rulesThatShouldBeApplied” and “rulesThatShouldNotBeApplied” (think about auto-completion when searching).
A function’s or method’s name should describe what it does. Avoiding ambiguity should be precise. A plus is to keep them consistent, for example: “getUsers,” “getPosts,” “getEvents.” These functions all start with “get” so if my co-workers are trying to “get” something using my codebase, they can start typing “get” and the auto-completion tool returns a list that includes what they’re looking for there. With this method, there is no need to ask or research extensively, and in consequence, it saves precious development time for everyone.
“Use the full potential of the tools at your disposal.”
2. Comment your code
Adding comments to your code is solid practice and is majorly used to define what its purpose is or how it works. It is sometimes used to leave reminders or TODOs for yourself or the rest of the team.
However, there’s more! Comments are also used to feed autocompletion tools with information such as the parameters (+ overloads) of a function (their types, restrictions, default values) and what is returned by that function, you can also give more information about the structure of a class or write examples or compelling use cases because sometimes a piece of code is worth a thousand words.
I encourage you to have a look at what your favorite IDE can do for you when it comes to comments and decorators.
3. Good readability
It goes along with having a good naming convention. Maintaining good code readability is extremely important as it helps everyone, including yourself.
Readable code is well spaced and indented (spaces or tabs, pick your favorite flavor). Let your code breathe a little by adding additional lines between blocks as it often clarifies the context.
Nobody fancies when a line of code is so long that your IDE displays it on two lines or forces you to scroll horizontally to read it all. At this point, you could create more variables and add other in-between steps, or just split your code across multiple lines.
It is the same thing for extended blocks of code; I advise trying to split your logic into multiple functions that come together to create the final output. Let’s not create one lengthened function called “buildChair,” instead “buildChair” is a small function that compiles the results coming from “getWood,” “cutParts,” and “assembleParts.” At this point the process is transparent, and because I name my functions correctly, I don’t have to write an elongated description of what is what.
If you don’t have a choice and a particular block of code is still super beefy, break down the code a little by adding comments and new lines.
4. Reusability and extensibility
“Don’t just code, code smart!”
To illustrate this part, let’s reuse our previous example. My fictional company makes chairs, but I named the functions “getWood,” “cutParts,” and “assembleParts.” If tomorrow I start building tables I am going to be able the same functions without changing the structure of my code, my process, or my logic. In fact, I’ll write a new function called “buildTable” that use the same three functions with different parameters.
The final output is not what matters the most and should not be your primary focus. It is vital to define a neat structure and to establish a process before writing your first line of code. It helps you identify what is reusable, what needs to be extensible, and what is meant to change in potential or hypothetical future builds.
It is always imperative to plan well and to be ready for possible changes, and this is why writing small, versatile blocks or modules saves you much time and avoids headaches.
5. Write bulletproof code
The secret about this part is that if you applied the 4 tips above, you are almost set and it is pretty simple.
The first step is to make sure that your logic is sound, and that it works in a standard case scenario with the appropriate input.
The second step is to verify the input. It needs to correspond to what is required, and that is why commenting your code and using auto-completion tools is imperative. However, in an instance where the expected input is not there, you have to put all the necessary checks to catch every exception. Besides, you have to handle these exceptions by throwing an exact error that helps the members of your team and you during the debugging phase. It is also beneficial to log your errors to keep a track record of what went wrong, and I also recommend logging the input that caused the issue so that you can reuse it in future Unit tests.
“Be proactive, don’t let your code break.”
The final step is always to have the expected output. It is not simple to explain but let’s use an example to illustrate: given that the stock of an item cannot be lower than 0, a function “getNewStock” gets the current stock of an item (parameter 1) and subtracts some items (parameter 2). My two parameters could be correct, but if the stock of the item that I’m trying to get is 2 and the number of items that I’m trying to subtract is 4, I should not return -2 but deliver an error instead.
If your codebase is composed of small blocks of code as I mentioned previously, it is easy for you to check your inputs and outputs at every step of the way. It is also straightforward to write unit testing for smaller blocks of code. In the end, it is a lot simpler to debug a well-organized and defined codebase as the traceback is a lot more detailed and complete.
That’s it! 5 simple tips that can save you a bunch of time, make your life easier, and significantly increase the quality of your code. In addition to that, it benefits your team as well, and they can reuse your code or take it apart to use parts of it in their projects.
If you’re interested in joining an open-source project, it is necessary to know about these tips and to apply them. Otherwise, no matter how useful your contribution is, it might be disregarded or overlooked.
I hope this article is useful and that you liked it. If you have any questions or if you want to add anything to what I explained, feel free to post a comment. It is always my pleasure to answer!