Анкл Боб не всегда прав

April 28, 2021

Следить за обновлениями блога можно в канале "Эргономичный код" в Telegram и Max.

Свежий пост Анкл Боба

Прочитал свежий пост Анкл Боба и я с ним категорически не согласен.

Краткое содержание поста:

  1. Где-то в интернете спросили как зарефакторить код:

    srp ocp conflict 197cb
  2. Анкл Боб сказал, что правильно инкапсулировать этот иф в фабрику:

    srp ocp conflict f5d0e

И далее он пишет:

Every business rule that would once have depended on an if/else/switch statement now has its own particular method to call in the base class

— Uncle Bob, https://blog.cleancoder.com/uncle-bob/2021/03/06/ifElseSwitch.html

В общем случае, это решение может быть хорошим дизайном или хотя бы разумным компромиссом. Но в изначальном примере он не является ни хорошим дизайном, ни разумным компромиссом.

С этим дизайном в контексте изначальной задачи с полами я вижу две основные проблемы:

  1. Он повлечёт за собой нарушение SRP;
  2. Он повлечёт за собой регрессии.

Давайте разбираться и начнём с SRP

Божественные объекты

Я с лёгкостью могу себе представить, что в одной программе в зависимости от пола надо:

  1. Сгенерировать аватар в UI;
  2. заложить дополнительный бюджет, на случай декрета женщины.

Оба этих метода придётся добавить в класс Gender. И это будет нарушением SRP в любой его интерпретации.

Если вы дочитали статью до конца, то там в последнем предложении упоминается Acyclic Visitor Pattern, для решения проблемы перекомпиляции клиентов Gender-а. Он же может помочь и с нарушеним SRP - в этом случае на каждое правило надо будет завести по интерфейсу и три его реализации. А если появится новый пол, то по новой реализации каждого интерфейса.

Тут я снова могу только спекулировать, но судя по тому, что Анкл Боб собирается расширять базовый класс, речь идёт о неопубликованном классе.

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

Квантовая запутанность

Вторая проблема вызвана тем, что связанная (cohesive) логика раскиданна в пространстве. Как минимум по разным классам. А может быть и разным пакетам и модулям - Анкл Боб ничего не говорит об ограничении иерархии полов.

При том критикуя вариант с ифами они пишет:

The fact that they are replicated in many places is problematic because when such statements are inevitably changed, it is easy to miss some. This leads to fragile systems.

— Uncle Bob

Но предлагает буквально тоже самое, только в другой плоскости!

Отсюда же и проблемы с анализом кода - разбираясь, как работает генерация аватара, придётся постоянно скакать между тремя файлами.

А как по другому

Лет пятнадцать назад (и с теми же мозгами), я бы не сомневаясь ответил: я тут предвижу больше количество новых операций, и никаких новых вариантов - естественно тут надо использовать алгебраические типы данных.

Сегодня я бы посомневался, но всё равно выбрал бы АДТ - появление нового пола я представить могу, но пока только теоретически. А вот новые операции будут точно.

В современных языках (и даже Java, почти), в случае АДТ компиляторы дают такую же безопасность на этапе компиляции, как и в случае интерфейсов. При добавлении нового типа программа не скомпилируется, пока все ифы/свичи не обработают его.

Касательно направления зависимостей, ничего не мешает HighLevel-у зависеть не от конкретной функции, а от абстрактной функции:

class UI(private val generateAvatar: (Gender) -> Image)

если очень хочется, её можно обернуть в IAvatarGenerator и реализовать в AvatarGeneratorImpl.

Но самое главное, что в этом случае, метод генерации аватра останется в UI-слое, а метод расчёта бюджета - в слое сервисов. И их код будет помещаться на одном экране.