loading...
Cover image for Identifying the dirt in our code - third party, tests and classes

Identifying the dirt in our code - third party, tests and classes

rachc profile image Rachel Curioso :D ・6 min read

When I started with the clean code series, the intention was not only to fix my knowledge about the subject but to pass on the knowledge that I acquired.

This series also serves as a quick guide to the book. When I need to remember some topics, I can find faster what I'm looking for.

On the first post, I talked about names, functions, and comments.

On the second post I talked about formatting, object and data structure, and how to handle errors.

In this last post, I talk about how to handle third-party code, how to organize your tests and your classes.

In addition to these 9 topics, the clean code book talks about dealing with systems, some more topics about Java, and it also covers how to identify some code smells.

I decided not to cover these topics because I would like to make the series more generic and friendly to all languages. Also because I don't have so much practical knowledge about building systems from scratch, and I also believe that an exclusive series on refactoring could better address code smells since in the clean code book the theme is a bit superficial.

That said, we can start with the topics of today's posts:

Handle with third-party code

two aliens partying

We hardly make a code 100% alone. We usually use some lib to help us, and most of the times, some colleagues contribute to the code

It's important to learn how to handle the code boundaries, identifying where our code ends and the other's code begins so we can maintain the system working more easily.

One of the problems with using third-party code is that they usually have more features than we need and this can make our code behave strangely.

Encapsulate your library!

The best way to solve this problem is to encapsulate the library we will use in a class, and pull only the methods that we will need.

Another advantage of encapsulating a third-party library into its own class is by doing so, we can make it easier to change the lib versions or even swap the library itself.

Let's suppose we have a library in our code that connects to a payment service. When we want to change the service, it is possible to make this change only in the class we created. We can adapt our method without the need to look through the entire code to change every reference from the old library to the new one.

Exploring third-party code

When we want to add a new library to our code, we need to understand what it does and how it works. Using code we don't understand in production can cause some problems.

The best way to find out the behavior of a library we want to add is to write tests for situations that we want the library to solve and get them passed. This way we can not only figure out how the code works, but these tests can be useful for any version or a library change.

If you change the version and run the tests, you will find out which behaviors no longer work and correct them.

If I can summarize the use of third party libraries in your code in some topics, they would be:

  • Maintain a clear separation of third party code and yours.
  • Write good tests to see if the third party lib will behave the way that makes sense for your code. It is important to note that you are testing your code, not the library.
  • Make sure that our code has no contact with third party codes. It is better to depend on something you control than something you do not control.
  • Encapsulate and create adapters so that our code refers to it as little as possible, and that we can swap the library in case of any problems.

Organizing your tests

Some cartoon showing a scientist testing something

The biggest concern we have when writing a test should be its readability.

In test code, readability may be even more important than production code.

When writing a test we should avoid writing details that are not part of it so that the person who needs to (re)fix this test in the future knows what is happening without the need to understand unnecessary code.

Use one assert per test

Ideally, you only have one assert per test, so there is no doubt which part of the test has broken when this happens.

This rule could result in many repeated tests. To solve this, you can use before with the code block that repeats.

If there is too much repetition, we may even use more than one assert per test, but very carefully to keep the number of asserts small.

Or, giving my opinion - not Uncle Bob - you may not solve this problem, since too much DRY can impair code readability, and readability should always be your priority when it comes to testing.

Perhaps a better rule is that each test should only test one concept.

It is very important that there is not more than one test at a time to keep it concise, and you can keep track of what is happening.

Uncle Bob uses an acronym to help to remember the rules of a clean test.

F.I.R.S.T

  • Fast: Tests should be fast. If they take too long, we won't want to run the tests.
  • Independent: A test should never depend on another to run. It should be possible to run all the tests randomly without breaking.
  • Repeatable: A test should run in any environment, whether it is QA, Production or in a computer in your house.
  • Self-validating: The test result should always be a boolean, informing if it passes or not.
  • Timely: They have a right time to be written, which is before production code. If you leave it for later, you may find the test too difficult to write and not test (TDD).

Organizing your classes.

Cookie monster baking some cookie

In java, the order that a class should be organized is this:

  1. Variables list.
  2. Public static constants.
  3. Private static variables.
  4. Instance private variables.
  5. Public Functions.
  6. Private functions called by public functions should be immediately below the calling public function.

In Ruby and other languages, however, all private methods must be grouped and written after all public code. Ideally, they follow the order they were called by public methods.

Classes should be small.

When we say that a function must be small, we say that it must have a few lines.

When we say that a class must be small, we mean that it must follow the principle of single responsibility.

But how do we know if a class has too many responsibilities?

The first sign that a class has too many responsibilities is its name.

Classes should be named in an explanatory way so that we can understand what they do. If you need an "and", an "or", an "if" and a "but", they are already doing more than one thing.

In the example below we can see that the SuperDashboard class looks for the last focused component and changes the build version, thus having two responsibilities.

public class SuperDashboard {
  public Component getLastFocusedComponent();
  public int getMajorVersionNumber();
  public int getMinorVersionNumber();
  public int getBuildNumber();
}

One way to solve this problem is to create a class for each responsibility.

Some people complain that when we have many classes, it's hard to find what we want.

But when we stop to think that if we have a class with many things, we will also have difficulty finding what we are looking for. When we have many classes, it is as if we have several drawers labeled with their contents. But when we have just a few classes we have few stuffed drawers.

A little about cohesion

In an ideal world, the functions inside classes should speak only with the variables created in that class, but as this is impossible, we need our classes to be cohesive.

New classes as a result of refactoring.

We can observe the existence of a flow when we are refactoring a code, which can result in the creation of more classes:

  1. When we break a large function into several smaller ones, we feel the need to share variables.
  2. We want to create class instance variables so that functions don't need parameters, but why do we create instance variables that the minority of functions will use? That doesn't look very interesting.
  3. Is this not an indication that new classes could be created? : D

Organizing a class for changes.

We need to remember that a class must follow the "open-closed" principle, being open to extension and closed to change.

Sometimes our class encapsulates a third-party library that is constantly changing. When this happens, we can solve this with a parent class to connect to the library, and create multiple child classes with the new library implementations as they happen.


post image from Sarah Dorweiler

Discussion

pic
Editor guide
Collapse
aeddi13 profile image
Patrick Decker

I really like the comparison of classes to drawers. When people say they have difficulty finding the right classes because there are too many, the problem often is, that these classes are not organized in the right way.
When you have your classes organized in a folder structure that maps the domain logic really well it is actually quite easy to find the class you are looking for.

This is a great series about clean code.
Thanks a lot.