Пришло время поговорить о солидном коде :) Но сначала хочу сделать небольшое отступление. Когда программист более-менее осваивается в собственно программировании и может реализовать в принципе любой проект, далее начинается нечто вроде коллекционирования аббревиатур, посвящённых разнообразным "лучшим практикам". Например, 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 и т.д. Ну а что? Зато у каждого единственная ответственность, ведь это правильно!
Нет, неправильно. Вы должны чувствовать ту границу, за которой начинается маразм и тупое следование принципу ради принципа.
Но конечно не стоит и пренебрегать. Если вы (правильно) используете его с самого начала, он позволит вам построить не только архитектуру приложения, но и структурировать данные в базе, например.
Но давайте предположим, что вы о нём ничего не знали. Тем не менее в своих программах вы можете (надеюсь) обнаружить, что естественным образом разделяли сложные вещи на простые, то есть занимались декомпозицией. А это и есть одно из проявлений принципа единственной ответственности.
В принципе, естественность это главное условие. Как только вы начинаете чересчур задумываться над формальностями и мудрить, то приходите к маразму и неверным решениям.
Остальные буквы – в следующих выпусках!
Читайте дальше: