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 of gymnastics from childhood.
Object gymnastics (Object Calisthenics) is a
programming exercise that consists of
9 rules that Jeff Bey described in his book
The ThoughWorks Anthology . Trying to follow these rules as best you can, you will change your coding habits. This does not mean that you must constantly follow all these rules. Find a balance and use only those that are convenient for you.
These rules focus on the
readability, testability, clarity and maintainability of your code. If you already write code that we read, test, understand and support, then these rules will help make it more readable, testable. Understandable and supported.
')
Below I will comment on these 9 rules:
- Only one indent level per method
- Do not use Else
- Wrap all primitive types and strings
- First Class Collections
- One point per line
- Do not use abbreviations
- Keep entities short
- No classes with more than 2 attributes.
- No getters, setters and properties
1. Only one level of indent in method
Many levels of indentation in your code
degrade 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();
To follow this rule you must separate your methods. Martin Fowler, in the book
Refactoring , gives the
Extract Method , which is just 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
Else
keyword is well known to many, as the
if/else
is found in almost all programming languages. Do you remember when you last met the enclosed conditions? Did you enjoy reading them? I do not think so, and I think this is exactly what should be avoided. It's so simple - to add another branch instead of refactoring - which often at the end you have a really bad code.
public void login(String username, String password) { if (userRepository.isValid(username, password)) { redirect('homepage'); } else { addFlash('error', 'Bad credentials'); redirect('login'); } }
The only way to remove the
else
is to rely on the
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 remainder of the method follows the default scenario, or you can take a
defensive approach (somewhat related to
Protection Programming )
—when you put the default script into a condition, and if it does not hold, then you return error status. This option is better because it protects against potential problems that you have not thought of.
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); }
Also, it should be recalled that OOP gives us powerful capabilities, just like
polymorphism . Patterns
Object Null ,
Status and
Strategy can also help you.
For example, instead of using
if/else
to 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.
It’s pretty easy to follow this rule, you just have to
encapsulate all primitives into objects to avoid the anti-
obsession pattern of
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. DDD describes Value Objects, for example: Money, Clock.
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 that the
behaviors related to the collection now have their place (for example, selection methods, applying the rule to each element).
5. One point per line
The point is the one 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 on a chain . However, this does not apply to the Fluent Interfaces, and in general, everything that implements the
pattern of a chain of methods (for example, the query builder).
For other classes you must stick to this rule. This is a direct consequence of the
law of Demeter , which prescribes
to refer only to immediate friends and not to appeal to 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
Piece
and Plot classes. In fact, the public attribute and private with setters / getters are the same (see rule 9).
However, the
boardRepresentation()
method 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
Piece
view, with which it performs the action. This is too far from
one point per line .
Fortunately, the law of Demeter tells you to contact only friends. Let's do that:
class Location { private Piece current; public void addTo(StringBuilder buf) { current.addTo(buf); } }
By making the
Piece
instance private, you made sure that you would not try to do something bad. However, since you need to perform an action on this attribute, you need to add a new
addTo()
method. It is not the responsibility of the
Location
class to 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 to expansion , but
closed to modification .
Also, the selection of the code for obtaining the first character
representation
in the new method looks like a good idea, since it can be used at some stage. 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(); } }
Much better, right?
6. Do not use abbreviations
The correct question is,
why should you use abbreviations?
You can answer that it is because you write the same name over and over 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 obligation .
I often say that if you can't find a suitable name for a class or method, then something is wrong. I use this rule when I follow the
development through the title .
Do not cut, point.
7. Keep Entities Short
The class is not more than
50 lines and the 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 behind 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 for 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 two attributes? My answer is
why not? Not the best explanation, but in my opinion, the main idea is to divide
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 you to strongly divide your classes.
9. No getters, setters and properties.
My favorite rule. It may be rephrased, as
indicated , but
not asked .
It's normal to use properties to get the state of an object, until you use the result to make a decision outside the object. Any decisions based entirely on the state of one object must be made inside the object itself.
This is why
gertters / settepras are considered evil . And again, they violate the
principle of openness / closeness .
For example:
In the code above,
getScore()
used to make a decision. You decide how to increase your score (
score
), instead of leaving this responsibility to the
Game
class itself.
The best solution is to remove the getters and setters and provide methods that make sense. Remember, you tell
the class what to do,
not ask it. Below, you tell
the class to update your score, since you destroyed the
ENEMY_DESTROYED_SCORE
enemies.
It is a responsible game class how to update the score.
In this case, you can leave
getScore()
as you would like to output it somewhere in the user interface, but remember that
setters are prohibited .
findings
If you don't like these rules, that's fine. 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 it's just a matter of practice. Some rules are simple to follow and can help you.
Links