⬆️ ⬇️

Object gymnastics

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:

  1. Only one indent level per method
  2. Do not use Else
  3. Wrap all primitive types and strings
  4. First Class Collections
  5. One point per line
  6. Do not use abbreviations
  7. Keep entities short
  8. No classes with more than 2 attributes.
  9. 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(); // 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 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:

 // 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); 


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.

 // Game public void addScore(int delta) { score += delta; } // Usage game.addScore(ENEMY_DESTROYED_SCORE); 


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



Source: https://habr.com/ru/post/206802/



All Articles