DRY KISS SOLID YAGNI DDD
lang_of_article_differ
want_proper_trans
DRY
Don’t Repeat Yourself
Этот принцип заключается в том, что нужно избегать повторений одного и того же кода.
Как можно обнаружить проблему:
- Изменяя алгоритм или поведение, приходится менять код в нескольких местах;
- В проекте есть одинаковые части кода.
Как исправить проблему:
- Просканировать код средствами статического поиска дублирующего кода (например: PMD);
- Подобрать нужную абстракцию для дублируемого кода;
- Заменить весь дублирующий код на выбранную абстракцию.
KISS
Keep It Simple, Stupid
Смысл этого принципа программирования заключается в том, что стоит делать максимально простую и понятную архитектуру, применять шаблоны проектирования и не изобретать “велосипеды”. Когда ведётся разработка крупного проекта, часто приходится сталкиваться с избыточной сложностью реализации. Люди плохо справляются с нахождением решения комплексной задачи. Самое простое решение по уменьшению сложности – разделить задачу на мелкие независимые задачи, которые необходимо решать по отдельности. К примеру, частью любого ПО с пользовательской авторизацией, является компонент, отвечающий за управление пользователями. Этот компонент может быть разделен на другие компоненты, например, управление уровнями доступа, который сможет работать с другими компонентами системы. Разделяя таким образом систему на компоненты глубже и глубже, можно достигнуть того, что каждая часть системы будет отвечать за четко определенные действия. Такие действия можно организовать в отдельные блоки кода, каждый из которых будет решать только узкую задачу. Для решения декомпозиции комплексной задачи следует использовать принцип DDD (Domain-driven design).
Как обнаружить проблему:
- Если только что написанный код невозможно прочитать, бегло пробежав глазами, приходится останавливаться;
- Если блок кода содержит больше 50 строк;
- Если код необходимо пояснять комментариями.
Как исправить проблему:
- Код разбить на небольшие короткие блоки кода, которые выполняют одно простое действие;
- Сложный код разбить на группу простых методов/функций;
- Провести рефакторинг наименования, дать говорящие названия (методы и функции должны говорить, что они делают и в каком контексте, например: getUserByEmail вместо userEmail, переменные должны иметь понятные читаемы названия и не требовать комментариев, например: sortedUsersByLastVisit вместо users).
SOLID
Принцип SOLID в упрощенном варианте означает, что если при написании кода использовать несколько принципов вместе, то это значительно облегчает дальнейшую поддержку и развитие программы. Полностью акроним расшифровывается так:
-
Single responsibility principle — принцип единственной обязанности (на каждый отдельно выраженный код должна возложена одна-единственная обязанность).
Как обнаружить проблему:
- Если код выполняет несколько действий над сущностями;
- Если код выполняет действия над несколькими сущностями;
- Если код выполняет действие в нескольких контекстах;
- В коде присутствуют анти-паттерн проектирования: GodObject;
- В коде есть классы, имена которых содержат следующие названия: Manager, Super, Common;
- В имени классов есть слова And и/или Or;
- Требуется описания, что делает класс.
class User{
function String getName(){...}
function String getSurname(){...}
function String renderWidget(){...}
function void saveToCache(){...}
}
Как исправить проблему:
- Провести декомпозицию по принципу DDD (Domain-driven design): разделить всё на объекты, действия и контексты;
- Создать классы для получившегося разделения.
class User{
function String getName(){...}
function String getSurname(){...}
}
class UserWidget{
function void render(){...}
}
class CachedUser{
function constructor(User user){...}
}
-
Open/closed principle — принцип открытости/закрытости (код должен быть закрыта для изменения, но открыт для расширения).
Как обнаружить проблему:
- При написании новой бизнес-логики, требуется менять уже существующую;
- Добавление новой функциональности становится всё дороже и дороже - повышается complexity;
- Метод, функция или класс меняет своё поведение в зависимости от входных параметров.
public class User{
public String name;
public String surname;
}
user = new User();
user.lastVisit
/********************/
final class Guest{
function void setName(String name){...}
function String getName(){...}
}
final class Admin{
function void setName(String name){...}
function String getName(){...}
}
Как исправить проблему:
- Ввести абстракцию.
class AbstractUser{
final function void setName(String name){...}
final function String getName(){...}
}
class Guest extend AbstractUser{...}
class Admin extend AbstractUser{...}
-
Liskov substitution principle — принцип подстановки Барбары Лисков (функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом. Подклассы не могут замещать поведения базовых классов. Подтипы должны дополнять базовые типы).
Как обнаружить проблему:
- Код перестаёт работать ожидаемо, если вместо объекта в качестве аргумента отправить объект наследник.
class User{
function String getName(){
return this.name;
}
}
class Admin extend User{
function String getName(){
return "bicycle";
}
}
class UserWidget{
function constructor(User user){...}
function void render(){
return "<div>" + this.user.getName() + "</div>";
}
}
Как исправить проблему:
- Использовать на практике контрактное проектирование.
-
Interface segregation principle — принцип разделения интерфейса (много специализированных интерфейсов лучше, чем один универсальный).
Как обнаружить проблему:
- В коде присутствуют методы заглушки с return null или throw Exception;
- При реализации класса по интерфейсу необходимо создавать методы, которые в классе совершенно не нужны;
- Очень большой список методов в интерфейсе.
interface SecurityService{
function User login(String email, String password);
function void logout();
function boolean hasUserRole(User user, Role role)
function void addUserRole(User user, Role role)
function void delUserRole(User user, Role role)
}
Как исправить проблему:
- Определить ответственность и разделить один интерфейс на несколько;
- Использовать подход TDD (Test-Driven Development);
- Не использовать методы заглушки.
interface AuthenticationService{
function User login(String email, String password);
function void logout();
}
interface AuthorizationService{
function boolean hasUserRole(User user, Role role)
function void addUserRole(User user, Role role)
function void delUserRole(User user, Role role)
}
-
Dependency inversion principle — принцип инверсии зависимостей (зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций).
Как обнаружить проблему:
- Сложно тестировать код;
- Высокая связанность классов: при переносе куска кода в новый проект, он тянет за собой много ненужных сущностей.
class ReportSender{
private ReportBuilder reportBuilder = new ReportBuilder();
private EmailSender emailSender = new EmailSender();
function void send(String text){
Report report = reportBuilder.build(text);
emailSender.send(report);
}
}
Как исправить проблему:
- Использовать подход TDD (Test-Driven Development);
- Для всех ключевых сущностей сперва сделать абстракцию и работать с ней, а не с реализацией
class ReportSender{
private ReportBuilderInterface reportBuilder;
private EmailSenderInterface emailSender;
function constructor(ReportBuilderInterface reportBuilder, EmailSenderInterface emailSender){
this.reportBuilder = reportBuilder;
this.emailSender = emailSender;
}
function void send(String text){
Report report = reportBuilder.build(text);
emailSender.send(report);
}
}
YAGNI
You Ain’t Gonna Need It
Суть принципа заключается в том, что необходимо реализовать только поставленные задачи и отказаться от избыточной функциональности. К примеру, достаточно часто доступ к базе данных осуществляется через абстракцию, которая может иметь реализацию для разных драйверов – MySQL, PostgreSQL, Oracle. Если ведётся разработка серверного приложения, которое должно быть реализовано в конкретном технологическом стэке, вероятность, что клиент сменит тип базы данных, - стремится к нулю. Так как любой проект реализуется в рамках определённого бюджета и договора. Использования в этом случае ORM не имеет смысла, кроме того, когда так хочет разработчик. Любая добавленная библиотека добавляет проекту сложности. Библиотекой придется управлять: выполнять обновления, писать заплатки, чтобы исправлять найденные недостатки, вносить правки в безопасность; и все это придется делать в будущем на постоянной основе. При выборе того или иного решения нужно исходить из оценки эффективности разработки и сопровождения: если цена сопровождения увеличивается значительно при сокращении цены разработки или цена разработки превышает договорную, даже с учётом значительного сокращения цены сопровождения - это точно неправильное решение. Любое решение должно уменьшать стоимость разработки и сопровождения, в противном случае не должно применяться.
Как обнаружить проблему:
- Во время реализации приходится разрабатывать код, который напрямую не решает поставленную задачу
class User{
function void login(String email, String password){...}
function void jumpForFun(){...}
}
Как исправить проблему:
- Использовать подход TDD (Test-Driven Development);
class User{ function void login(String email, String password){...} }
Чем же весь зоопарк отличается?
DRY призывает упростить сложность, разделив систему на управляемые компоненты KISS призывает делать вещи как можно проще YAGNI призывает уменьшить сложность, уменьшив избыточность SOLID хорошо применим только для ООП языков
DDD
Domain-driven design
Это принцип, направленный на создание оптимальных систем. Всё сводится к описанию моделей, которые входят в бизнес-логику системы, которая устанавливает связь между реальными условиями применения системы и кодом. Фактически DDD - это набор правил, которые позволяют принимать правильные проектные решения. Данный подход позволяет значительно ускорить процесс проектирования программного обеспечения в незнакомой предметной области. Он особо полезен в тех случаях, когда разработчик не является специалистом в области разрабатываемой системы.
Терминология:
- Область (domain) — предметная область, к которой применяется разрабатываемое программное обеспечение;
- Модель (model) — описывает отдельные аспекты области;
- Язык описания — используется для единого стиля описания домена и модели.
Правила:
- Ограниченные связи - использование нескольких моделей на различных уровнях проекта. Данный подход используется для уменьшения различных связей между моделями, что исключает сложность и запутанность кода. Например: User и AuthedUser, что приводит к неясности, в каком контексте используется модель. Рекомендуется всегда определять контекст и границы использования.
- Целостность - определение минимальных границ модели. При участие большого числа разработчиков часто приводит к избыточному дроблению моделей, что приводит к потере целостности проекта. Рекомендуется постоянно проверять используемые модели на схожесть и принимать меры по объединению, в случае необходимости - это позволяет держать проект в одной концепции.
- Взаимосвязь - при работе над несколькими моделями в группе, разработчики могут не знать о сущностях других моделей, что усложняет процесс конечной общей сборки продукта. Рекомендуется на этапе проектирования точно обозначить, что именно выполняет каждая модель и как она взаимосвязана с другими. В конечном итоге должна получиться диаграмма взаимосвязей моделей.
Элементы:
- Контекст - определение границ применения модели. Например: покупатель и посетитель - человек в разных контекстах.
- Сущность - объекты, над которыми совершаются действия, которые применимы только в конкретном контексте. Каждая сущность уникальная по характеристикам и имеет свой жизненный цикл. Например: человек, покупка.
- Объект-значение - это свойства модели, применяются для описания сущностей, они не имеют жизненного цикла. Например: в магазине 2 человека, где число людей - это описание модели, но не относится к конкретной сущности.
- Сущность-агрегатор - сущность, которая объединяет/группирует другие сущности, чтобы снизить высокую взаимосвязанность при проектировании, фактически все внешние сущности связаны с группированными сущностями только чере сущность агрегатор. Например: управляя автомобилем (сущностью-агрегатором), водитель ничего не знает о скорости вращения колёс или ремней силового агрегата.
- Событие - событие, которое происходит в рамках проектируемой модели. Например: запуск двигателя автомобиля водителем, где старт - событие в модели.
- Сервис - объект модели, который не может быть выделен в сущность, но выполняет какое-то действие. Например: система контроля блокировки колёс, в модели, которая описывает управление автомобилем - это сервис, а не сущность.