В прошлом посте была история про кодовую базу на Go, где код собирался и тесты проходили, но никто не мог объяснить, зачем половина всего этого существует. Такой долг это не грязный код, а решения, чья причина потерялась. Здесь разберём, что с ним делать и как писать, чтобы не плодить его самому.
Что автор сделал с унаследованным кодом
Он не стал чистить всё подряд, потому что 47 000 строк за пару недель не разберёшь параллельно с обычными задачами. Подход был такой.
• Middleware с мёртвым значением. Удалил сразу. ID запроса протаскивался сквозь четыре слоя ради библиотеки трейсинга, которой давно нет. Правка маленькая и однозначная, поведение не меняется.
• Несогласованность
transport. Задокументировал, не трогал. Чтобы починить, нужно понять исходные настройки ретраев и актуальны ли они. Вместо правки он написал в пакете подробный комментарий. Что это, когда сделано, под что настроено, какой вопрос открыт. • Избыточность
sync2. Убрал частично. Четыре из семи мест с Pool спокойно заменялись на sync.Pool. Три остальных имели тонкие отличия, в которых он не был уверен. Убрал те, где был уверен, остальные пометил. • Разделение
cache и Redis. Оставил как есть. Самый запутанный кусок с наименьшим пониманием. Менять поведение, не зная, зачем оно, слишком рискованно.Это честный подход к чужому долгу. Чините то, в чём уверены. Документируйте то, в чём нет. И не поддавайтесь желанию вычистить всё, потому что чистка без понимания создаёт новый долг быстрее, чем гасит старый.
Пишите комментарии про «почему», а не про «что»
Go читается достаточно, чтобы понять, что делает код. Ценность в комментарии, который объясняет, почему выбран этот путь, на какое ограничение он отвечает и что должно измениться, чтобы подход перестал быть нужным.
Вот как выглядел бы тот самый legacy-фолбэк, если бы автор оставил после себя контекст:
// fetchWithLegacyFallback существует, потому что основной эндпоинт
// был нестабилен во время миграции БД в 2021 году.
// Legacy-эндпоинт только на чтение и медленнее, но стабилен.
//
// Статус (2023-06). Миграция завершена, но снятие фолбэка требует
// проверки, что стабильность основного эндпоинта закрепилась
// на новой инфраструктуре. Отслеживается в issue #1847.
func (c *Client) fetchWithLegacyFallback(ctx context.Context, req Request) (*Response, error) {
Такой комментарий пишется три минуты и экономит полчаса тому, кто откроет функцию следующим.
Ставьте дату у технических решений
Комментарий
// TODO: remove when migration complete почти бесполезен, если не знать, когда он написан. Вариант // TODO (2021-03): убрать после миграции, tracked in #1204 говорит, когда решение принято и где искать контекст. Через два года вы просто проверяете, закрыт ли issue #1204.Трение на онбординге это сигнал долга
Если новому инженеру нужно больше дня, чтобы разобраться в пакете или границе системы, это трение несёт информацию. Код, в который тяжелее всего въехать, обычно и есть тот, где накопилось больше всего необъяснённых решений.
Убирайте абстракции, которые не можете объяснить
Если вы не можете в одном-двух предложениях сказать, зачем существует абстракция и какую проблему она решает, это пассив. Либо разберитесь и задокументируйте, либо удалите. Абстракция без объяснения это цена, которую платят при каждом следующем контакте с кодом.
Самое интересное в той истории было не то, что нашлась необъяснимая часть. А то, что разбор этой части показал историю системы. Где она стабильна, где её латали много раз, какие компромиссы в архитектуре были сделаны осознанно, а какие просто накопились. Чистая кодовая база такого знания не даёт. Археология медленная, но уроки складываются.
➡️ Оригинал
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction