В данной статье хочу поделиться своими заметками на книгу Егора Бугаенко "Элегантные объекты. 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:
Сайт автора книги Егора Бугаенко:
Спасибо, что прочитали. Буду вам очень признателен если поделитесь этой статьей!
Опубликовано