Найти в Дзене
ZDG

SOLID-ный код для солидных господ

Оглавление

Пришло время поговорить о солидном коде :) Но сначала хочу сделать небольшое отступление. Когда программист более-менее осваивается в собственно программировании и может реализовать в принципе любой проект, далее начинается нечто вроде коллекционирования аббревиатур, посвящённых разнообразным "лучшим практикам". Например, KISS, DRY, YAGNI и прочее. Знание каждой из них повышает ЧСВ программиста. Все они направлены на то, чтобы разрабатывать код ещё лучше и ещё быстрее, но зачастую превращаются просто в самоцель и слепое поклонение невесть откуда вылезшим "авторитетам". Но про это я ещё напишу, а мы начнём.

Что такое SOLID

Это аббревиатура, где каждая буква обозначает один из принципов создания программного кода:

  • Single Responsibility
  • Open–Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Я начну с первой буквы S.

Single Responsibility

Что переводится как Единственная Ответственность. Собственно, на этом можно и закончить, так как объяснение банально: любая сущность в программе, будь то модуль, процедура или класс, должны выполнять ровно одну задачу и не отвечать ни за что другое.

Звучит логично? Конечно.

Например, вот функция вычисления квадрата числа:

function sqr(x) { return x*x; }

У неё единственная ответственность? Да. Придёт ли нам в голову нагружать эту функцию ещё какой-то ответственностью? Нет. То есть принцип реализуется сам собой, для этого не надо напрягаться, и не надо даже его знать.

Но в других примерах мы можем и попасть в затруднение. Например, мы сделали класс User, у которого есть метод login(). Данный класс владеет информацией о юзере, такой как логин, пароль, но также дата рождения и... например деньги на счету. Соответственно, у класса User может быть ещё пара методов: isBirthdayTomorrow() и addMoney(). Является ли это нарушением принципа единственной ответственности?

И здесь правда зависит от того, где именно мы проводим разделительную линию.

Например, должно быть очевидно, что операция логина пользователя и операция, связанная с его деньгами – разные зоны ответственности. Следовательно, было бы целесообразно сделать новый класс UserMoney, в котором вести денежные операции.

А вот isBirthdayTomorrow() теоретически можно оставить... или нет, но давайте сначала рассмотрим, как он устроен внутри.

Допустим, чтобы узнать, завтра ли ДР у юзера, этот метод получит текущую дату и прибавит к ней один день. А у текущей даты есть часовой пояс... А ещё високосные годы и летнее/зимнее время... Получается, что данный метод должен иметь функции полноценного календаря и откуда-то брать текущие системные настройки часового пояса. Но это опять нарушение принципа.

Значит, вместо isBirthdayTomorrow() нужно сделать метод попроще: getBirthday(), который просто вернёт дату рождения юзера, а проверять её будет кто-то другой. Либо сделать отдельный класс Calendar, который заранее настроить и передать в метод isBirthdayTomorrow(), чтобы тот просто им воспользовался. В данном случае получение корректной текущей даты возложено на Calendar, у которого есть единственная ответственность.

Как вариант, можно сделать вообще отдельный проверяльщик дней рождений BirthdayChecker, с методом isBirthdayTomorrow(), в который передавать дату рождения юзера.

На приведённом примере, хоть он и довольно вырожденный, можно видеть, что по мере роста сложности приложения делить ответственность будет всё труднее. Здесь мы попадаем в ловушку придуманных нами же смыслов. А можно ли считать, что день рождения принадлежит классу User? А его имя? А емейл?

В конце концов, задурманив себе голову, мы можем выдать какой-то маразм вроде отдельных классов UserLogin, UserName, UserEmail и т.д. Ну а что? Зато у каждого единственная ответственность, ведь это правильно!

Нет, неправильно. Вы должны чувствовать ту границу, за которой начинается маразм и тупое следование принципу ради принципа.

Но конечно не стоит и пренебрегать. Если вы (правильно) используете его с самого начала, он позволит вам построить не только архитектуру приложения, но и структурировать данные в базе, например.

Но давайте предположим, что вы о нём ничего не знали. Тем не менее в своих программах вы можете (надеюсь) обнаружить, что естественным образом разделяли сложные вещи на простые, то есть занимались декомпозицией. А это и есть одно из проявлений принципа единственной ответственности.

В принципе, естественность это главное условие. Как только вы начинаете чересчур задумываться над формальностями и мудрить, то приходите к маразму и неверным решениям.

Остальные буквы – в следующих выпусках!

Читайте дальше: