How long should classes be – when “clean” code isn’t really that good

How long should classes be – when “clean” code isn’t really that good

Hello, Habre!

Our colleagues from beeline cloud threw up an interesting article for PHP translation, bad practices and more. This is the story of how the rules of clean code can undermine its actual quality. The material contains many considerations on this topic and will be useful to anyone who is just starting their development journey. Happy reading!

The fictional story of one developer

Recently, I was looking for a new job, carefully sifting through a sea of ​​job openings, each one eager to attract attention and entice a candidate to send in a resume.

In the stream of countless and identical ads, one offer really stood out. It was written differently. Especially, without this advertising and sales message. The company was not just looking for a developer, it needed a true master of his craft. “We value clean code,” they said.

For a brief moment I felt genuine joy. But, as always, the trick was revealed in the very next paragraph: “Each class we write is limited to a hundred lines.”

Photo: Ben Griffiths / Unsplash.com

We estimate the length of the class

For your understanding, my main language is PHP, and the vacancy was intended specifically for PHP developers.

And here I would like to note that it is not so easy to determine the optimal class size by eye. Each programming language has its own nuances and features. There are weakly typed and strongly typed languages. Some support multi-threading, others – single-threading. The list can be continued for a long time. In addition, there are also standards.

I haven’t come across Java for almost a decade now, but if I remember correctly, it’s customary to put an opening parenthesis at the end of a line. In PHP, it is customary to put this parenthesis on a separate line.

Here is a simple example:

public class Calculator {
    public double add(double number1, double number2) {
        return number1 + number2;
    }
}
class Calculator
{
    public function add(float $number1, float $number2): float
    {
        return $number1 + $number2;
    }
}

At first glance, these fragments have the same functionality. But if you follow the coding standards of both languages, the PHP version will be longer than its Java counterpart. And that’s without taking into account extra lines like imports, comments, and line skipping to improve readability—all of which add to the length of the class.

You will say that such things do not apply to the length of the class. But someone may seriously think that if there are already empty lines in the file, they should also be taken into account.

What will happen if you tie the developer’s hands with a strict rule – no more than a hundred lines of code per file? Let’s look at the possible results.

We encourage bad practices

A function definition in PHP, by default, requires at least four lines—one for the function title, two for opening and closing braces, and at least one meaningful line in the body.

If the number of lines is limited, it is tempting to save space by writing overly complex functions.

In addition, it is easy to fall into the abyss of questionable strategies, approaches and methods. Which ones, you ask? Here’s my “favorite” way to save file space: long and overly complex expressions on one line.

I will give here a fragment from mine integration library. This is part of the class responsible for creating and managing access to remote resources:

class Address implements AddressContract, Stringable
{
    //...

    /**
     * Checks if the collection contains parameters with
     * the given names and validates them. This method
     * accepts an arbitrary number of arguments, each of
     * which should be a string representing a parameter
     * name.
     *
     * @param string ...$names The names of the parameters
     * to check for.
     *
     * @return bool Returns true if all parameters are
     * found, false otherwise.
     */
    public function hasValid(string ...$names): bool
    {
        foreach ($names as $name) {
            if (!isset($this->parameters[$name]) || !$this->parameters[$name]->isValid()) {
                return false;
            }
        }
        return true;
    }

    //...
}

For context, Address in my system assumes parameters like {id} in https://example.com/articles/{id}. The code checks whether the Address instance contains valid parameters.

There is a very clear PHPDoc (by the way, it is still under development), and the code itself is easy to learn. It iterates through all of the specified names and verifies that each one exists and is valid. Otherwise, false is returned. If the if statement does not return true, then the output will also be true.

And to save space, I could write the following code instead:

class Address implements AddressContract, Stringable
{
    //...

    public function hasValid(string ...$names): bool
    {
        return array_reduce($names, fn($carry, $name) => { return $carry && isset($this->parameters[$name]) && $this->parameters[$name]->isValid(); }, true);
    }

    //...
}

Yay, we saved 5 rows!

Let employees waste their time

Obsessing over class length is simply counterproductive.

Instead of putting their energy into creating high-quality components, developers count lines and invent crutches to fit within the given framework.

While one may argue that the row counting process can be automated, it is a sharp sword. Automation can unwittingly promote bad practices, as the developer will probably want to outsmart the system.

Even if it is designed to ensure compliance with the best practices in the world, it will be a perpetual game of cat and mouse; developers will come up with new ways to get around existing rules to save time.

At the same time, do not forget that automation must either somehow work separately or be part of the CI/CD pipeline.

One way or another, the developers will not have instant feedback on the compliance of the code with internal standards, and this is another stone in the garden of the productivity of their work.

A tangle of tiny classes

Forcing classes into components can sometimes inadvertently create what I like to call a “rat king”: a tangle of tiny classes that can’t live without each other.

This is probably the most common problem, and it requires close attention.

With one comprehensive class, users only need to know about that class. However, if you break it down into many small, closely related classes, users will have to know about them all.

This is convenient because each component can be replaced. This is justified, but only if you really plan to replace something.

John Ousterhout points this out in a talk about the old Java I/O handling system. In order to efficiently read serialized objects from a file, it used to be necessary to instantiate three nested objects. At the same time, it was practically never necessary to interact with the first two in an explicit form.

// You rarely interact with these two objects,
// except when injecting them into the next one.
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);

// You actually interact with this one.
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);

This problem often arises due to the abuse of the principle of sole responsibility. Trying to break a large class into many smaller ones can lead to creating classes that are useless.

Putting a hard limit on class length can further encourage such an architecture.

There are approaches and better ones

It is difficult to qualitatively monitor the counting of rows in the classroom. In addition, it actually says nothing about the quality of the code itself, only about its length.

Of course, a large number of lines can serve as a kind of marker — a signal that the code needs refactoring. However, refactoring decisions based on length alone should be made with caution.

Take the Model class in Laravel as an example. It has more than 2.4 thousand lines of code. Although this may seem like a huge amount, it is deliberately designed that way. This approach ensures that developers can extend the base Model class, and voila, their own model works without too much effort.

When writing code, first of all, you should pay attention to its convenience, and not to its length. Try to make things that the user will work with most often as simple as possible.

This article is inspired by my personal experience and John Ousterhout’s book The Philosophy of Software Design. This book is an in-depth study of how to write good code and is a must-read for anyone serious about software development.

beeline cloud – Secure cloud provider. We develop cloud solutions so that you provide customers with the best services.

Read more about the development here:

Related posts