К основному содержимому
к выпуску
Конспект выпуска2024CTO

A Philosophy of Software Design: глубокие модули прячут сложность

Двадцать первый выпуск Code of Leadership: Александр разбирает A Philosophy of Software Design с гостем — Гришей Скобелевым, который руководит платформенной разработкой в мексиканском финтех-стартапе и ведёт подкаст «Между скобок». За час — первые семь глав, спор про дублирование кода, модуль Legacy и истории из практики обоих.

Code of Leadership · выпуск №217 минут

Конспект сокращён относительно полного выпуска; примеры и оценки передают опыт и позиции участников.

Основная линия материала
01

Гость, цитата Хоара и виды сложности

Гость сразу делает дисклеймер: книгу он ещё не дочитал и вернётся к ней в более расслабленном режиме, поэтому драйвером разбора будет Александр, а он сам делится опытом и мыслями. Книга его заинтриговала, и он ставит её рядом с Designing Data-Intensive Applications Мартина Клеппмана: обе читаются с любого уровня — и мидл, и сеньор, и лид найдут в них новое. Пересечений с Clean Code Анкла Боба много, но эта, по словам Гриши, «более лидовская»: про верхнеуровневый дизайн систем и про то, как вести техпроцесс внутри команды. Александр пришёл к ней года два-три назад, услышав у Влада Хононова рассказ про микросервисы и DDD с идеей глубоких и мелких модулей. Автор — Джон Оустерхаут, преподаватель Стэнфорда и один из создателей алгоритма консенсуса, задуманного как более понятная замена Paxos. В книге введение и 22 главы, всего 190 страниц; перевода на русский Гриша не знает.

Книга выросла из курса: Оустерхаут ставил студентам проектные задачи, они за семестр делали несколько проектов в командах, а он давал обратную связь. Курс не масштабируется — нужен человек, который читает работы, — а книга работает как бродкаст идей, достойных распространения. В отличие от Анкла Боба, у которого многие тезисы превратились в догмы и часть уже не работает, советы здесь практичнее: обсуждая Go, автор напоминает, что в Риме надо жить как римлянин и подстраиваться под существующий контекст. Первая глава объясняет замысел: сначала понять, какая бывает сложность, потом решить, что с ней делать. Оттуда же цитата Тони Хоара про два метода проектирования — сделать программу настолько простой, чтобы было очевидно, что ошибок нет, или настолько сложной, чтобы очевидных ошибок не было. Вторая глава раскладывает сложность на симптомы: амплификация изменений, когнитивная нагрузка и неизвестное неизвестное.

02

Дублирование, модуль Legacy и тех долг

Причины у этих симптомов две: избыточные зависимости и неясность. Неясность — это когда нужная информация недоступна и ментальная модель не собирается: тебе говорят «ты сделал не так», а ты спрашиваешь, откуда должен был узнать про негласное правило, которое передаётся из уст в уста внутри круга избранных. Автор лечит это комментариями, Гриша — конвенциями: практику девелопер-буков он унёс из Яндекса и завёл в следующих компаниях, и описанные процессы вроде «работаешь с Kafka — делай так» ускоряют онбординг и делают безболезненными ротации между командами. Отдельный спор — про дублирование: разработчики его не любят, но вынесенная в библиотеку общая часть становится модулем, от которого зависят все, причин для изменений у неё много и тянут они в разные стороны, а у команд появляется соблазн форкнуть её под себя. По памяти Александра, автор разрешает продублировать небольшой кусок, если видно, что развиваться он будет в другую сторону.

Гриша добавляет: относитесь к модулю как к API и всегда понимайте, обратно совместимо ваше изменение или нет. У них библиотеки обновляют чуть ли не каждый месяц, поднимая всё на версию выше, и, наткнувшись на несовместимое изменение, команда завела отдельный модуль с именем Legacy: туда складываются старые сабмодули, имя мозолит глаза, а в бэклоге появляется задача когда-нибудь от него избавиться. Рабочего кода недостаточно и по другой причине: в тактическом режиме сначала кажется, что ты молодец, а потом прирост становится всё меньше, пока не доходишь до состояния технического банкрота, когда проще закрыть версию и делать вторую. Бизнесу быстро нужно часто — в стартапе Гриши гипотезы в мобильной разработке делали максимально быстро, но такой код потом не оставляли, а полностью переписывали. Александр приводит свежую историю производителя аудиосистем, который к выпуску новой линейки пересобрал мобильное приложение: внутри предупреждали, что двадцать лет не вкладывались в обновление, тревожные сигналы проигнорировали ради срока и инвесторов, а после релиза получили баги, недоступную на части устройств функциональность и внутренний аудит. Рецепт Гриши — числовой порог: доля спринта под тех долг растёт с 20 до 40 процентов.

03

Глубокие модули и течь абстракций

Модуль — это интерфейс и реализация, и узкий консистентный интерфейс ограничивает причины изменений. Глубокий модуль прячет сложность: обычному инженеру не обязательно знать, как устроено объектное хранилище вроде S3, если ему обещают SLA на объём и скорость. Обратный пример из книги — интеграция через общую базу: интерфейс здесь всё, что позволяет база, реализация не скрыта, и модуля фактически нет. Интерфейс стоит делать минимально необходимым — Гриша приводит свой outbox, где интерфейс отвечает за крон-задачи, а работа с локами и записями спрятана внутри, — но участники оговариваются, что из россыпи мелких функций сборку целого приходится держать в голове. Общие модули глубже специализированных: автор вывел это из студенческих проектов (Александру помнится, что это был простой текстовый редактор), а у Гриши общее ядро трейсинга вынесено в отдельный модуль, от которого зависят реализации для Spring и Micronaut.

Скрытие информации ломается, когда абстракция течёт. Александр вспоминает раннюю Mongo, которая обещала скорость, но добивалась её тем, что не синхронизировала запись на диск и жила без транзакций, — данные терялись; Гриша подтверждает, что недавно столкнулся с транзакциями, которые работают не так, как ждёшь. Его пример утечки — эндпоинт, перед вызовом которого надо знать цепочку предыдущих; лечат конечным автоматом, отдающим внятную ошибку. У Александра пример злее: над коробочными системами надстраивают общий API — саркофаг, как над Чернобылем, — с кэшами, которые греются, когда мобильное приложение заходит на главную; пользователь, пришедший из почты сразу в нужный метод, кэши не греет — и запросы долбят внутренние системы. В главе про уровни абстракции метод, который только пробрасывает вызов, — красный флаг, но Гриша спорит: пустой на вид сервис заказов, который только зовёт репозиторий, со временем обрастает нотификациями и атомарностью. Александр в ответ вспоминает, как после сертификации напихал паттерны банды четырёх куда надо и не надо. Семь глав за час — и договорённость встретиться снова, начиная с восьмой.

Выводы

Что стоит унести с собой

  1. 01Общий код не обязан жить в библиотеке: причин для изменений у общего модуля много и тянут они в разные стороны, поэтому, по памяти Александра, автор разрешает продублировать небольшой кусок, который разойдётся по смыслу.
  2. 02Тех долг проще держать в числах: отдельный модуль Legacy с задачей в бэклоге, числовая приоритизация и порог, после которого доля спринта под долг растёт с 20 до 40 процентов.
  3. 03Глубина модуля измеряется тем, сколько он прячет: объектное хранилище с обещанным SLA — глубокий модуль, а интеграция через общую базу не оставляет интерфейса вовсе.
  4. 04Метод, который только пробрасывает вызов, — красный флаг уровней абстракции, но пустой на вид сервис-обёртка часто становится местом для нотификаций и атомарности, и вводить его позже дороже.

Источники