Прокидывание методов и переменных
Седьмая глава начинается с красного флага: разные уровни должны иметь разную абстракцию, а если она совпала, разделение на модули выбрано неправильно. Классический пример слоёв — файловая система: внизу блоки и иноды, наверху пара методов для доступа к файлам вроде open; с сетевыми протоколами история похожая. Дальше — методы в стиле pass-through: модуль повторяет сигнатуру и просто передаёт управление ниже, своей логики в нём нет, и класс от этого становится мелким. На картинке в книге это вариант «a» со звёздочками, кружочками и пятиугольниками, а варианты b, c и d показывают лечение: ходить в нижние модули напрямую, перераспределить между ними логику или объединить их в один. Автор не раз повторяет, что большой модуль — не беда, если он прячет сложность за простым интерфейсом. Участники цепляются за перевод: shallow как «мелкий» тянет за собой размер, хотя речь о глубине — о том, что находится под поверхностью.
Не всякое повторение сигнатуры плохо: у диспетчера есть собственная логика — он выбирает реализацию и уводит вызов ниже. А вот декораторами, предупреждает автор, злоупотребляют, и он даёт набор вопросов для самопроверки: нельзя ли положить эту логику напрямую, реализовать её в вызывающем коде или вынести в отдельный самостоятельный класс. Участникам нравится, что вместо правила автор даёт способ рассуждать; сценарии для декораторов остаются — внешний класс, который нельзя изменить, или трансляция интерфейса. Отдельная мысль: абстракция интерфейса должна отличаться от реализации, иначе реализация протекает наружу и модуль перестаёт быть глубоким. Пример — текстовый редактор, где студенты автора делали построчный API вместо посимвольного, потому что так проще хранить текст внутри, хотя вставки в редакторе произвольные.
Сложность вниз и цена распила
Конец седьмой главы — про переменные, которые прокидывают через все слои: IDE с готовностью подставит параметр во всю цепочку вызовов, и на это очень легко сесть. Идеального решения автор не знает: общий объект-контейнер, глобальная переменная или контекст, желательно неизменяемый, чтобы не ловить проблем с конкурентным доступом; контекст он считает лучшим из того, что видел на практике. Восьмая глава сводится к правилу опускать сложность вниз, потому что пользователей у модуля обычно больше, чем разработчиков. Наверх её выталкивают исключения и конфигурация, когда пользователь докручивает параметры, смысла которых может не понимать, а комбинации опций перемножаются так, что вряд ли все протестированы. Конфигурация и эксепшены — это тот самый неявный интерфейс: формальная сигнатура плюс недокументированный хвост. Обработчики к тому же почти не тестируются — отсюда цитата про то, что незапускавшийся код не работает.
Девятую главу — «вместе или порознь» — рассказывает Олег. Разделение само по себе добавляет сложности: резать метод по карго-культному правилу «не длиннее двадцати строк» значит плодить сущности; распилить монолит на микросервисы значит переселить сложность в эксплуатацию, а не убрать её; неразрывные куски кода дублируются, и менять их приходится в нескольких местах. Красный флаг границы — когда понять один модуль нельзя без чтения реализации другого. Автор предупреждает, что резать слона на маленьких слоников — идея так себе, и предлагает собирать разрезанное обратно. Участник вспоминает, как коллега переписывал систему, плохо разобравшись в домене, и нарезал микросервисы по числу основных моделей; потом понадобились транзакции между ними, он ушёл из компании, а команда слепила десяток сервисов обратно в три по понятным доменам. Объединять стоит, когда куски делят общую информацию, используются вместе, объединены понятием более высокого уровня или непонятны по отдельности.
Анкл Боб, исключения и дизайн дважды
Отдельный сюжет главы — go to: обычно красный флаг, но в ядре Linux им, насколько помнят участники, переходят в секцию, освобождающую ресурсы, и автор советует ровно это — собрать одинаковые обработчики в одном месте. Запрет тянется ещё из фортрановских времён, и кто-то тут же скидывает в чат статью 1968 года: правило без объяснения повторяют не думая. В конце главы автор впервые прямо спорит с Анкл Бобом: альтернативный путь есть, но метрики там жёсткие и непонятные, а сложность не уменьшают. Участникам это нравится: автор излагает чужую позицию, потом свою и объясняет почему, — больше похоже на учёного, чем на продажу книжки. Десятая глава — про то, как определять ошибки так, чтобы их не существовало. Бросить исключение легко, а решать, что с ним делать, — уже когнитивная нагрузка: обработать здесь, отдать выше или прервать операцию, рискуя оставить систему в несогласованном состоянии; проверяемые исключения в Java заставляют клиентский код что-то делать на уровне API. Показательная пара примеров — удаление файла: в Unix удалил и пошёл отдыхать, в Windows дело может дойти до перезагрузки.
Автор вспоминает и собственный язык: сброс переменной кидал исключение, если её нет, — хотя звали сброс как раз чтобы почистить состояние, и ошибки приходилось глушить; поменяй он семантику на «сделать так, чтобы значения не было», операция оказалась бы уже выполненной. Олег добавляет маскировку сбоев: недошедшую часть пакета протокол пересылает заново, ретраи в разумных пределах решают сетевую ошибку на месте, а сетевая файловая система лучше подождёт, чем сразу отдаст ошибку, — нажать refresh клиент всё равно сможет только сам. Дальше агрегация: пустить исключения вверх и обработать в одном месте — формально это противоречит правилу не поднимать сложность наверх, зато сверху проще контролировать; при нехватке памяти автор советует просто упасть. Одиннадцатую главу про дизайн дважды один из ведущих принимает лично: в школе задачи решались с первого раза вплоть до олимпиад, а в университете без системного подхода ничего не выходило. Алгоритм: собрать альтернативы, выписать плюсы и минусы, при необходимости сложить комбинированный вариант, а если не помогло — отойти на шаг назад и искать снова, пока решение не станет достаточно хорошим; это продолжение совета тратить на дизайн 10% времени. Половина книги прочитана, через неделю — комментарии в коде и документация.
Что стоит унести с собой
- 01Одинаковая абстракция на разных уровнях — красный флаг: модуль, который только пересылает вызов дальше, ничего не скрывает; лечится это прямым вызовом нижнего модуля, перераспределением логики или объединением модулей.
- 02Идеального способа прокинуть переменную через слои нет: общий объект-контейнер, глобальная переменная или контекст — последний автор считает лучшим из известных ему решений и советует делать его неизменяемым.
- 03Распил не убирает сложность, а переносит её: микросервисы, нарезанные по числу моделей домена, потребовали транзакций между собой и вернулись к трём сервисам по понятным доменам.
- 04Лучшее исключение — то, которого нет: ретраи и ожидание вместо ошибки, агрегация обработки в одном месте, а при нехватке памяти — просто упасть и вложиться в быстрый старт.