Below simple list of principles and practices can help you write cleaner methods in your program:
- Methods should be as small as possible and should have descriptive names that explain its purpose without the need of any extra comments. Bigger methods should be refactored to move contents that can be grouped together, into other methods with descriptive names that reflect the task done by that method and then invoke those methods from the original method.
- A good descriptive name may even explain the order and intent of the arguments in addition to the purpose. For instance, assertEquals(expected, actual) may be written for more clarity on the order of arguments as assertExpectedEqualsActual(expected, actual).
- Statements within a function should be all at the same level of abstraction. This will help us to read one level of abstraction at a time, which will be very helpful in understanding the overall program structure. Mixing abstractions may also confuse readers. We can try to control the indent level of a method to be not greater than one or two. Intended contents should be moved into other methods with descriptive names that reflect the task done by those contents and then invoke those methods from the original method.
- Single responsibility principle should be followed. A method should be doing only one thing and we should not pack a lot of things which could be separated into separate methods, in a single method.
- Don’t repeat yourself (DRY) – Avoid duplicating code. Move it into a separate function or class.
- Switch statements cannot be asked to do one thing as they are designed to do multiple things based on input. However, we can move them to a separate class and make sure they are not repeated. One way is to move switch statements into an Abstract Factory, and never let others access it directly than through the Abstract Factory’s exposed APIs
- Number of arguments should be kept to minimum, ideally zero and preferably less than three. The more the number of arguments, you will need to write more unite test cases to test all their combinations. The book clean code suggests that the ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) required very special justification and then shouldn’t be used anyway.
- If a method need to transform an input argument, the transformed value should be a return value; you should not modify the input argument itself making it an input output argument. An output argument forces you to look into the method signature or documentation to understand it fully, which is an extra task.
- Function that accept a Boolean does two things based on the value of the Boolean; hence it should be split into two methods with describing names and no arguments.
- A method that accept two arguments are ok in some cases as in point(x,y), whereas in other cases it may create confusion as in the case of assertEquals(expected, actual) where you may pass in actual and expected in different orders unknowingly. If possible, we should try to refactor two argument methods to use single argument. For instance if you have a method equals(obj1, obj2), you could make the method as part of obj1 and call it as obj1.equals(obj2), which is exactly how Java’s Object class’s equals method is.
- If we need to pass more than two or three variables, we should try to wrap some of them into a class of their own, and then pass that class instead. In addition to reducing the number of arguments, this also groups some arguments and gives them a descriptive group name.
- If there are more identical arguments, then we can try to pass them as a list or even a Varargs in Java, as it gives better clarity and readability.
- Methods should not have any side effects. For instance, a method supposed to do one thing as reflecting in the method name may do some changes to some other variables too. You should at least rename the method name to reflect the additional change, even though it violates the single responsibility principle as the method now does two things.
- Methods that try to change the state of an object and also answer a query at the same time may lead to confusion. One quick solution may be to change the method name to reflect this multiple tasks, but a better solution would be to separate these to two separate methods.
- Exceptions should be preferred over error codes as returning error codes will force you to have an “if” statement to check for error code, which may be confusing to some readers. Exceptions can be taken care using a try-catch block in Java. If you use error codes, you will also need to have some class that defines all error codes and all classes that use those error codes will be dependent on this class. Adding new error code will require all these classes to be recompiled and hence people might try to reuse existing error codes even though the required error might not be exactly same.
- In a try catch block, it is preferred to move the contents of try and catch to separate methods as it will separate error processing with normal processing into separate methods giving better clarity.
- When using try-catch for error handling, there should not be any other code before or after the try-catch in a method, as error handling is one thing and hence should reside in its own method to follow single responsibility principle.
TAGS: Best Practices
This note is based on the book “Clean Code” by “Robert C. Martin”. The chapter ‘Functions’ explains all these tips in detail along with examples.