Clean Code Tips: Handle Names, Functions and Classes

Vinod Madubashana
unibench
Published in
10 min readFeb 5, 2024
Photo by Bruno Martins on Unsplash

It is very easy to write a program that is understood by computers, but it is not easy to write a program that is easy to understand by other developers. Most codes are written once and read many times, so it is very important to write them as cleanly as possible. It might benefit others and also for future you that reading your code.

Remember there are no hard rules in software development, so no point in arguing with others to prove that your opening is the best. If there is only one best to do anything why do people implement the same thing in many ways? that’s because everything has tradeoffs you only can decide what you can tolerate and what not depending on your context. With that in mind let’s start the discussion on names.

There is a famous saying by Phil Karlton “There are only two hard things in Computer Science: cache invalidation and naming things.”. I’m sure that Every developer has been in a situation where they struggled to name something in their code. why? because there is no right name, it all depends on personal preferences, context, and many other factors. But we don’t need to write code to understand by whole world. We need some conventions that the working team gets comfortable with and easy to onboard a new member to the team. There is a famous article written by Tim Ottinger called Ottinger’s Rules for Variable and Class Naming which is the base of many resources you will find on naming best practices. People describe it in many ways by adding their own opinions and experiences

Remember the reason we need clean maintainable code is that there is no hard end of development, requirements get added, and existing ones change due to many uncontrollable reasons. Good naming is not enough to have maintainable clean code, the structure is also very important. Here I will discuss some principles that you can use while naming and structuring code.

Please don’t lie.

This is the most important message I want to convey in this article. All other tips will help you to achieve this goal. One of the major benefits of writing clean code is when there is a change you should be able to do without reading lots of unrelated code pieces to understand the whole application at once. Think of a simple example, you are calling a simple getter to get a property value and then you realize there a database update operation is hidden inside that getter. Then you will never call any function without reading it. The trust is broken. There is no value in having clean code after this trust is broken.
Good names and structure help you to navigate to the place you need to make the change without reading a hell lot of code. It’s fine to have long names. Comments are not the solution, developers do not read comments but they will have to read the code. Why do we need comments when we are writing code also using plain English? Comments can be useful to say why you do something in that way, not what code does. The code should speak for itself. Does not matter how well you craft the code if it’s not telling what it does.

Most times lies are in function names. Especially when a side effect is executed when you run a function name that says it gives you something. The principle Command Query Separation which is also known as CQS can be used to write clean functions. Queries return some value while not changing the observable state of the system and commands make changes to the system state and do not return anything. If you can separate those two very good but reality is not that nice. So in such cases mention it in the function name. There is no better documentation than code which is the actual implementation. One obvious benefit of CQS by looking at the signature you can say whether it has side effects to not. One tip to convert queries that return something is to pass a callback function that deals with that return value which makes the query return nothing.

Spend some time when you name something which will save you a lot of time in the future.

Developers are very excited about new technology trends frameworks and programming languages and like to spend many hours learning them. But when they use them they are very rushed. Nobody is interested in investing time to revisit your variable and function names. If you want to do this fast you have to practice it, spend some time at the beginning and surely it will become natural to you later.

The best way is to communicate your intent. Use Domain-specific words that are used in normal communication. It will reduce the gap between your normal conversation and reading a code. Don’t try to be oversmart by shortening names into nice acronyms. Over time these names become very confusing. Using long names is not a bad thing.

Long functions might have a hidden class

The main reason functions get long is they are reusing function-scoped variables throughout the function. That also means the function is doing many interconnected things. Isn’t that what do use classes in object-oriented languages? Even procedural languages have mechanisms to create scoped modules. So next time when you see a long function try to identify hidden classes inside it and do the extraction.

The relationship between the length of name and execution scope

For variables use long descriptive names when the execution scope is large. For example, class-level variables need to have full names. If you use single-letter variables in the functions you might not understand what it is and you will have to go back to the declaration to identify. If you need to go to the declaration to understand what a variable is that name is bad. But for finer scopes, it is ok to use short names. For example, it is very common to use i, and j for variables in loops. since their scope is very small you don’t need to go anywhere to figure out what they are. Even for objects it is fine to use these small variable names because their usage is within the next couple of lines. So names do not need to be very descriptive to understand what they do if their scope is very finer. This is one reason you should make your functions very small, maybe a maximum of 4 or 5 lines.

This applies oppositely to function names. Think about the entry point function that you call in any object to do something. If you try to give a name that does everything by invoking it the name will be pretty long right, but when you split your function into more scope you can give each of these functions more finer names. Each function might split further which might means way more longer names. One good practice is when you arrange a function don’t mix both high-level function calls and primitive data manipulations and arrange them within a class by adding the most important things at the top and more detailed things down which is described as the step-down rule by Robert C. Martin in his book Clean Code. Try to bring these primitive operations at the end of your function calls. This will also make your functions very small and clean

Try to tell a story from your code.

I hope nobody writes programs in ones and zeros or assembly languages anymore. Modern programming languages use English words. So think about what makes code different from a normally written story. It is mostly because of curly braces, semi-colons, and indentations. The more you can reduce those usage your code will be more natural. This is another reason that your functions need to be very small which means you can reduce indentation and nested structures like for and if. Always extract the logic of for loops and if blocks to separate function which has a proper name. When naming some simple guidance like the one below can be used to build a natural story from code

  • Variables — Noun
  • Functions — Verb
  • Enums — Adjectives
  • Booleans — Predicate names

Another thing I suggest is to stop using encodings like Hungarian notation which adds prefix letters to indicate its type. A famous example is using start interface names with I. Even though you are very concerned about knowing the type modern IDEs are very powerful that you can see the type by just hovering over. I know this is a debating point, but my thinking is it clutters the code. If your team thinks it is good and comfortable with it then it is perfectly fine to use them. But be consistent as a team.

Function arguments

Try to keep function arguments under three, which means you don’t need to worry about their order. If it requires more arguments try to group them into objects which has the advantage of having names. This concept can be applied to object constructors too by using setter functions. I know that many best practices suggest you make this object creation process atomic to make it a consistent and valid object. That’s why the builder pattern is also very popular. But you can achieve the same goal by having a good set of automated tests which has more benefits.

Avoid passing boolean arguments which means your function surely doing two things. Separate them into two small functions. Passing null values to a function is also similar to this, you are doing two things based on null or not. But you can use the Null Object pattern to handle both cases within a single function. Null objects are objects that implement the same type and the methods will act neutrally to method invocations, carefully design this neutral behavior in null object functions logic if you need some logic in it.

Modify objects passing to function is bad, if you do so make it obvious by adding it in the return of the function. But the better way get the required modification attributes separately and do the update in a single place. Now you don’t need to go across many places to find how an object is modified.

Prefer polymorphism over switch statements

In object-oriented languages, you can use base classes instead of using actual implementations using switch statements. This reduces the source code coupling and can centralize dependency management. Strategy pattern might help you to do the refactoring

Temporal coupling

Temporal coupling is the coupling of your function invocation order. For example, you can’t close a resource if you haven’t opened it. That example is cleaner, but the reality lots of innocent-looking function calls assume something happens before it is called which leads to bugs.

Of course, we can’t avoid this, but we can impose the order by carefully designing the public interface of an object. For example, we can write an open method that accepts a command handler object where you can open the file, pass the open object to the handler, and then close the file. Another way is to create fluent functions that do something and return an intermediate type which you can use to create domain-specific language and create a chain of calls to execute the full flow. If you want to check a detailed example in Java check this article

Tell Don’t Ask principle

It is very common to get some state from an object and execute some operation on the same object. Tell don’t ask pattern simply says instead of asking the state and then performing something, just tell the object to do its work. This helps to encapsulate the internal state. Let the object take decisions based on its state.

The law of Demeter

This law suggests stopping sharing the knowledge of the internal data structure of any object. Simple means avoid calling something like object.getFirst().getSecond().getThird().execute() instead of call object.execute() which delegates to getFirst() which is internal to the object. The main argument of this principle is if you need to change internal structure now you have to change many places. Some developers don’t like this and have some valid points, check this article titled “You Should Break the Law of Demeter”.

Error handling

Error handling becomes harder when you think about how to do it after you implement a feature. So start your code with error handling which stops catching the same exception multiple times and unnecessary exception conventions. As a practice, I am not writing the logic in a function with a try-catch block. I separate it into a new function. The try-catch blocks obscure our code. Domain specif exception throwing helps to handle these exceptions in the calling site easily. So extend the exceptions provided by your language and create custom meaningful exceptions accordingly.

Conclusion

This is a broad topic that can have endless principles and perspectives. But the most important thing I want to highlight is team conventions. Everybody might not have the same flavor when writing and it’s perfectly fine when everybody is comfortable with reading the code. Even the same principles different people implement in different ways. Writing clean code is an art. Don’t expect that you will be writing code after reading hundreds of books on this subject. You have to practice. It will take time, but with time you will get better each day. So start with simple practices and improve them if you have a new perspective discuss it with your team, and share it with us.

Resources

https://www.tedinski.com/2018/12/18/the-law-of-demeter.html#:~:text=The%20%E2%80%9CLaw%20of%20Demeter%E2%80%9D%20suggests,and%20deeper%20into%20indirect%20dependencies.

--

--

Vinod Madubashana
unibench

Full-stack Developer, Java, Spring Boot, React, Angular, AWS ...