Нотатки за книгою "Елегантні об'єкти" Єгора Бугаєнко


Нотатки за книгою Елегантні об'єкти Єгора Бугаєнко

У цій статті хочу поділитися своїми нотатками на книгу Єгора Бугаєнка "Елегантні об'єкти. Java Edition". Книга про Об'єктно-орієнтоване програмування (ООП) з практичними порадами щодо поліпшення супроводу та проектування проектів. Поради націлені на те, щоб зробити класи чистішими, ціліснішими і зрозумілішими. До більшості порад я вказав посилання на сайт Єгора Бугаєнко, де можна більш детально вивчити пораду з прикладами та коментарями.

Поради та принципи для застосування

Не використовувати закінчення -er в імені класу

Назва класу з "-er" говорить про те, що це набір процедур для маніпулювання даними.

💡
Клас потрібно називати на основі того, чим він є, а не того, що він робить.

Повноцінний об'єкт - це представник власних даних, а не бездумний перехідник між своїм внутрішнім станом і зовнішнім світом.

Приклади поганих назв:

Manager, Controller, Helper, Handler, Writer, Reader, Converter, Validator (-or также вреден), Router, Dispatcher, Observer, Listener, Sorter, Encoder и Decoder.

Винятком є такі назви, як: User, Computer.

Don't Create Objects That End With -ER
Manager. Controller. Helper. Handler. Writer. Reader. Converter. Validator. Router. Dispatcher. Observer. Listener. Sorter. Encoder. Decoder. This is the class names hall of shame. Have you seen them in your code? In open source libraries you're using? In pattern books? They are all wrong. What do they have in common?
https://www.yegor256.com/2015/03/09/objects-end-with-er.html

Зробіть головним один конструктор у класі

Конструктор - точка входу та створення нового об'єкта.

Має бути тільки один головний конструктор, який ініціалізує властивості. Решта конструкторів з іншим набором даних повинні використовувати головний конструктор.

Це покращує супроводжуваність створення об'єктів класу.

There Can Be Only One Primary Constructor
I suggest classifying class constructors in OOP as primary and secondary. A primary constructor is the one that constructs an object and encapsulates other objects inside it. A secondary one is simply a preparation step before calling a primary constructor and is not really a constructor but rather an introductory layer in front of a real constructing mechanism.
https://www.yegor256.com/2015/05/28/one-primary-constructor.html

Конструктори робити без виконуючого коду

Конструктори повинні тільки створювати об'єкт, не роблячи обробку переданих у них даних.

Обробка даних відбуватиметься на вимогу викликом методів об'єкта.

Плюси цього підходу:

  • Спрощує конструктори: у них відбувається лише ініціалізація полів.
  • Робота з перетворення даних відбувається тільки тоді, коли вона дійсно знадобиться. Результат перетворень можна закешувати за допомогою декоратора, якщо це буде потрібно.
Constructors Must Be Code-Free
How much work should be done within a constructor? It seems reasonable to do some computations inside a constructor and then encapsulate results. That way, when the results are required by object methods, we'll have them ready. Sounds like a good approach? No, it's not.
https://www.yegor256.com/2015/05/07/ctors-must-be-code-free.html

Інкапсулюйте в класах якомога менше полів

Клас із невеликою кількістю полів легше супроводжувати і тестувати.

💡
Слід інкапсулювати не більше чотирьох полів.

Це спрощує ідентифікацію об'єкта в системі. Ідентичність об'єкта - це як координати об'єкта в системі. Чим більше координат, тим ускладнюється супроводжуваність.

Інкапсулюйте в класі що-небудь

Повноцінний клас в ООП не може бути набором методів без властивостей. Він співіснує з іншими класами в системі та використовує їх. Отже, клас інкапсулює інші класи для своєї роботи.

Наявність стану в об'єкта класу також допомагає ідентифікувати об'єкт у системі.

How Much Your Objects Encapsulate?
Which line do you like more, the first or the second: What is the difference? The first class HTTP encapsulates a URL, while the second one expects it as an argument of method read(). Technically, both objects do exactly the same thing: they read the content of the Google home page.
https://www.yegor256.com/2014/12/15/how-much-your-objects-encapsulate.html

Завжди використовуйте інтерфейси

Плюси використання інтерфейсів:

  • Описують контракт класу: що клас може робити. Покращує організованість і супроводжуваність.
  • Дають змогу розчепити (decoupling) класи між собою: легше підміняти класи і тестувати.
Seven Virtues of a Good Object
Martin Fowler says: A library is essentially a set of functions that you can call, these days usually organized into classes. Functions organized into classes? With all due respect, this is wrong. And it is a very common misconception of a class in object-oriented programming. Classes are not organizers of functions.
https://www.yegor256.com/2014/11/20/seven-virtues-of-good-object.html#2-he-works-by-contracts

Обдумано іменуйте методи

  • Методи «Будівельники», які щось повертають у відповіді, називати іменниками.

Не потрібно в імені методу додавати префікси: get, create, fetch. Метод сам має знати, як йому створити результат. Додавання префікса робить виклик методу наказом.

Приклади:

int pow(int base, int power);
int speed ();
Employee employee(int id);
String parsedCell(int х, int у);
  • Методи «Маніпулятори» називати дієсловами. Вони нічого не повертають у відповіді.

Дієслово вказує, що метод виконує певну роботу та перетворення. У відповіді такого методу немає результату, тому що за певних умов робота може й не виконатися.

Приклади:

void save(String content);
void put(String key, Float value);
void remove(Employee emp);
void quicklyPrint(int id);
  • Методи, які повертають результат булевого типу називати прикметниками.

Приклади:

boolean empty ();
boolean readable ();
boolean negative();

Не використовуйте публічні константи

Константи служать спільного застосування даних.

Негативні наслідки використання публічних констант об'єктами різних класів:

  • Привнесення зчеплення: класи стають жорстко пов'язані між собою. Це негативно позначається на супроводжуваності та тестуванні.
  • Втрата цілісності: класи стають залежними від зовнішньої логіки, клас стає менш самодостатнім і інкапсульованим. А також публічні константи можуть бути використані в найрізноманітніших доменних областях. Константа не регулює де вона використовується.

Замість публічних констант слід створити нові класи, які допоможуть класам спільно використовувати логіку констант.

Public Static Literals ... Are Not a Solution for Data Duplication
I have a new String(array,"UTF-8") in one place and exactly the same code in another place in my app. Actually, I may have it in many places. And every time, I have to use that "UTF-8" constant in order to create a String from a byte array.
https://www.yegor256.com/2015/07/06/public-static-literals.html

Робіть класи незмінними

Використовуючи незмінні класи ми покращуємо супроводжуваність. Незмінні класи простіше зрозуміти і підтримувати.

В об'єктах таких незмінних класів не можна змінити стан після створення. Якщо потрібно змінити стан об'єкта, ми повинні створити новий об'єкт зі зміненим станом і повернути його в результаті виклику методу.

Плюси:

  • Атомарність відмов: у нас або цілісний об'єкт, або відбувається відмова. Немає проміжного стану.
  • Відсутність тимчасового зчеплення: наявність можливості змінювати стан об'єкта за допомогою сеттерів може призвести до помилок під час виконання програми. Це може бути неправильний порядок використання сеттерів або відсутність виклику.
  • Відсутність побічних ефектів: при виклику методів немає неочевидних змін стану об'єкта.
  • Відсутність NULL-посилань: об'єкти створюються в цілісному стані. Немає необхідності перевіряти на NULL під час подальшої роботи об'єкта.
  • Потокобезпека: незмінні об'єкти завжди в передбачуваному стані і можуть бути використані паралельно в різних потоках.
  • Менші та простіші об'єкти: незмінність допомагає зробити класи чистішими та коротшими. Важко зробити незмінний клас занадто великим.
Objects Should Be Immutable
In object-oriented programming, an object is immutable if its state can't be modified after it is created. In Java, a good example of an immutable object is . Once created, we can't modify its state. We can request that it creates new strings, but its own state will never change.
https://www.yegor256.com/2014/06/09/objects-should-be-immutable.html

Пишіть тести замість документації

Допоміжна інформація дуже важлива для супроводжуваності. Вона допомагає читачеві краще зрозуміти використання коду.

Якщо є необхідність писати документацію, це може бути ознакою погано спроектованого коду.

💡
Ідеальний код говорить сам за себе і не потребує додаткової документації.

Погано спроектовані класи вимагають писати документацію для них. Відповідно, якщо клас спроектовано добре, документація не потрібна.

💡
Не документуйте код - робіть його чистішим.

Краще писати прості зрозумілі класи, ніж писати під них документацію.

💡
Добре написані unit-тести - корисна документація використання класу.

У unit-тесті можна подивитися:

  • Варіанти використання класу.
  • Дані, які подаються на вхід.
  • Помилки, які можуть бути викликані в процесі роботи класу.

Використовуйте fаkе-об'єкти замість mосk-об'єктів

Мокінг ускладнює супровід тестів, оскільки перетворює припущення на факти. Моки розкривають внутрішню логіку класу. Класи перестають бути чорними скриньками під час тестування.

Фейкові класи покращують супроводжуваність тестів. Вони допомагають краще продумати і спроектувати інтерфейс класу.

Built-in Fake Objects
While mock objects are perfect instruments for unit testing, mocking through mock frameworks may turn your unit tests into an unmaintainable mess. Thanks to them we often hear that "mocking is bad" and "mocking is evil." The root cause of this complexity is that our objects are too big.
https://www.yegor256.com/2014/09/23/built-in-fake-objects.html

Робіть максимум 5 публічних методів у класі

Плюси невеликих класів:

  • Менша ймовірність зробити помилку: легше узгодити невелику кількість методів.
  • Краще супроводжуваність: менше коду, менше методів, легше розуміти і підтримувати.
  • Простіше досягти цілісності класу - зв'язку методів класу з усіма його властивостями.
  • Простіше тестувати: менше точок входу і простіше відтворити всі сценарії використання.

Не використовуйте статичні методи

Статичні методи більше належать до процедурного стилю програмування, а не ООП. Вони ускладнюють підтримку і тестування.

Також не варто використовувати класи-утиліти. Це не повноцінний об'єкт, а набір процедур.

OOP Alternative to Utility Classes
A utility class (aka helper class) is a "structure" that has only static methods and encapsulates no state. StringUtils, IOUtils, FileUtils from Apache Commons; Iterables and Iterators from Guava, and from JDK7 are perfect examples of utility classes. This design idea is very popular in the Java world (as well as C#, Ruby, etc.)
https://www.yegor256.com/2014/05/05/oop-alternative-to-utility-classes.html

Не допускайте аргументів із значенням NULL

Дозволяючи передачу NULL як аргумент, потрібно буде додавати перевірку на NULL у методі. Для того, щоб обробити такий варіант виклику методу. Це ускладнює логіку та знижує супроводжуваність.

Якщо користувач методу все-таки передав NULL, краще ігнорувати таке використання та не робити додаткову перевірку. Зрештою, це все одно призведе до помилки NullPointerException і користувачеві потрібно буде виправити помилку.

Why NULL is Bad?
A simple example of NULL usage in Java: What is wrong with this method? It may return NULL instead of an object-that's what is wrong. NULL is a terrible practice in an object-oriented paradigm and should be avoided at all costs.
https://www.yegor256.com/2014/05/13/why-null-is-bad.html

Не використовуйте гетери та сетери

Повноцінні об'єкти в ООП це не прості структури даних.

Структура даних - це набір даних, прозорий ящик. Ми безпосередньо спілкуємося з її даними. Структура даних пасивна.

Об'єкт - це чорний ящик зі своєю логікою роботи та інкапсульованими даними.

Отже, геттери і сеттери в об'єктах порушують його цілісність і розкривають його деталі. При цьому інкапсуляція не використовується повноцінно. Такі об'єкти стають подібними до структур даних.

Getters/Setters. Evil. Period.
There is an old debate, started in 2003 by Allen Holub in this Why getter and setter methods are evil famous article, about whether getters/setters is an anti-pattern and should be avoided or if it is something we inevitably need in object-oriented programming. I'll try to add my two cents to this discussion.
https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html

Не використовуйте оператор new поза вторинними конструкторами

Не слід створювати в одному класі об'єкт іншого класу. Це пов'язує два класи жорстким зв'язком. Виникають труднощі при супроводі та тестуванні. Не можна підмінити клас створюваного об'єкта без зміни коду.

Краще використовувати ін'єкцію (впровадження) залежностей з використанням інтерфейсів.

У вторинних конструкторах можна використовувати new() для створення об'єктів свого класу.

Operator new() is Toxic
To instantiate objects, in most object-oriented languages, including Java, Ruby, and C++, we use operator new(). Well, unless we use static factory methods, which we don't use because they are evil. Even though it looks so easy to make a new object any time we need it, I would recommend to be more careful with this rather toxic operator.
https://www.yegor256.com/2018/01/02/operator-new-is-toxic.html

Уникайте інтроспекції та приведення типів

Йдеться про оператор instanceof та метод Class.cast() у Java або їх аналоги іншими мовами.

Приведення типів і перевірки на тип негативно позначаються на розумінні та супроводжуваності коду.

Це дискримінація об'єктів за типом. Залежно від типу об'єкта ми використовуємо його по-різному.

Використовуючи інтроспекцію і наведення типів, ми висловлюємо свої очікування про об'єкт без їх документування. Це стає непрозорими прихованими відносинами, які впливають на супроводжуваність.

Class Casting Is a Discriminating Anti-Pattern
Type casting is a very useful technique when there is no time or desire to think and design objects properly. Type casting (or class casting) helps us work with provided objects differently, based on the class they belong to or the interface they implement.
https://www.yegor256.com/2015/04/02/class-casting-is-anti-pattern.html

Ніколи не повертайте NULL

Повернення NULL вносить недовіру до класу. Це змушує користувачів класу перевіряти ще раз і обробляти результат його роботи.

Повернення NULL - це спроба безпечної відмови, що призводить до погіршення супроводжуваності.

Є три варіанти вирішення:

  • Викидати виняток для швидкої відмови (fail fast). Це допомагає клієнту швидше побачити й обробити помилку.
  • Повертати колекцію об'єктів. Це допомагає обробити варіант із порожнім результатом пошуку об'єкта.
  • Використовувати патерн «Порожній об'єкт» («Null object»), якщо це підходить під ситуацію. Такий об'єкт схожий на справжній, але він поводиться інакше.
Why NULL is Bad?
A simple example of NULL usage in Java: What is wrong with this method? It may return NULL instead of an object-that's what is wrong. NULL is a terrible practice in an object-oriented paradigm and should be avoided at all costs.
https://www.yegor256.com/2014/05/13/why-null-is-bad.html

Кидайте лише виключення, що перевіряються.

Перевірені винятки Java додає контракт і покращує документування коду. Краще використовувати їх замість неперевірених.

Ще кілька порад:

  • Не ловити винятки без необхідності: обробку винятків краще проводити на більш верхніх рівнях програми
  • Будуйте ланцюжки винятків: ловимо виняток на нижньому рівні і викидаємо нове з додатковою інформацією і словленим винятком у ланцюжку. Так поліпшується контекст помилки.
  • Відновлюйтеся один раз: відновлення і повернення користувачеві помилки повинні бути на найвищому рівні програми.
Checked vs. Unchecked Exceptions: The Debate Is Not Over
Do we need checked exceptions at all? The debate is over, isn't it? Not for me. While most object-oriented languages don't have them, and most programmers think checked exceptions are a Java mistake, I believe in the opposite-unchecked exceptions are the mistake. Moreover, I believe multiple exception types are a bad idea too.
https://www.yegor256.com/2015/07/28/checked-vs-unchecked-exceptions.html

Робіть класи або константними (final), або абстрактними

У разі звичайного успадкування класів можуть виникати проблеми із супроводжуваністю, тому що відносини між класами можуть стати заплутаними. Методи можуть бути перевизначені на різних рівнях успадкування. При цьому зростає складність і стає важче читати і розуміти код.

Краще використовувати константні (final) класи або абстрактні батьківські класи. Це стосується як самого класу, так і до методів.

Такий підхід дає змогу чітко вказувати наміри та запобігає можливості невдалого використання успадкування:

  • final - чорний ящик для його користувачів. Його можна тільки інкапсулювати.
  • abstract - незавершений прозорий ящик, напівфабрикат. У нього є відсутні компоненти, які потрібно зробити.
Seven Virtues of a Good Object
Martin Fowler says: A library is essentially a set of functions that you can call, these days usually organized into classes. Functions organized into classes? With all due respect, this is wrong. And it is a very common misconception of a class in object-oriented programming. Classes are not organizers of functions.
https://www.yegor256.com/2014/11/20/seven-virtues-of-good-object.html#7-his-class-is-either-final-or-abstract

Підсумок

У книзі багато корисних і практичних порад із прикладами. З деякими з них можна не погоджуватися або їх буде важко застосувати у вже наявних проєктах. Але поступове їх прийняття та застосування призведе до поліпшення ваших навичок і супроводжуваності ваших проєктів, у яких використовується ООП.

Сайт книги Elegant Objects:

Elegant Objects
Elegant Objects (EO) is an object-oriented programming paradigm that renounces traditional techniques like null, getters-and-setters, code in constructors, mutable objects, static methods, annotations, type casting, implementation inheritance, data objects, etc. Books: Online: Telegram chat and@painofoop (Russian only). Join our soalition if you are ready to help. Twitter: #elegantobjects.
https://www.elegantobjects.org/

Сайт автора книги Єгора Бугаєнко:

Elegant Objects
"Elegant Objects" is my first book. It is about object-oriented programming from an idealistic and, maybe, a bit of a futuristic point of view. There are 23 independent recommendations for a practical OOP designer on how to make code cleaner, classes more solid, and architecture more visible.
https://www.yegor256.com/elegant-objects.html

Дякую, що прочитали. Буду вам дуже вдячний, якщо поділитесь цією статтею!

Опубліковано