Когда документации нет: исходники, история, трекер
Три ситуации, в которых предыдущая глава не помогает:
- Внутренний сервис вашей компании. Есть README на четыре строки, написанный при создании репозитория, и человек, который его писал, уволился.
- Заброшенная, но незаменимая библиотека. Последний релиз три года назад, документация — сгенерированный список методов без описаний.
- Недокументированное поведение живого проекта. Документация есть и хорошая, но про интересующий вас случай в ней ничего нет.
Во всех трёх случаях источник остаётся один: сам проект. Его код, его история и его обсуждения. Хорошая новость в том, что этот источник полнее любой документации: в нём записано всё, что происходило, включая то, что авторы не собирались рассказывать.
Порядок раскопок
Копать надо не как попало, а по возрастанию стоимости. Каждый уровень отвечает на свой вопрос и часто закрывает вопрос целиком.
недокументированной системы"] --> B["1. README, examples/, doc-комментарии
2 минуты"] B --> C["2. Тесты по интересующему API
10 минут — отвечают на «как использовать»"] C --> D["3. Публичная поверхность:
сигнатуры, типы, константы, ошибки"] D --> E["4. Реализация конкретной функции
отвечает на «что происходит»"] E --> F["5. История: когда и каким коммитом
это стало таким"] F --> G["6. Обсуждение вокруг коммита:
PR, issue, рассылка — отвечает на «почему»"] G --> H["7. Люди: автор, мейнтейнер,
коллега, который это писал"] C -.->|"вопрос закрыт"| Z["Ответ"] E -.->|"вопрос закрыт"| Z F -.->|"вопрос закрыт"| Z G -.->|"вопрос закрыт"| Z
Обратите внимание: пунктирные стрелки выходят с каждого уровня. Большинство вопросов закрывается на втором или четвёртом, и до истории дело не доходит. Идти сразу в историю — распространённая ошибка увлечённых: она интереснее, но дороже.
Тесты как справочник
Первое, что стоит открыть в проекте без документации, — не исходники, а тесты.
Причины, по которым тест лучше примера из README:
- Тест проверяется на каждом коммите. Пример из README может не компилироваться уже два года; тест — нет.
- Тесты покрывают границы. В README счастливый путь; в тестах — пустой ввод, отрицательные числа, таймауты, конкурентный доступ. Это ровно те случаи, из-за которых вы и пришли.
- Тест показывает намерение. Название теста — это утверждение о том, что система обязана делать.
TestClientRetriesOnlyIdempotentRequestsотвечает на вопрос лучше любого абзаца. - Табличные тесты — готовая таблица поведения. В экосистемах, где принят табличный стиль, один такой тест содержит два десятка пар «вход — ожидаемый выход».
# найти тесты, относящиеся к интересующему символу
rg -n --type go 'func Test.*Retry' .
rg -n -A20 'describe\(.retry' test/
# в Go: запустить один тест и посмотреть, что он делает
go test -run TestClientRetries -v ./internal/client/
# найти тестовые данные — часто они информативнее самих тестов
fd -t d 'testdata|fixtures|__snapshots__'
Приём: если тест непонятен, запустите его под отладчиком или добавьте печать. Тест — это минимальная воспроизводимая программа, использующая интересующий вас API; ничего лучше для экспериментов не существует. Про чтение чужого кода ради обучения — отдельная глава соседнего трека; здесь нас интересует извлечение конкретного ответа, а не построение общей модели.
Чтение реализации под вопрос
Читать незнакомый код целиком — плохая идея и обычно невыполнимая. Читать под конкретный вопрос — выполнимая задача на двадцать минут.
Тактика:
- Найти точку входа по имени. Публичная функция, которую вы вызываете. Семантический переход к определению (LSP,
go to definition) точнее текстового поиска. - Идти вглубь только по той ветке, которая относится к вашему случаю. Всё остальное — шум. Читать не «функцию целиком», а путь исполнения при ваших параметрах.
- Останавливаться на первом объяснении. Как только найдено условие, объясняющее наблюдаемое поведение, чтение прекращается.
- Проверять понимание экспериментом, а не дальнейшим чтением. Гипотеза «оно так себя ведёт, потому что вот эта проверка» проверяется за минуту изменением входа.
Два инструмента, которые заменяют часы чтения:
# трассировка: что программа делает на самом деле, без чтения кода
strace -f -e trace=network,openat -o trace.log ./service
ltrace -e 'malloc+free' ./tool # вызовы библиотечных функций
# для интерпретируемых языков — трассировка на уровне вызовов
python -X importtime -c 'import mypkg' # что и сколько импортируется
python -m trace --trace script.py | head -50
Наблюдение вместо чтения — часто самый быстрый ответ на вопрос «а что оно вообще делает». Сюда же относятся сетевые дампы (tcpdump, Wireshark, mitmproxy для HTTP) и повышение уровня логирования: у многих библиотек есть скрытый debug-режим, который печатает ровно то, что вам нужно. Про инструментальную сторону — глава про диагностику сети и про измерения.
История как источник знания
Здесь мы переходим к самому недоиспользуемому источнику. Механика инструментов Git подробно разобрана в главе про расследование по истории; ниже — их применение именно как приёмов разведки, с ответом на вопрос «какой вопрос закрывает каждый».
| Вопрос разведки | Инструмент | Что получаете |
|---|---|---|
| Когда появилось это значение/строка/константа | git log -S'строка' |
коммит, где строка добавлена или удалена |
| Когда менялось что-то по шаблону | git log -G'regex' |
все коммиты, где дифф совпадает с регуляркой |
| Кто и зачем написал эту строку | git blame -C -M |
коммит, автор, дата; дальше — сообщение и PR |
| Как жила именно эта функция | git log -L :funcName:file.go |
история одной функции без шума |
| Когда сломалось | git bisect run ./check.sh |
точный коммит, вносящий регрессию |
| Что менялось между релизами | git log v1.4..v1.6 -- path/ |
список изменений в подсистеме |
| Что менялось в документации | git diff v1.4..v1.6 -- docs/ |
смысловые изменения, отфильтрованные авторами |
# когда в проекте появилось значение по умолчанию, которое нас удивило
git log -S'MaxIdleConns' --oneline -- transport.go
# что вообще меняли вокруг таймаутов за последний год
git log -G'[Tt]imeout' --since='1 year ago' --oneline
# история одной функции целиком, с диффами
git log -L :dialContext:net/http/transport.go
# кто трогал строку, игнорируя коммиты чистого форматирования
git blame -C -M --ignore-revs-file .git-blame-ignore-revs config.go
# от коммита к обсуждению: в сообщении почти всегда есть номер
git show a1b2c3d --stat | head -30
Приём git log -S (его называют pickaxe) заслуживает отдельного внимания, потому что закрывает самый частый исторический вопрос: «с какого момента это стало так». Ответ на него автоматически даёт датировку — то, чего не хватает большинству найденных утверждений.
Бинарный поиск по истории
Когда известно, что «в версии 1.4 работало, в 1.6 нет», а между ними триста коммитов, поиск виновника — это git bisect, и его стоит уметь запускать автоматически.
# скрипт-проверка возвращает 0, если всё хорошо, и 1, если воспроизводится баг
cat > /tmp/check.sh <<'EOF'
#!/bin/sh
go build ./... || exit 125 # 125 — «пропустить, не собирается»
./run-repro.sh
EOF
chmod +x /tmp/check.sh
git bisect start v1.6.0 v1.4.0
git bisect run /tmp/check.sh
git bisect reset
Логарифм от трёхсот — примерно восемь шагов. Автоматический bisect по большому диапазону обычно занимает меньше времени, чем чтение changelog в поисках подозрительного изменения, и даёт точный ответ вместо гипотезы. Код 125 в скрипте важен: он говорит «эту ревизию нельзя проверить», и bisect её пропускает вместо того, чтобы объявить плохой.
От коммита к намерению
Коммит отвечает на «что изменилось». На «почему» отвечает то, что вокруг него.
зависимости баг исправлен О->>Р: проверить нашу версию зависимости Р-->>Н: обход больше не нужен —
или, наоборот, нужен именно нам
Что искать в сообщении коммита, чтобы попасть в обсуждение:
- номер задачи или PR (
#1234,PROJ-567) — прямая ссылка; - трейлеры
Fixes:,Closes:,Reviewed-by:,Reported-by:,Link:— в проектах, живущих на рассылках,Link:ведёт прямо в архив письма; - упоминание CVE — значит, есть бюллетень с подробностями;
- префиксы Conventional Commits (
fix:,feat!:) — восклицательный знак означает ломающее изменение.
Если сообщение коммита пустое и ссылок нет, остаётся поиск по дате: посмотрите, что обсуждалось в трекере и рассылке в те же дни. Это работает чаще, чем кажется.
Недокументированное поведение: опираться или нет
Вы нашли, что система делает нечто полезное, но нигде не написано, что она обязана это делать. Вопрос: можно ли на это опираться?
Полезная формулировка — закон Хайрама (Hyrum Wright, hyrumslaw.com):
При достаточном числе пользователей API не имеет значения, что вы обещали в контракте: любое наблюдаемое поведение вашей системы будет использовано кем-то из них.
Это не разрешение опираться на что угодно, а описание того, что происходит с вами как с автором. Как с пользователем — решение принимается по чек-листу:
| Вопрос | Ответ «да» | Ответ «нет» |
|---|---|---|
| Покрыто ли поведение тестом в проекте? | это фактический контракт, менять его будет больно и авторам | случайность реализации |
| Упоминается ли в changelog или в issue? | о нём знают | о нём не знают, и оно исчезнет молча |
Публичный ли это символ (не _private, не internal)? |
шанс на стабильность выше | явное приглашение к поломке |
| Есть ли явное «subject to change»? | опираться нельзя | нейтрально |
| Что будет, если это исчезнет? | оцените цену | это и есть ответ |
Практический вывод: опираться иногда приходится, и это нормально. Но опора должна быть осознанной и записанной: комментарий в коде, тест, который сломается при изменении поведения, и запись в следе разведки (глава 13). Разница между инженерным решением и миной — в наличии этих трёх вещей.
Внутренний проект, автор которого ушёл
Самый частый практический случай — не заброшенная библиотека из интернета, а сервис вашей же компании без документации. Здесь корпус источников шире, чем кажется, и он специфический.
| Источник | Что оттуда достаётся | Как искать |
|---|---|---|
| Инфраструктурный код | реальная конфигурация: переменные окружения, лимиты, реплики | rg по репозиториям с Terraform, Helm, манифестами |
| Конфигурация CI | как это собирается, тестируется и выкатывается | .github/workflows, Jenkinsfile, .gitlab-ci.yml |
| Дашборды и алерты | что считается нормой и что считается поломкой | определения алертов лежат в коде, их можно грепать |
| Тикеты и доски | история изменений требований | поиск по названию сервиса в трекере, включая закрытое |
| Разборы инцидентов | как система ломается на самом деле | внутренние постмортемы; см. жанр |
| ADR и проектные документы | почему выбрано именно так | поиск по названию сервиса в вики и в docs/ репозитория |
| Переписка в мессенджере | обсуждения, которых нет нигде | поиск по имени сервиса и по имени бывшего автора |
| Код ревью | сомнения и компромиссы на момент написания | комментарии к старым PR по этому репозиторию |
Два приёма, специфичных для внутренней археологии.
Начинать с алертов и дашбордов, а не с кода. Определения алертов — это формализованные представления команды о том, что такое «плохо». Из них за десять минут вычитывается модель отказов сервиса, на реконструкцию которой по коду ушёл бы день.
Искать по имени ушедшего автора. git shortlog -sn даёт список тех, кто писал систему. Дальше — поиск по их сообщениям в трекере и по их комментариям в ревью. Это часто выводит на текст, который человек написал один раз в чате и который отвечает на ваш вопрос целиком.
И самое важное: у ушедшего автора почти всегда есть преемник по контексту — тот, кто с ним рядом работал. Пять минут разговора экономят день раскопок. Как формулировать такие вопросы, чтобы получить ответ, — глава 10.
Когда проекта больше нет
Отдельный случай: библиотека заброшена, сайт исчез, репозиторий удалён.
- Форки. У популярного заброшенного проекта почти всегда есть живой форк. Ищите по имени плюс «fork», смотрите список форков с недавними коммитами и сеть зависимостей в реестре пакетов.
- Пакеты дистрибутивов. Мейнтейнеры Debian, Fedora, Alpine и других дистрибутивов накладывают на исходники патчи и документируют их. Эти патчи — концентрат знаний о проблемах проекта: сборка на новых компиляторах, исправления безопасности, несовместимости. Смотреть их можно в исходном пакете дистрибутива.
- Software Heritage. Архив, специально сохраняющий исходный код исчезнувших проектов: softwareheritage.org.
- Веб-архивы. Документация исчезнувшего сайта часто сохранена в Wayback Machine.
- Зеркала рассылок. Обсуждения старых проектов живут в архивах списков рассылки дольше, чем сами проекты.
- Копия у вас на диске. Не забывайте самое очевидное: версия, которую вы используете, лежит в кэше пакетного менеджера вместе со всеми исходниками.
Реверс без исходников
Крайний случай — закрытый бинарник или чужой сервис. Полноценный реверс-инжиниринг выходит за рамки главы, но три дешёвых приёма стоит знать, потому что они часто закрывают вопрос:
# читаемые строки: пути сборки, версии, сообщения, иногда — эндпоинты
strings ./binary | rg -i 'http|version|error'
# какие библиотеки нужны и какие символы импортируются
ldd ./binary
nm -D ./binary | head -40
# что бинарник делает с системой
strace -f -e trace=file,network ./binary 2>&1 | head -60
# что ходит по сети (для HTTP удобнее прокси с подстановкой сертификата)
tcpdump -i any -A 'port 8080'
Юридическая и этическая сторона здесь важнее технической: лицензионные соглашения часто ограничивают исследование, а исследование чужого работающего сервиса без разрешения — отдельная тема со своими правилами. Про границы — глава про моделирование угроз.
Мини-практика
Упражнение на сорок минут, которое переводит главу из чтения в навык. Возьмите библиотеку, которой вы пользуетесь каждый день, и ответьте на четыре вопроса, пользуясь только её репозиторием:
- Какое значение по умолчанию у любого таймаута в ней и когда оно последний раз менялось? Инструмент:
rgплюсgit log -S. - Какое поведение покрыто тестом, а какое — случайность реализации? Возьмите одну функцию и найдите её тесты.
- Есть ли в коде обход чужого бага? Ищите комментарии со словами
workaround,hack,bug,TODOи номерами задач; откройте одну такую задачу целиком. - Кто реально держит проект?
git shortlog -sn --all, затем — сколько из этих людей коммитили за последний год.
Ценность упражнения в том, что оно даёт калибровку: вы увидите, сколько времени на самом деле занимает каждый уровень раскопок, и в следующий раз будете оценивать стоимость разведки не наугад.
Типичные ошибки
- Начинать с истории. Она интереснее и дороже. Сначала тесты и реализация.
- Читать код целиком. Читать надо путь исполнения при ваших параметрах, а не файл.
- Игнорировать тесты. Это самая точная и самая доступная документация проекта.
- Останавливаться на коммите. Коммит говорит «что»; «почему» лежит в обсуждении, на которое он ссылается.
- Опираться на недокументированное поведение молча. Опора без комментария и без теста — мина замедленного действия.
- Не проверять гипотезу экспериментом. Чтение кода даёт правдоподобную модель; правильность даёт запуск.
- Считать заброшенный проект тупиком. Форки, патчи дистрибутивов и архивы обычно живы.
Мини-итог
- Порядок раскопок: README и примеры, тесты, публичная поверхность, реализация, история, обсуждение, люди. Большинство вопросов закрывается на втором и четвёртом уровне.
- Тесты — лучшая документация недокументированного проекта: они проверяются, покрывают границы и формулируют намерение в названиях.
- Читать реализацию надо под вопрос и по одной ветке исполнения, останавливаясь на первом объяснении.
- Трассировка и дампы часто отвечают быстрее чтения: наблюдение вместо реконструкции.
- История отвечает на вопрос «с какого момента»:
log -Sдля строк,log -Gдля шаблонов,blameдля авторства,log -Lдля функции,bisect runдля регрессии. - От коммита к намерению ведут номера задач и трейлеры в сообщении; без них — поиск по дате в трекере и рассылке.
- Опора на недокументированное поведение допустима, если она осознана, покрыта своим тестом и записана.
- Заброшенный проект остаётся источником: форки, патчи дистрибутивов, архивы кода и веба, локальный кэш пакетов.
Источники
- Hyrum’s Law — формулировка и обсуждение: hyrumslaw.com; подробный разбор — Titus Winters et al., Software Engineering at Google (O’Reilly, 2020), глава про эволюцию API.
- Git Documentation,
git-log(опции-S,-G,-L) — git-scm.com/docs/git-log. - Git Documentation,
git-bisectи режимrun— git-scm.com/docs/git-bisect. - Conventional Commits — соглашение о сообщениях коммитов: conventionalcommits.org.
- Software Heritage — архив исходного кода: softwareheritage.org.
- Архив списков рассылки ядра Linux с поиском — lore.kernel.org.
Что дальше
Источник найден. Теперь главный вопрос всего трека: можно ли ему верить. Проверка: дата, автор, воспроизводимость.