Object gymnastics

Original author: William DURAND
  • Transfer
In the first two paragraphs of the original text, the author describes how he drank beer with friends. I replaced them with a Friday picture about childhood gymnastics.

Object gymnastics (Object Calisthenics) - these are exercises in programming , which consist of 9 rules that Jeff Bay described in his book " The ThoughWorks Anthology ". By trying to follow these rules as closely as possible, you will change your coding habits. This does not mean that you must abide by all these rules. Find a balance and use only those that are convenient for you.

These rules focus on readability, testability, comprehensibility, and maintainability.your code. If you are already writing code that we read, test, understand and support, then these rules will help make it more readable, testable. Clear and supported.

Below I will comment on these 9 rules:
  1. Only one level of indentation in a method
  2. Do not use Else
  3. Wrap all primitive types and strings
  4. First Class Collections
  5. One dot per line
  6. Do not use abbreviations
  7. Keep Entities Short
  8. No classes with more than 2 attributes
  9. No getters, setters, or properties


1. Only one level of indentation in a method


Many levels of indentation in your code impair readability and maintainability. Most of the time, you cannot understand the code without compiling it in your head, especially if you have conditions at different levels or a loop inside a loop, as shown in this example:
class Board {
    public String board() {
        StringBuilder buf = new StringBuilder();
        // 0
        for (int i = 0; i < 10; i++) {
            // 1
            for (int j = 0; j < 10; j++) {
                // 2
                buf.append(data[i][j]);
            }
            buf.append("\n");
        }
        return buf.toString();
    }
}

To follow this rule you must separate your methods. Martin Fowler, in his book “ Refactoring, ” cites the Extract Method method , which is exactly what you need to do.

You will not have fewer lines, but you will significantly improve their readability :
class Board {
    public String board() {
        StringBuilder buf = new StringBuilder();
        collectRows(buf);
        return buf.toString();
    }
    private void collectRows(StringBuilder buf) {
        for (int i = 0; i < 10; i++) {
            collectRow(buf, i);
        }
    }
    private void collectRow(StringBuilder buf, int row) {
        for (int i = 0; i < 10; i++) {
            buf.append(data[row][i]);
        }
        buf.append("\n");
    }
}


2. Do not use Else


The keyword is Elseknown to many, since there is a construction if/elsein almost all programming languages. Do you remember the last time you met the nested conditions? Did you enjoy reading them? I don’t think so, and I think this is exactly what should be avoided. It is so simple - to add one more branch instead of refactoring - that often in the end you have really bad code.
public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        redirect('homepage');
    } else {
        addFlash('error', 'Bad credentials');
        redirect('login');
    }
}

Just a way to clean elseit up is to rely on early return .
public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        return redirect('homepage');
    }
    addFlash('error', 'Bad credentials');
    return redirect('login');
}

The condition may be optimistic - when you have error conditions and the rest of the method adheres to the default script, or you can take a defensive approach (a bit related to Protective Programming ) - when you put the default script in the condition, and if it fails, then you return error status. This option is better, as it protects against potential problems that you did not think about.

An alternative would be to introduce a variable to make your return parametric . Although the latter is not always possible.
public void login(String username, String password) {
    String redirectRoute = 'homepage';
    if (!userRepository.isValid(username, password)) {
        addFlash('error', 'Bad credentials');
        redirectRoute = 'login';
    }
    redirect(redirectRoute);
}

It should also be recalled that OOP provides us with powerful capabilities, such as polymorphism . The Null Object , State, and Strategy patterns can also help you.

For example, instead of using if/elseto determine the action depending on the status (for example, RUNNING, WAITING, etc.), give preference to the State pattern , as it is used to encapsulate different behavior depending on the state object.


Source: sourcemaking.com/design_patterns/state

3. Wrap all primitive types and strings


Following this rule is pretty easy, you just have to encapsulate all the primitives in objects in order to avoid the anti-pattern of obsession with primitives (Primitive Obsession).

If a variable of your primitive type has a behavior, you must encapsulate it. And this is especially true for Domain-driven design (Domain Driven Design). DDD describes the value of the object (Value Objects), eg: Money Watch.

4. First Class Collections


Any class that contains a collection must not contain other attributes . If you have a set of elements and you want to manipulate them, create a class that is specifically designed for this set.

Each collection is wrapped in its own class, so behaviors related to the collection now have their place (for example, selection methods, applying the rule to each element).

5. One dot per line


A point is one that you use to call methods in Java or C #. In PHP, it will be an arrow.

Basically this rule says that you should not call methods in a chain . However, this does not apply to fluid interface (Fluent Interfaces), and in general everything that implements the pattern chain methods (for example, Query Builder).

For other classes, you must adhere to this rule. This is a direct consequence of Demeter’s law , which requires only direct friends and not strangers:

Look at these classes:
class Location {
    public Piece current;
}

class Piece {
    public String representation;
}

class Board {
    public String boardRepresentation() {
        StringBuilder buf = new StringBuilder();
        for (Location loc : squares()) {
            buf.append(loc.current.representation.substring(0, 1));
        }
        return buf.toString();
    }
}

It is normal to have public attributes in the classes Piece(plot) and Location(location). In fact, a public attribute and a private one with setters / getters are the same thing (see rule 9).

However, the method boardRepresentation()is terrible. Take a look at its first line:
buf.append(loc.current.representation.substring(0, 1));

The method takes Location, then its current Piece, then the view Piecewith which it performs the action. This is too far from one point per line .

Fortunately, Demeter’s law tells you to only contact your friends. Let's do that:
class Location {
    private Piece current;
    public void addTo(StringBuilder buf) {
        current.addTo(buf);
    }
}

By making the instance Pieceprivate, you make sure that you will not try to do anything bad. However, since you need to perform an action with this attribute, you need to add a new method addTo(). It is not the responsibility of the class Locationto add the Piece class, so you need to ask him:
class Piece {
    private String representation;
    public String character() {
        return representation.substring(0, 1);
    }
    public void addTo(StringBuilder buf) {
        buf.append(character());
    }
}

Now you must change the visibility of the attribute. I remind you that the principle of openness / closeness says that program entities (classes, modules, functions) should be open for extension , but closed for modification .

Also, highlighting the code to get the first character representationin a new method looks like a good idea, since it can be used at some point. Finally, the updated Board class code:
class Board {
    public String boardRepresentation() {
        StringBuilder buf = new StringBuilder();
        for (Location location : squares()) {
            location.addTo(buf);
        }
        return buf.toString();
    }
}

A lot better, right?

6. Do not use abbreviations



The right question is, why do you need to use abbreviations?

You can answer that it is because you write the same name again and again. And I will answer, if this method is used many times, then it looks like duplication of code.

You can say that the name of the method is too long anyway. Then I will tell you that perhaps this class has too many responsibilities, and this is bad, because it violates the principle of the only duty .

I often say that if you cannot find a suitable name for a class or method, then something is wrong. I use this rule when I follow development through the name .

Do not cut, point.

7. Keep Entities Short



A class is not more than 50 lines and a package is not more than 10 files. Well, it depends on you, but I think you can increase this number from 50 to 150.

The idea of ​​this rule is that long files are harder to read , harder to understand and harder to maintain.

8. No classes with more than 2 attributes



I thought people would laugh when I talk about this rule, but this did not happen. This rule is perhaps the most complex, but it contributes to high connectivity and better encapsulation .

A picture is worth a thousand words, so here is an explanation of this rule in the picture. Please note that this is based on the third rule.

Source: github.com/TheLadders/object-calisthenics#rule-8-no-classes-with-more-than-two-instance-variables

The main question is why are there two attributes? My answer is why not? Not the best explanation, but in my opinion, the main idea is to separate two types of classes , those that serve the state of one attribute, and those that coordinate two separate variables . Two is an arbitrary choice that forces your classes to be separated.

9. No getters, setters and properties


My favorite rule. It can be rephrased as indicated , rather than asked .

It’s normal to use properties to get the state of an object until you use the result to make decisions outside of the object. Any decisions based entirely on the state of one object must be made within the object itself.

This is why gertters / setters are considered evil . And again, they violate the principle of openness / closeness .

For example:
// Game
private int score;
public void setScore(int score) {
    this.score = score;
}
public int getScore() {
    return score;
}
// Usage
game.setScore(game.getScore() + ENEMY_DESTROYED_SCORE);

The code above is getScore()used to make a decision. You decide how to increase your score ( score), instead of leaving this responsibility to the class itself Game.

The best solution is to remove getters and setters and provide methods that make sense. Remember, you tell the class what to do, rather than asking it. Below, you tell the class to update your score, since you destroyed the ENEMY_DESTROYED_SCOREenemies.
// Game
public void addScore(int delta) {
    score += delta;
}
// Usage
game.addScore(ENEMY_DESTROYED_SCORE);

This is responsible for the game class how to update the score.

In this case, you can leave getScore()it as you want to display it somewhere in the user interface, but remember that setters are prohibited .

conclusions



If you do not like these rules - this is normal. But believe me when I say that they can be used in real life. Try them in your free time, for example, to refactor your open source projects. I think this is just a matter of practice. Some rules are easy to follow and may help you.

References



Also popular now: