Being a developer, whether we are starting our carrier or in the industry for years, we always try to enhance our skills. We try to learn new tools, languages, and technologies to add to our skill set. Besides the technology stacks that we know and how much we are proficient, the other thing that really matters for becoming a better developer is how we write our codes. Everything we know, the problem and solutions, are expressed in terms of the code that we write. The code we write really matters because we need to continuously add or modify the feature and debug it, which requires clean and understandable code. Writing easily readable, understandable, and maintainable programs requires some guidance, experience, and enthusiasm. If we want to be better, then we need to look for ways to improve our writing skills. Writing good programs is a continuous process. We may have heard of self-descriptive or self-documented code which leads towards writing better programs. So, how can we write good programs? The answer is to follow the guidelines of clean code.
The process of writing good programs that are easily readable and understandable and that facilitate ease in maintenance is what we call writing clean Code. This article includes some of the areas that are helpful for writing good code as mentioned by Robert C. Martin, also known as Uncle Bob, in his book Clean Code: A Handbook of Agile Software Craftsmanship. Writing clean code means considering different dimensions of the program such as naming conventions, comments, error handling, functions and objects, and data structures. Things that we need to consider on these dimensions that guide us towards writing better code are as follows.
A good name provides lots of information and adds clarity of the program. Class, variable, and functions are few to mention where developers have to choose meaningful names to reveal the intended purpose of code. Considering the following points helps to make meaningful names.
Use Intention-Revealing Names
Giving names to classes, member variables, functions, and local variables that reveal their usage improves increases in readability, understandability, and maintainability.
Make Meaningful Distinctions and Avoid Disinformation
Developers have to write often similar objects within the same scope or type of variables. In such a situation, choosing a good name is important to provide clear distinctions. For example, instead of using obj1 and obj2, proper naming is preferred that describes their usage within the scope.
Use Pronounceable Names
How do you declare variables of any kind? It can be of objects, primitive types, etc. Are you able to remember the name of the classes that you defined in your program? In a small program or project, it is easy; but as the program goes large, it is hard to remember all the classes, variables, and functions that we define. Using pronounceable names helps increase readability and understandability, and makes it easy to remember within the scope of their usage scenario.
Use Searchable Names
Using searchable names means avoiding generic names such as file1, file2, var1, etc. These terms are hard to search because they can be used anywhere and they do not provide their scope of use, which helps in locating them. Searching has become easy with advanced IDE, but it still consumes time and relatively produces large results. If we follow the points listed above, then we easily achieve searchable names. Using searchable names helps you to easily locate and your favorite IDE also produce less search results which have higher accuracy and consumes less time.
We often write intSum or strName to tell what the type of the variable is. Adding type encoding before the variable name adds no information and reduces its readability.
Don't Be Cute
Avoid using fancy names; no one is interested in this stuff.
Comments in code play a vital role in understanding the code. We are often taught to place comments on programs and write comments for each statement. The question lies in whether the comment is necessary everywhere. The answer is no. This section gives some ideas bout writing comments.
If there are any legal concerns such as copyrights, then add a comment.
If there is any information that you think it is definitely valuable for others, then write it (for example, the algorithm being implemented).
Explanation of Intent and Clarification
There are some cases when we do certain things that should not really be done in that way, but the use case requires it. In such case, placing comments to explain the intent is good. This will help others understand why certain things are done in such a way.
Warning of Consequences
We write functions that do certain things. There are cases where any interference with the code or usage in any other scenarios than specified may introduce abnormal behavior. In such case, placing comments that describe the consequences is helpful.
We write functions that have their own responsibilities, and together, they solve certain problems. For functions, questions such as "how long does it should be?" and "is it appropriate to use a single function to provide different functionalities?" are very crucial to all developers. Very long detail or many responsibilities for a single function increase complexity and hard to debug and maintain. Therefore, a function should have following properties.
A function should be as small as possible to facilitate ease in debugging, understanding, and modification. Some define function in terms of the number of lines and some think that a function detail should be completely visible on a screen. It depends on how much a developer wants. Therefore, a function should be relatively small as possible so that other can easily read, debug and modify.
Do One Thing and Have No Side Effects
If we want our function to be more maintainable and easily understandable, then it should be driven by single responsibility, which means that a function should perform only one thing. Single responsibility also means reducing side effects. If a function does the intended purpose as well as another, then it is not good. Such functions can show side effects at any time. For example, for an e-commerce application, using a login function to authenticate and initialize shopping cart is a bad idea. It can introduce unwanted behavior at any time. It is better to use two functions for the authentication and initialization of a shopping cart. Therefore, a function should be responsible for only one thing.
One Level of Abstraction
A function reveals its usage by its name and hides all the details. A function should hide the details that it is responsible for, which means that it should provide only a level of abstraction. In our previous e-commerce example, the login functions provide two level of abstraction: one for authentication and another for initialization of shopping cart.
No software is 100% error-free. Therefore, error handling is a very crucial part in developing applications. We may or may not know when and where errors can occur in development time, but we need to be prepared for errors while developing applications. Errors should be handled properly and with care; otherwise, they can mess up a lot. For better error handling and to be on the safe side, the following guidelines are helpful.
Prefer Exceptions Instead of Error Code
Instead of returning error code such as 0, -1, ERR for error conditions, etc., it is better to use default or custom exceptions. Since exceptions have names, it is more appropriate and helpful in understanding error being occurred (for example, if we encounter a "file not found" error and we return 0 instead of FileNotFoundException). In such case, the return code is more verbose and difficult to understand. In such case, if we use Exception, then it more understandable and easy to identify the type of error being occurred.
Provide Context With Exception
Exceptions should be handled with care. This provides flexibility as well as increases complexity. Therefore, whenever an exception occurs, it is better to provide context for it. Exceptions provide lots of information for debugging and providing the context makes it more meaningful. For example, we are querying a record from a table in a database and we need to throw an exception on "record not found." In such case, throwing only an exception does not provide any meaningful exception being occurred. If we convey a "record not found" message with the exception, then it is more meaningful. It is also necessary because we can use the same type of exceptions in many cases. Making such Exception distinguishable from one another requires context for each occurrence.
Do Not Return and Pass NULL Values
Passing and returning NULL values causes a lot of problems. It is also uncertain when this occurs. Any function that accepts and return NULL values are in abnormal states. To prevent any uncertainty, always validate the parameters before calling a function and always return value if it does. If a value does not exist to return, then throw an exception with context but do not return NULL.
I hope that the above guidelines will help you write better code. Since it is a continuous process, we need to consider these guidelines on a daily basis to improve what we write and how we write. If you want to have more and clear idea about writing clean code then, read Clean Code: A Handbook of Agile Software Craftsmanship, written by Uncle Bob.