Clean Code is under rated. If you've ever got frustrated with the way some code was written or spent way too much trying to understand what some function is doing, it probably means that code could be written better.
You can surely build great things without following the principles of clean code. But it becomes a LOT LOT easier for you and your team to maintain the same code if it is easily understandable and written well. This is a continuous process that should be taken into account with each line of code you write and is not a retrospective action you work on at the end of the week/sprint.
Java projects especially can get really big and complex, and clean code helps your team build, enhance and fix bugs faster and more efficiently. And if you're a beginner, this can be a lot to take in but it surely helps to get started off on the right foot.
In the below sections, I'll go over points/best practices to keep in mind while writing java code.
Thumb rule - always be as specific as possible with all names.
Class Names should ideally be nouns or can be abstract and are written in PascalCase. Example: AudioTransmitter, SalaryCalculator.
As much as possible try and make it specific to what your use case is. If you have a FileReader class to read xml files, a better way of naming it would be XmlFileReader. That way if there is an enhancement later to have a JSON or CSV FileReader, you can have separate classes for it instead of having all the logic in one class.
- Written in camelCase, variable names should be descriptive yet concise.
- Avoid naming variables with alphabets such as a,b,x,y,z unless it is a temp variable like an index. *When using booleans start it with 'is'. Example: isValid instead of valid.
- For Constants use ALL_CAPS_WITH_UNDERSCORES. Example: MAX_CUSTOMER_COUNT Basically, if we see the variable in a random piece of code, we should be able to understand what type of data it holds. Example: customerAccountDetails
- The name of the method should describe 'what' is done by the method (it doesn't matter how). eg: generateSalaryReport(). The method tells us it is creating the salary report. We don't care how it is being done internally.
- A method should ideally have only 1 purpose. If the method does more than 1 thing, you should ideally split it up such that each method does exactly what it says.
- Avoid writing huge methods. Each method should be specific and do just one thing. Keep in mind if multiple methods have the same code in them, you are repeating yourself. Instead create a small private method in the class and call the method wherever the duplication is observed.
- When returning an object, avoid returning null. When we do so we are assuming that all clients of the method expect null and handle it. If they are aware they need to add an extra try catch every single time your method is called. This is just repetitive code. Instead return an empty Collection or throw an error if appropriate.
- Avoid having too many arguments in a method. Too many arguments also increase complexity and might show that you need to split your method into multiple parts each doing a small part of the logic. Or are a few of those arguments very cohesive and do they actually need a class of their own? If yes, it might mean more code and an extra class but it's a lot easier and more scale-able.
- Avoid flag arguments which are boolean in nature. This points to the fact that you have too much logic for the function again. Split the true and false part into two separate methods and call them accordingly.
- Fail Fast & Return Early. Check for invalid arguments right at the start and throw an error instead of executing code that will ultimately fail for the same reason. This will save on execution time and make it easier to debug. Return early is the way of writing methods so that the expected positive result is returned at the end of the function and the rest of the code terminates the execution (by returning or throwing an exception) when conditions are not met.
- If you have a Class that has many variables that need to be assigned and multiple combinations of these variables therefore needing many constructors, a good way to organize your code is to use the Builder Design Pattern. This is a Creational design pattern and you dynamically assign variables when creating the object.
- Another way to avoid duplication of code in constructors is using Constructor chaining, where you call one constructor from inside the other.
- Try and follow the Single Responsibility Principle (from SOLID principles) - the class should have only one reason to change. This results in smaller well defined classes.
- It should be cohesive - where fields and methods are highly related to each other. Higher the cohesion, the better. This will also help you avoid classes with way too many variables.
- Follow Loose coupling - a change in one class requires minimum/0 changes in another class. This reduces the inter dependency between classes. Most frameworks follow loose coupling for this reason.
- Use Dependency Injection if possible.
- Uses interfaces as much as possible. If you ever want to change the implementation, it becomes a very quick and easy change. Allows you to be very flexible in the future. Example use
List<String> customerNames = ArrayList<String>();instead of
ArrayList<String> customerNames = ArrayList<String>();
- Encapsulation - keep variables private and any methods that need not be public, private. Even for getters and setters, only create them if it needs to be accessed from outside the class.
- Avoid catching Throwable or Exception Classes. As the programmer, you need to know when Errors(Example: OutOfMemory) happen or when Runtime Exceptions that you would want to know when it happens.
- Do not catch and handle Null pointer exceptions in the catch block, instead write code to prevent the null from happening itself(if possible) and if not just do a
if(obj != null).
- Exceptions when caught should be specific. Only if you expect an error to happen, you should try and catch it. Also Java 7 allows you to catch multiple exceptions in one block with
catch( IOException | SQLException e)and handle all exceptions the same way.
- When catching an exception, make sure you log the details(with a logging framework) of the exception and surely do not keep it empty.
- When catching an exception make sure you mention why the exception happened. Example if it is an Illegal Argument Exception, you can mention what type of value the argument had for the exception to happen. This helps with debugging.
- Use try with resources(if using java 7+) wherever possible to avoid additional code.
- Do not catch and handle exceptions you do not expect to occur. You should add catch blocks only for those exceptions that you are aware could happen for a reason. Anything else is extra avoidable code.
Base rule: Never comment code out.
- A comment should only increase our understanding of code.
- Avoid stating obvious points in comments that you can easily understand by reading the code.
- Keep note that that comments are rarely updated when code is updated. This can cause issues. As a team, always make sure any updates to code, is also reflected on comments or remove the comment altogether in such cases.
- Writing comments in the form of JavaDocs for public methods that will be used by clients of that class is extremely valuable.
Most Java IDEs have an inbuilt code formatter which helps you do this with a shortcut.
The Google Java Style Guide and Java Style Guide by Twitter, both have some best practices when it comes to writing java code.
Make sure you use line breaks and brackets wisely, making the code readable. Less lines of code doesn't make it more readable.
There are plugins that help you check if code is being reused, written as well as they can be. SonarLint their very tag line is - Fix issues before they exist. It is available on all popular Java IDEs and you can do a full project scan to see how the code can be improved.
SonarQube is an open source tool that can be added to your CI/CD pipeline and is extremely popular to check the entire project's code quality.
At the end of the day, as a team, decide on what standards you would like to adhere to and stick to it. If each member follows his own best practices, it helps no one. It often helps to keep a document for each project with the coding standards followed by all and is shared with any new members joining the team.
Make sure when you are reviewing code with Pull Requests, your teammates are adhering to the rules agreed upon (and they make sure you do as well) so your overall code is consistent throughout the application.
If you have any additional pointers to add that you feel have helped you and your team out, leave them in the comments down below.