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

April 28, 2021

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

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

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

  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-слое, а метод расчёта бюджета - в слое сервисов. И их код будет помещаться на одном экране.