Your Team Deserves Clean Codes

Jonathanjojo
9 min readApr 6, 2020

--

it’s not gonna make the code nor the computer works any better, it’s gonna make the developers work better

image: https://medium.com/strands-tech-corner/make-uncle-bob-be-proud-of-your-code-f092b2900bfa

This article is written as a part of individual review criterion of PPL CS UI 2020

A poorly-made car would still take you from point A to B, but a well-made car would be more likely to be able to take you (and your friends) from A to Z over and over.

Overview

If you are a programmer, the term “clean code” will sound familiar to you. The name of “clean code” itself is quite self-explanatory: a non-dirty, non-messy code. A lot of people highly regard clean code as a top-notch quality standard for code. Although I may want to ease the definition of “clean code”.

I personally regard a code as clean if it is:

  • Obviously readable
  • Works and proven to be working (tested)
  • Efficiently written and isn’t a subject to many harmful changes
  • Easy and cheap to maintain and needs the minimum amount of maintenance

Today we are going to see why clean code is important in collaborative work. It is not for you, nor for the computer, it is for your team.

Motivations

The computer isn’t the only one to read your code

You may be wondering, clean or unclean, they both works. Why the bother? Heck, computers can read the code no matter how incomprehensible it is to the human eyes.

Take a look at this code (it is a bit to the extreme, but you will get my point)

def min(x):
x.n = x.n - 1
x.save()
for a in b:
for b in c:
min(b)

The writer would probably say “it’s simple enough, just a 2-level nested loop and a utility function. My teammates are top university graduates, they would understand this, no worries”. The other developers are currently plotting to fire this guy. Now take a look at this one:

def reduce_stock_count(item):
item.stock_count -= 1
item.save()
for component in components:
for part in component:
reduce_stock_count(part)

It is the same thing, but with clearer naming (suiting the business context lied behind it). The computer sees them both as equally the same.

source: knowyourmeme.com

But now, not only the computer can read it, the other developers too! Now they can continue the work with less to no misunderstanding. Look at the code this way, codes are art performed to tell the computer what to do AND to tell others what you tell the computer to do.

Code is the window to the mind

The code reflects how you think. Better code means a better train of thought. One of the most sophisticated computers is arguably the human mind. It is only logical to mimic the flow you have in mind with your code. This way, the code would reflect your mind and how you see the problem.

When you read your own mind through your code. source: vlipsy.com

Alas, your developers are also human, it is probably easier for them to understand human thoughts.

Destroying the bugs hideout

Clean code also means transparency. Clean code should be clear as day, and you shouldn’t need many readings to understand what it does. The clearer it becomes, the easier for you to find bugs or unwanted flow within your code.

Generally, it does not minimize the presence of bugs within the code since it is handled by the logic. But, it provides a better view to discover and eliminate the bugs.

Efficiency

Efficiency means: “for the least amount of work, it gives out the maximum results”. In terms of coding, this translates to reusability (and performance, but that is not in our context).

Your code should be structured enough so that any modification would need as little change as possible to the other parts it is connected to. There are many ways to achieve this considering the many situations and conditions you may have. Just google Design Patterns, as it is the general approach to many forms of common problems.

Why? Well, you will never know if your team needs to reuse some of your work or edit it out. If your code is highly intertwined (high-coupling), they will have a hard time making changes as they need to tweak much from your (messy) work.

It works and you should know it!

One of the utmost sign of clean code is its testability and the fact that it is tested and it passes the test. One trait of clean code is the modularity, and modularity means higher testability.

It is useless if your code is clean, tested, but it does not pass the test. So make sure it works as planned. This will further increase the failure rate of your team’s overall work if the team uses the code they assume to be working (when in fact, it does not).

source: pinterest.com

How we should do it (we actually do it, trust me)

Over the years, developers have come up with some ways to fight with code uncleanliness — better known as Code Smells. Alas, some tricks are used more than others and got documented for others to utilize. Later on, some become part of the essence of refactoring.

They are all based on certain principles, so by understanding these principles alone, you will go far:

SOLID

SOLID covers 5 sub-principles — mostly related to object-oriented programming along with GRASP. If you are doing that, please refer to SOLID (you can check it out here). SOLID will give you better modularity and reusability for your objects and classes, furthermore, it will improve your system’s adaptability to changes.

Open Close Principle Implementation

Above is one example utilizing the Open-Close Principle (O in SOLID) by creating a base class that can be extended with ease. This way, the BasePermission (by Django) and the IsAuthenticated by us will not be easily modified, but easily extended.

KISS

Keep it simple and stupid. If a problem can be solved in a simple manner, why bother to take it on with a more complex solution? This rests the case.

Our code is simple and stupid enough. No weird flows or algorithms here. Just don’t overdo it, for example:

The Log model can be attached to revisions from 5 other models. Rather than storing a foreign key for each model, just store their id and the model name. We can later then map it using a simple dictionary. Voila.

DRY

Don’t repeat yourself. If you can reuse something, use it, and use it well. It might sound simple, but it is tricky to create something that is reusable yet modular… The trick is not to make a silver bullet for everything, but to try to create sub-codes to help you construct the code as a whole.

This factory will create a model object over and over for me. No need to rewrite all those fields by my self. This is one implementation of DRY. More to it, you can also apply DRY by making extendable classes (like in the permissions and models at the 2 snippets above)

Composition over Inheritance

Instead of describing what an object is, describe what it has. This gives you more flexibility than the more strict inheritance. This principle is held and chosen by most developers.

This is one perfect example. Notice that our Account model is not extending the User, but it is compounded by a User object.

user = models.OneToOneField(User, on_delete=models.CASCADE)

This way, any modification of behavior in the Account model wouldn’t disrupt the original User implementation. For behaviors that refer to the User behavior, simply call the account.user.method().

Consistent in naming and structure

There are a lot of conventions, but the main value is to write it as close to the human language (follow the case commonly used by your programming language). For example:

  • Variables use nouns (books, item, cart, etc) or adjectives for boolean statuses (is_admin, is_approved). If it's plural in nature like arrays, use the plural form.
  • Methods use verbs (check_out_items, subscribe, get_total)
  • Classes use singular nouns and capitalized (Book, Item)

For flows, you should aim it as close as possible to the human-readable flow. And be consistent! If you use X to tackle one type Y problem, use X for any type Y other problems.

The same goes for data structure, define the real-world object within your code. For example, if a book were to have a title and author, then your object should represent a book with the attribute of title and author. Not the other way around, or any other way that the connections between them is not obviously noticeable.

We follow the above naming convention, and we even have our own convention regarding Serializers. We use the term “Summary” for serializers that are purely intended to display thorough (sometimes nested) information.

Favor Readability (yes, even over performance in some cases)

Code is like a joke: if you have to explain it, it is bad. One thing that I myself hold on, is to see the code like a story. Variables are the actors, and the logic defines the plot. So, name them descriptively and easy to read.

Most of the time, it is better to write code that is easily readable, even if you have to sacrifice a little on the performance side. The considerations are laying around the fact that if it is readable, it is more likely to be easier to fixed and improved later on.

This does not apply to all cases, sometimes your code is intended to improve performance, e.g. DB rows fetcher, etc.

The Log model can be attached to revisions from 5 other models. Rather than storing a foreign key for each model, just store their id and the model name. This is way easier for our team to understand and work with than by using foreign keys to 5 different models. The workaround is by getting the Model object by filtering them, this is surely slower than getting the related object by ForeignKey, but it is easier to maintain.

Simple and structured information passing

Most information passing is done by parameters for intrasystem passing and requests/messages for intersystem passing. While for intersystem passing, the structure of the passed information is defined, we shall not cover it in this blog.

The intrasystem passing is important for developers who read/plan to continue your work. Never use too many parameters. You can either arrange the functions to sub-functions, use factory patterns, or parameter objects: objects that have structured, type-defined, and constrained attributes. This way, the information passed becomes clearer, simple, more structured.

So far, we code with as little parameters as we can since most of our implementations are quite modular already.

Design Pattern

Remember the Design Pattern I mentioned earlier? So far we use some to reduce repetition, such as by using:

  1. Factory Pattern
Factory for CaseSubjects

2. Strategy Pattern

Object Managers are Strategies

3. Proxy Pattern

Permission as access control proxy implementer

How we should NOT do it

Like all good things in the world, too much of it can be a bad thing. One tends to overthink and overused some techniques to achieve “clean code” — when in fact, the code is already clean enough. In the end, you will be stuck since your “cleaning” is making more mess, and you will have to clean it again..

One example would be: imagine you have a series of simple if-else if-else, yet you try to apply a Design Pattern called state pattern or something else. It is already quite simple and easy to read, and it does not give any threat, so leave it that way. One thing to remember is the KISS principle above, Keep It Simple & Stupid.

So remember, with wrong usages: patterns could be anti-patterns, your so-called “refactoring” could mess your code even more.

That is all for clean code, ’til next time!

References

--

--

No responses yet