Revisiting Clean Code as a Senior Engineer – Part 1

Thomas / June 4, 2024 in 

BTI360 requires all new engineering hires to attend a six-session course called Clean Code 101, inspired in part by Robert “Uncle Bob” Martin’s book Clean Code. Created and taught by BTI360 engineers, the classes involve writing, testing, reviewing, and refactoring code while discussing Clean Code concepts.

Why is Clean Code so important?

To summarize, the majority of software engineering involves reading code: you reading other people’s code, other people reading your code, and you reading your own code. You want to prepare your code for the next engineer who will need to read it, understand it, and maintain it (or fix it). Following Clean Code concepts—ensuring your code is always readable, understandable, well structured, and well tested—makes software engineering simpler, faster, cheaper, and far more pleasant.

Sure, but for me?

As a senior engineer, I admittedly entered the course with mixed feelings. My grumpy, cynical side wanted to rebel: Why should I have to go to this? I read this book over 10 years ago! Do I not know all of this already? Is there not something more useful I should be doing with my time?

But my cheery, optimistic side was more hopeful: I do believe that mastering Clean Code is vital to being a great software engineer. Surely I can learn something new from this experience. My company requires—and pays—us to review this content, right? So it will certainly be valuable. Look at it as a professional growth opportunity. (I try to let my optimistic side win most of the time.)

Well, I’ve completed the class and suffice it to say, I am extremely grateful to have taken the course.

I was already familiar with the basics of Clean Code, like giving things useful names, removing unused and duplicated code, not writing everything in one long function, and the importance of testing. However, I was able to learn a lot by revisiting these basics and going deeper into more nuanced concepts of Clean Code. Over a decade ago, as a junior engineer, recently thrust into the professional workforce, I did not have the confidence or expertise to fully grasp everything I learned about Clean Code. Reviewing these lessons as an experienced engineer has reintroduced me to the finer points of Clean Code which I can now more effectively appreciate and apply to my daily work.

My Takeaways

Today let me tell you about three of my main takeaways from the course.

1. Naming

I like to joke that naming things is the most difficult part of programming. Yes, it is a clear exaggeration, but it is also funny because there is an element of truth to it.

Well-named variables, functions, and classes are integral to Clean Code: your code cannot be readable or understandable if it was not written using informative names. Actually choosing good names, though, can be difficult or intimidating. I have even used a thesaurus before.

Fortunately, Clean Code gives us a lot of good advice for naming. Here are three recommendations from our course that surprised me:

  • Use code scope to determine a name’s length. Variables with large scopes, like class attributes, should have longer, more descriptive names, while variables with small scopes, like function parameters, can have shorter names—like single-letter variables in functions only a line or two long. Conversely, functions and classes with large scopes should have shorter names, since they need to be easy to reference and remember, while functions and classes with small scopes should have longer, more descriptive names.
  • It is common practice to use predicates for naming boolean variables and functions that return a boolean (e.g. isActive) and nouns for naming non-boolean variables. However, you can also use nouns to name functions that return non-boolean variables (a.k.a. “functions pretending to be variables”). This helps with the readability of your code and allows you to reserve verbs and imperatives for naming other functions.
  • In order to discuss code with your teammates, or even just read code yourself, you need to be able to pronounce it (out loud, or in your head). Pronounceability is a legitimate concern when it comes to naming. Unpronounceable names are often caused by other naming no-nos, like using unfamiliar abbreviations or truncating words. It is worth using a few extra characters in order to make a name more intelligible.

Takeaway: Use the scope to help determine the length for names. Use variable types (or function return types) to help determine the parts of speech for names. Ensure each variable, function, and class is given an understandable, informative name using conventions that are consistent throughout your codebase.

2. Functions

According to Uncle Bob, every function should be no more than 4 to 6 lines long. To those engineers who are not versed in Clean Code, that sounds… completely ridiculous. 4 lines?! What is Uncle Bob thinking? Who does this? Is this even possible?

Well, you might be surprised. I would encourage you to try it for yourself. I did not believe it until I started doing it, and now I have actually gotten pretty good at it. I think you can as well. You do not need to get all the way down to 4 lines (or even 6)—I agree on that length being fairly ambitious—but I bet your functions could stand to be smaller than they are now.

What is so good about having small functions? Long functions are usually trying to do more than one thing, which makes them more confusing. Splitting a long function up into multiple smaller functions makes it more obvious what each part is doing and how they all tie together, especially since (as established in the previous section) you name them well. Plus, having “too many” functions is less of an issue these days since any modern IDE can easily help you follow the call stack when exploring an unfamiliar section of your codebase.

Another sign that a function has gotten too long is when you start using multiple levels of indentation. Got a long loop? Extract the whole loop, or at least the content of the loop, into its own function. What about a long “if” clause? Extract it into its own function that returns a boolean. Have a lambda that has gotten hefty? Then that is not a lambda anymore; give it a name and make it a real function.

(Concerned about passing a lot of variables to all of these new functions? We will discuss the Clean Code solution in our following section.)

Side note: One reason I do not get too concerned about formalizing a specific number of lines is because I appreciate the value of vertical spacing. I dislike long lines of code, especially since I normally have multiple files open in a split screen. Furthermore, I enjoy using brackets, even around single-line blocks (it is much safer this way—trust me). However, your preferences may vary, so figure out what works best for you and your team, and try to stick with it. Once you get good at it, you can even introduce new linting rules to enforce it.

Takeaway: Your functions could likely be smaller. Whenever you create or modify a function, think about whether you should be making a new function instead, especially if that function is doing more than one thing or has multiple levels of indentation.

3. Classes

I never spent much time thinking about when I should create a new class. There are the obvious occasions when I needed to represent a brand new type of entity or define a new web component. Maybe I wanted to reuse some existing functionality in another part of the codebase. Sometimes whatever framework I was using demanded I implement a class hierarchy in a specific way. But often I would not bother creating new classes. I can just continue adding new attributes and functions to my existing classes, even if doing so makes them longer and longer. That is just what you do, right?

Clean Code takes a more aggressive approach to creating new classes, encouraging you to do so more frequently. Classes do not always need to represent an entity or component; classes are simply collections of functions and variables that are related in some way. I personally find this approach liberating. I am not sure why I previously felt restricted to only creating a new class if I had a good reason, but I have now embraced this shift in attitude.

Here are some guidelines that Clean Code provides for when you should create a new class:

  • Uncle Bob says “classes hide in long functions.” Whenever you have a long function, or multiple functions operating on the same variables, that code can likely be extracted into a new class. Some (or all) of the variables from the original code can instead be stored as class attributes. And you should be able to give nice, descriptive names to the new class, its functions, and its variables using Clean Code naming guidelines.
  • If a function has a lot of arguments, extract them into a separate class. Obviously the arguments are all related somehow, since they need to be passed into this function. Plus, if this group of variables needs to be passed into one function, the same group of variables might need to be passed into another function later on, especially if you are good at keeping your functions small. While Uncle Bob says a function should never have more than three arguments, I am personally a bit more lenient and tend to only start extracting variables when I would otherwise need to wrap a line.
  • Long switch statements and if/else statements can be extracted into polymorphic class hierarchies. Create one abstract class and a subclass for each case, and have the subclasses override the same function. Pass any required data into the class constructors or the function itself. This allows you to provide more detail about your clauses by “naming” them via your new class and function names.

Concerned about having to deal with more classes? There are plenty of tools available to help manage them. Modern IDEs let you easily create and organize class files as well as track down the lines at which functions and variables are defined. Libraries like Lombok (Java), Dataclass (Python), TypeScript, and native Java Records give you the ability to write classes using fewer lines of code. And Clean Code keeps our individual classes small and easy to understand.

Takeaway: Look for more opportunities to extract your code into classes. Whenever you have a group of related variables, functions, or function arguments, consider extracting them together into a new class. Likewise, whenever you have a long switch statement or if/else statement, consider extracting it into a class hierarchy representing the different cases.

In my next post, I will discuss my three additional takeaways related to function design and code comments. Stay tuned!


Career Opportunities
Are you looking to join a software company that invests in its teammates and promotes a strong engineering culture? Check out our current Career Opportunities. We’re always looking for like-minded engineers to join the BTI360 team.

Previous

Meet the 2024 Interns

Next

Revisiting Clean Code as a Senior Engineer – Part 2

Close Form

Enjoy our Blog?

Then stay up-to-date with our latest posts delivered right to your inbox.

  • This field is for validation purposes and should be left unchanged.

Or catch us on social media

Stay in Touch

Whether we’re honing our craft, hanging out with our team, or volunteering in the community, we invite you to keep tabs on us.

  • This field is for validation purposes and should be left unchanged.