Дели на модули
Modules is files?
С первых шагов в программировании мне говорили: “Дели на модули программу”. И я думал, да, логично. и разделял на несколько большие файлы, постепенно пришел к тому, что 1 класс - 1 файл. Но почему, мы говорим “на модули”, а не “на файлы”? Что кроется за этим словом?
Ответ простой - деление на файлы, это не деление на модули. Модуль - это штука, чуть шире класса, но так же должна следовать SRP принципу.
Модули призваны решить проблему ростущей сложности, когда у нас слишком много концепций встречается в 1 месте. Тут можно возразить: так KISS, YAGNI - и будет просто. Не всегда - дело в том, что не все предметные области простые. Если же делится - то наверно и модули не нужны.
Просто это когда используешься принцип DRY, делаешь на 1 логическую сущность 1 класс и вот они - проблемы.
Refactoring
Его очень тяжело рефачить, потому что класс используется практически везде. Удалил поле - будь готов долго и нужно переделывать логику везде, где оно использовалось.
Я не говорю насчет переименовывания, а о смещении логики с одной сущности на другую. Была у вас “задача”, имела она свойство “приоритет” и тут вы поняли что нужны проекты, в которые эти задачи будут входить и, внезапно, приоритет нужен именно на проектах, т.к. ресурсы из общего pool выдаются именно на проект, а не на задачу. Вот в таких ситуациях нужно прилично перелопатить, и если проекты будут в контексте мест, где использовалась задача - это еще просто.
Можно ли сделать рефакторинг постепенно - врятли. Да, можно сказать: “Ок, пускай в БД лежит, вот тут мы перестанем использовать, а вот тут продолжим”. Но в коде это поле останется, в БД останется. Использование этого поля сходит на нет, но медленно. Такой рефакторинг лучше делать быстро, не растягивать на десятки спринтов, потому что код живет и меняется, и как результат можно получить класс в котором 2 десятка полей, половина в процессе рефакторинга, и только пара используется по настоящему. К тому же бизнес не всегда готов ждать и тогда могут появляться костыли, типа “берем приоритет проекта и ставим его задаче”.
Большей неопределенноси добавляют поля, которые по своей сути могут быть пустыми: описание, связи - вроде их нет, а могут ли они тут быть, можно ли их использовать - не понятно.
Serialization
Сериализация в широком смысле (в/из JSON/БД/etc.) тоже сделана по DRY, но она
становится очень плотно загруженной параметрами, а в крайних случаях можно
получить метод get
который принимает список полей, которые мы хотим вытащить.
В таких случаях можно подумать “а чем мне свой DSL вместо SQL?”.
Это происходит по причинам оптимизаций: не везде нужна вся информация и в случаях когда какое-то поле весит пару мегабайт, то тянуть его для отображения списка сущностей приведет к долгим прогрузкам таких страниц - web, не веб - значения не имеет.
Но такое развитие в части сериализации из хранилища ведет к следущей проблеме: где бы то ни было, встречая этот класс, мы не можем только глядя на него сказать какие поля у него есть в БД, а каких нет.
False Duplication
И того, получается следуем принципу DRY, вроде должна быть поддержка проще, но все не так просто и мы получили неопределенность: наша сущность принимает разные облики в разных процессах/контекстах.
Иногда, встречая похожие куски кода, мы обобщаем их с лозунгом “DRY!”, не особо задумываясь. Позднее, приходится начинать добавлять флаги для каждой отдельной “развилки” которые мы втиснули в общую канву. Это случай, когда мы объединили не настоящее дублирование, или другими словами - временное. Оно поначалу выглядит действительно как одно и тоже, но проблема в том, что это не так: дальше код будет различаться, и расходиться. Такое хорошо запоминается потому что встречается часто. Со структурами данных такое тоже может происходить, но видно немного меньше.
Тут может показаться, что как я “по системе огурца” ушел от начальной мысли - не совсем (хотя Остапа все же понесло) - это связанные темы и я хочу как раз сделать акцент на структурах данных в вопросе модулей, по той причине, что модули как раз и решаеют проблему ложного дублирования.
DDD - Sucrifice DRY
В случае с обманчивым дублированием, решением является антогонист DRY - “разделяй и влавствуй”, и модули тут как тут.
Модули хочется связать с DDD, а точнее с подобластями или контекстами. С чем именно - зависит от ситуации.
Модули - как я это понимаю - своего рода “контекст”, в рамках которого какая-то сущность выглядит особым образом. В другом контексте-модуле - она выглядит иначе. Это все еще она, таже сущность (DDD Entity, с тем же id), но имеет другое представление.
Конечно, тут начинает скрепеть DRY, потому что на 1 сущность мы плодим по несколько классов. К этим классам нужно добавлять отдельные методы сериализации, но они становятся проще: часть if’ок теперь выражается названием метода, и каждый такой метод составляет сущность без опций и весьма прямолинейно. Конечно все такие методы могут ходить в 1 таблицу БД, просто будут брать от туда только то, что нужно.
Так же, если мы друг поймем, что пресловутой задаче приоритет не к лицу, а вот на проекте он смотрится как влитой, то сможем переносить это частями, в каждом кодуле по отдельности.
Чистая архитектура, луковичная - объясняют как делить в “вертикальной” плоскости: бизнес-логика где-то внизу и вообще - библиотека, а инфраструктура - это поплавок на верхушке, который как плагин, встраивается в библиотеку бизнеса.
Модули - это создание таких же границ в коде, но в “горизонтальной” плоскости.