У цій статті хочу поділитися своїми нотатками на книгу Єгора Бугаєнка "Елегантні об'єкти. Java Edition". Книга про Об'єктно-орієнтоване програмування (ООП) з практичними порадами щодо поліпшення супроводу та проектування проектів. Поради націлені на те, щоб зробити класи чистішими, ціліснішими і зрозумілішими. До більшості порад я вказав посилання на сайт Єгора Бугаєнко, де можна більш детально вивчити пораду з прикладами та коментарями.
Поради та принципи для застосування
Не використовувати закінчення -er в імені класу
Назва класу з "-er" говорить про те, що це набір процедур для маніпулювання даними.
Повноцінний об'єкт - це представник власних даних, а не бездумний перехідник між своїм внутрішнім станом і зовнішнім світом.
Приклади поганих назв:
Manager, Controller, Helper, Handler, Writer, Reader, Converter, Validator (-or также вреден), Router, Dispatcher, Observer, Listener, Sorter, Encoder и Decoder.
Винятком є такі назви, як: User, Computer.
Зробіть головним один конструктор у класі
Конструктор - точка входу та створення нового об'єкта.
Має бути тільки один головний конструктор, який ініціалізує властивості. Решта конструкторів з іншим набором даних повинні використовувати головний конструктор.
Це покращує супроводжуваність створення об'єктів класу.
Конструктори робити без виконуючого коду
Конструктори повинні тільки створювати об'єкт, не роблячи обробку переданих у них даних.
Обробка даних відбуватиметься на вимогу викликом методів об'єкта.
Плюси цього підходу:
- Спрощує конструктори: у них відбувається лише ініціалізація полів.
- Робота з перетворення даних відбувається тільки тоді, коли вона дійсно знадобиться. Результат перетворень можна закешувати за допомогою декоратора, якщо це буде потрібно.
Інкапсулюйте в класах якомога менше полів
Клас із невеликою кількістю полів легше супроводжувати і тестувати.
Це спрощує ідентифікацію об'єкта в системі. Ідентичність об'єкта - це як координати об'єкта в системі. Чим більше координат, тим ускладнюється супроводжуваність.
Інкапсулюйте в класі що-небудь
Повноцінний клас в ООП не може бути набором методів без властивостей. Він співіснує з іншими класами в системі та використовує їх. Отже, клас інкапсулює інші класи для своєї роботи.
Наявність стану в об'єкта класу також допомагає ідентифікувати об'єкт у системі.
Завжди використовуйте інтерфейси
Плюси використання інтерфейсів:
- Описують контракт класу: що клас може робити. Покращує організованість і супроводжуваність.
- Дають змогу розчепити (decoupling) класи між собою: легше підміняти класи і тестувати.
Обдумано іменуйте методи
- Методи «Будівельники», які щось повертають у відповіді, називати іменниками.
Не потрібно в імені методу додавати префікси: 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();
Не використовуйте публічні константи
Константи служать спільного застосування даних.
Негативні наслідки використання публічних констант об'єктами різних класів:
- Привнесення зчеплення: класи стають жорстко пов'язані між собою. Це негативно позначається на супроводжуваності та тестуванні.
- Втрата цілісності: класи стають залежними від зовнішньої логіки, клас стає менш самодостатнім і інкапсульованим. А також публічні константи можуть бути використані в найрізноманітніших доменних областях. Константа не регулює де вона використовується.
Замість публічних констант слід створити нові класи, які допоможуть класам спільно використовувати логіку констант.
Робіть класи незмінними
Використовуючи незмінні класи ми покращуємо супроводжуваність. Незмінні класи простіше зрозуміти і підтримувати.
В об'єктах таких незмінних класів не можна змінити стан після створення. Якщо потрібно змінити стан об'єкта, ми повинні створити новий об'єкт зі зміненим станом і повернути його в результаті виклику методу.
Плюси:
- Атомарність відмов: у нас або цілісний об'єкт, або відбувається відмова. Немає проміжного стану.
- Відсутність тимчасового зчеплення: наявність можливості змінювати стан об'єкта за допомогою сеттерів може призвести до помилок під час виконання програми. Це може бути неправильний порядок використання сеттерів або відсутність виклику.
- Відсутність побічних ефектів: при виклику методів немає неочевидних змін стану об'єкта.
- Відсутність NULL-посилань: об'єкти створюються в цілісному стані. Немає необхідності перевіряти на NULL під час подальшої роботи об'єкта.
- Потокобезпека: незмінні об'єкти завжди в передбачуваному стані і можуть бути використані паралельно в різних потоках.
- Менші та простіші об'єкти: незмінність допомагає зробити класи чистішими та коротшими. Важко зробити незмінний клас занадто великим.
Пишіть тести замість документації
Допоміжна інформація дуже важлива для супроводжуваності. Вона допомагає читачеві краще зрозуміти використання коду.
Якщо є необхідність писати документацію, це може бути ознакою погано спроектованого коду.
Погано спроектовані класи вимагають писати документацію для них. Відповідно, якщо клас спроектовано добре, документація не потрібна.
Краще писати прості зрозумілі класи, ніж писати під них документацію.
У unit-тесті можна подивитися:
- Варіанти використання класу.
- Дані, які подаються на вхід.
- Помилки, які можуть бути викликані в процесі роботи класу.
Використовуйте fаkе-об'єкти замість mосk-об'єктів
Мокінг ускладнює супровід тестів, оскільки перетворює припущення на факти. Моки розкривають внутрішню логіку класу. Класи перестають бути чорними скриньками під час тестування.
Фейкові класи покращують супроводжуваність тестів. Вони допомагають краще продумати і спроектувати інтерфейс класу.
Робіть максимум 5 публічних методів у класі
Плюси невеликих класів:
- Менша ймовірність зробити помилку: легше узгодити невелику кількість методів.
- Краще супроводжуваність: менше коду, менше методів, легше розуміти і підтримувати.
- Простіше досягти цілісності класу - зв'язку методів класу з усіма його властивостями.
- Простіше тестувати: менше точок входу і простіше відтворити всі сценарії використання.
Не використовуйте статичні методи
Статичні методи більше належать до процедурного стилю програмування, а не ООП. Вони ускладнюють підтримку і тестування.
Також не варто використовувати класи-утиліти. Це не повноцінний об'єкт, а набір процедур.
Не допускайте аргументів із значенням NULL
Дозволяючи передачу NULL
як аргумент, потрібно буде додавати перевірку на NULL
у методі. Для того, щоб обробити такий варіант виклику методу. Це ускладнює логіку та знижує супроводжуваність.
Якщо користувач методу все-таки передав NULL
, краще ігнорувати таке використання та не робити додаткову перевірку. Зрештою, це все одно призведе до помилки NullPointerException
і користувачеві потрібно буде виправити помилку.
Не використовуйте гетери та сетери
Повноцінні об'єкти в ООП це не прості структури даних.
Структура даних - це набір даних, прозорий ящик. Ми безпосередньо спілкуємося з її даними. Структура даних пасивна.
Об'єкт - це чорний ящик зі своєю логікою роботи та інкапсульованими даними.
Отже, геттери і сеттери в об'єктах порушують його цілісність і розкривають його деталі. При цьому інкапсуляція не використовується повноцінно. Такі об'єкти стають подібними до структур даних.
Не використовуйте оператор new поза вторинними конструкторами
Не слід створювати в одному класі об'єкт іншого класу. Це пов'язує два класи жорстким зв'язком. Виникають труднощі при супроводі та тестуванні. Не можна підмінити клас створюваного об'єкта без зміни коду.
Краще використовувати ін'єкцію (впровадження) залежностей з використанням інтерфейсів.
У вторинних конструкторах можна використовувати new()
для створення об'єктів свого класу.
Уникайте інтроспекції та приведення типів
Йдеться про оператор instanceof
та метод Class.cast()
у Java або їх аналоги іншими мовами.
Приведення типів і перевірки на тип негативно позначаються на розумінні та супроводжуваності коду.
Це дискримінація об'єктів за типом. Залежно від типу об'єкта ми використовуємо його по-різному.
Використовуючи інтроспекцію і наведення типів, ми висловлюємо свої очікування про об'єкт без їх документування. Це стає непрозорими прихованими відносинами, які впливають на супроводжуваність.
Ніколи не повертайте NULL
Повернення NULL
вносить недовіру до класу. Це змушує користувачів класу перевіряти ще раз і обробляти результат його роботи.
Повернення NULL
- це спроба безпечної відмови, що призводить до погіршення супроводжуваності.
Є три варіанти вирішення:
- Викидати виняток для швидкої відмови (fail fast). Це допомагає клієнту швидше побачити й обробити помилку.
- Повертати колекцію об'єктів. Це допомагає обробити варіант із порожнім результатом пошуку об'єкта.
- Використовувати патерн «Порожній об'єкт» («Null object»), якщо це підходить під ситуацію. Такий об'єкт схожий на справжній, але він поводиться інакше.
Кидайте лише виключення, що перевіряються.
Перевірені винятки Java додає контракт і покращує документування коду. Краще використовувати їх замість неперевірених.
Ще кілька порад:
- Не ловити винятки без необхідності: обробку винятків краще проводити на більш верхніх рівнях програми
- Будуйте ланцюжки винятків: ловимо виняток на нижньому рівні і викидаємо нове з додатковою інформацією і словленим винятком у ланцюжку. Так поліпшується контекст помилки.
- Відновлюйтеся один раз: відновлення і повернення користувачеві помилки повинні бути на найвищому рівні програми.
Робіть класи або константними (final), або абстрактними
У разі звичайного успадкування класів можуть виникати проблеми із супроводжуваністю, тому що відносини між класами можуть стати заплутаними. Методи можуть бути перевизначені на різних рівнях успадкування. При цьому зростає складність і стає важче читати і розуміти код.
Краще використовувати константні (final) класи або абстрактні батьківські класи. Це стосується як самого класу, так і до методів.
Такий підхід дає змогу чітко вказувати наміри та запобігає можливості невдалого використання успадкування:
- final - чорний ящик для його користувачів. Його можна тільки інкапсулювати.
- abstract - незавершений прозорий ящик, напівфабрикат. У нього є відсутні компоненти, які потрібно зробити.
Підсумок
У книзі багато корисних і практичних порад із прикладами. З деякими з них можна не погоджуватися або їх буде важко застосувати у вже наявних проєктах. Але поступове їх прийняття та застосування призведе до поліпшення ваших навичок і супроводжуваності ваших проєктів, у яких використовується ООП.
Корисні посилання
Сайт книги Elegant Objects:
Сайт автора книги Єгора Бугаєнко:
Дякую, що прочитали. Буду вам дуже вдячний, якщо поділитесь цією статтею!
Опубліковано