Как искать информацию Поиск по коду и артефактам: индекс, который не врёт
0%

Поиск по коду и артефактам: индекс, который не врёт

Поиск по коду и артефактам: индекс, который не врёт

У документации есть одно неустранимое свойство: она описывает намерение. Код описывает поведение. Когда эти двое расходятся — а расходятся они регулярно, — прав код.

Из этого следует практический вывод, который многие инженеры делают слишком поздно: поиск по коду — не запасной вариант на случай, когда документации нет, а основной инструмент для нескольких классов вопросов. Эта глава про то, какие это классы, чем искать и как не утонуть в результатах.

Пять вопросов, на которые отвечает только код

1. Как этим на самом деле пользуются. Документация показывает канонический вызов из трёх строк. Реальный код показывает, что рядом с этим вызовом всегда стоят ещё два, что первый аргумент почти никогда не бывает значением по умолчанию, и что все оборачивают это в try. Сотня реальных употреблений — это статистика по способу использования, которую никакая документация не даёт.

2. Откуда взялась эта строка. У вас в логе строка, которой нет нигде в интернете. Она напечатана каким-то кодом. Поиск по коду находит место, где она формируется, а вместе с ним — условие, при котором это происходит. Это самый прямой путь от симптома к механизму из всех существующих.

3. Существует ли это вообще. Метод, который вам подсказали (человек, статья или модель), либо есть в исходниках, либо его нет. Проверка занимает секунды и закрывает целый класс потерь времени — включая самый обидный, когда вы полчаса ищете документацию к функции, которой никогда не было.

4. Как выглядят реальные конфигурации. Примеры в документации минимальны. Поиск по имени файла (Dockerfile, .golangci.yml, nginx.conf) даёт тысячи боевых конфигов, в которых видно, какие опции люди ставят на практике и в какой комбинации.

5. Кто уже наткнулся на то же самое. Обходной путь чаще всего фиксируется не в тексте, а в коде — комментарием вида «workaround for issue #1234 in library vX». Поиск по такому комментарию находит и обходной путь, и ссылку на обсуждение.

GitHub code search: синтаксис, который стоит выучить

В 2023 году GitHub заменил старый поиск по коду на новый движок с индексацией по токенам и поддержкой регулярных выражений. Это самый большой доступный индекс исходников, и у него есть язык запросов.

# базовое: слово в коде на конкретном языке
connectTimeout language:go

# точная строка — в кавычках; экранирование через \"
"unable to find valid certification path" language:java

# ограничение областью
repo:kubernetes/kubernetes language:go "wait.PollUntilContextTimeout"
org:apache path:*.py "def retry"

# по пути и имени файла
path:Dockerfile "FROM golang" "CGO_ENABLED=0"
path:**/testdata/*.json "expires_at"

# по объявлению символа: находит определение, а не употребления
symbol:RetryPolicy language:python

# регулярное выражение — между слэшами
/timeout\s*=\s*[0-9]{4,}/ language:go

# булевы операторы, скобки, отрицание
(language:rust OR language:go) NOT path:vendor "graceful shutdown"

Что важно знать про ограничения, чтобы не делать ложных выводов из пустой выдачи:

  • Индексируется ветка по умолчанию публичных репозиториев (плюс ваши приватные, к которым есть доступ). Кода из других веток и из тегов в индексе нет.
  • Форки по умолчанию не индексируются — иначе выдача состояла бы из копий.
  • Очень крупные файлы и часть сгенерированного кода могут не попасть в индекс.
  • Поиск по коду требует авторизации. Это неудобно, но это плата за размер индекса.
  • Регулярные выражения ограничены по сложности: обратные ссылки и слишком «дорогие» конструкции отклоняются.

Приём, который окупается сразу же: symbol: отличается от обычного поиска тем, что находит определение, а не тысячу вызовов. Когда вам нужно «где это объявлено», это разница между одним результатом и тысячей.

Второй приём: комбинация path: и точной строки. Хотите увидеть, как настраивают редко используемую опцию, — ищите её внутри файлов конфигурации, а не в коде вообще.

grep.app и другие индексы

grep.app — поиск регулярными выражениями по большому набору публичных репозиториев. Отличается от GitHub тремя вещами: не требует входа, отдаёт результаты почти мгновенно и показывает распределение по репозиториям и языкам сбоку. Для вопроса «насколько распространён этот паттерн» это удобнее, потому что видно, сколько разных проектов его используют, а не сколько строк нашлось.

Debian Code Search — регулярные выражения по исходникам всех пакетов Debian. Это другой корпус: не то, что люди выложили на GitHub, а то, что реально собирается и поставляется в дистрибутиве, включая старые проекты, которых на GitHub нет вовсе.

Software Heritage — архив исходного кода, задача которого — сохранить всё, включая исчезнувшие проекты. Полезен, когда репозиторий удалён, а вам нужно посмотреть, что там было.

Sourcegraph — движок поиска по коду с продвинутым синтаксисом (структурный поиск, type:diff для поиска по изменениям, type:symbol). Условия доступа к публичному инстансу менялись, поэтому проверяйте актуальность; самостоятельно разворачиваемая версия и открытый индексатор Zoekt, лежащий в её основе, доступны и применимы к своему монорепозиторию.

Поиск по коду внутри одного проекта. Часто самый быстрый путь — не глобальный индекс, а клон и rg. Клонирование среднего репозитория занимает секунды, а локальный поиск даёт вам полную историю, все ветки и возможность сразу читать соседний код.

Реестры пакетов как индекс

Отдельный слой, который недоиспользуют.

Экосистема Что искать Чем полезно
pkg.go.dev пакеты, символы, документация из комментариев видно, кто импортирует пакет — прямой сигнал живости
docs.rs сгенерированная документация всех версий крейта можно смотреть API конкретной старой версии
crates.io версии, зависимости, обратные зависимости обратные зависимости — список реальных примеров использования
npm пакеты, версии, время публикации дата последней публикации отвечает на вопрос про заброшенность
PyPI релизы, классификаторы, требования к версии Python список файлов релиза показывает, есть ли колёса под вашу платформу
Maven Central артефакты, координаты, зависимости поиск по классу помогает найти, из какого артефакта он приходит
deps.dev граф зависимостей, лицензии, уязвимости отвечает на «что притащит эта зависимость» без установки

Два неочевидных приёма.

Обратные зависимости — это курированный список примеров. Если вы не понимаете, как пользоваться библиотекой, посмотрите на список проектов, которые от неё зависят, и загляните в два-три. Там будет реальное употребление в контексте, а не сниппет.

Документация конкретной старой версии. docs.rs и аналоги хранят документацию всех опубликованных версий. Это закрывает частый вопрос «а что было в версии, которая у нас в проде» без археологии.

Как искать пример использования, а не шум

Запрос по имени функции даёт тысячи строк. Отсев делается по четырём признакам, и они упорядочены по силе сигнала.

Самое ценное здесь — первая строка. Тесты внутри самого проекта — лучшая документация по его API, потому что они писались автором, проверяются на каждом коммите и покрывают граничные случаи, которых нет в примерах. Практический приём: вместо поиска примеров в интернете клонируйте репозиторий библиотеки и прочитайте её тесты по интересующей функции. Это надёжнее любого сниппета.

Тревожный признак «сниппет повторяется дословно в сотнях репозиториев» стоит пояснить. Есть известный эффект: неудачный пример из популярного ответа на Q&A-площадке расходится по проектам копированием, и его распространённость перестаёт быть свидетельством правильности. Классический случай — небезопасные конфигурации TLS-клиентов, которые копировали годами: «отключить проверку сертификата, чтобы заработало». Частота употребления — это не голосование за корректность. Про то, почему аргумент от популярности не является аргументом, есть отдельный разбор в главе про ошибки релевантности.

Расследование строки: как это выглядит целиком

Обратите внимание на два места, где расследование могло бы закончиться раньше и хуже. Если остановиться на шаге 2, вывод будет «у нас таймауты» — верно и бесполезно. Если остановиться на шаге 4, вывод будет «поставим таймаут больше» — рабочее лечение симптома. Только шаг 6 даёт знание: умолчание менялось, значит, поведение зависит от версии, значит, надо зафиксировать версию и проверить остальные места.

Оценка чужого репозитория за пять минут

Поиск по коду постоянно приводит вас в незнакомые проекты, и надо быстро решить, стоит ли на этот код опираться — как на пример, как на зависимость или как на источник истины. Порядок осмотра, который занимает несколько минут:

# 1. Живость: когда был последний коммит и как распределена активность
git log -1 --format='%ci %an'
git log --since='1 year ago' --pretty=format:'%an' | sort | uniq -c | sort -rn | head

# 2. Кто на самом деле держит проект: bus factor по числу авторов
git shortlog -sn --all | head -10

# 3. Есть ли тесты и сколько их относительно кода
tokei . 2>/dev/null || cloc .
ls test tests spec __tests__ 2>/dev/null

# 4. Есть ли релизы и версионирование
git tag --sort=-creatordate | head -10

Дальше — то, что смотрится в вебе:

На что смотреть Хороший признак Тревожный признак
Последний релиз месяцы назад, с changelog несколько лет назад, релизов нет вообще
Открытые issue есть ответы мейнтейнеров, идёт триаж сотни без единого ответа
CI зелёный, конфигурация видна нет вовсе или красный годами
Число реальных авторов несколько человек один человек, не отвечавший год
Архивность активен помечен archived
Лицензия явная и совместимая с вашей отсутствует — по умолчанию все права защищены
Зависимости немного, известные глубокое дерево из заброшенных пакетов

Отдельный сигнал, который часто игнорируют: отсутствие файла LICENSE не означает «делайте что хотите». По умолчанию код без лицензии защищён авторским правом, и права на использование у вас нет. Это касается и сниппетов, найденных поиском.

Структурный поиск: когда регулярка не справляется

Регулярные выражения не понимают синтаксис. Если вам нужно найти «все вызовы функции, у которых третий аргумент — литерал nil», регуляркой это делается плохо. Есть инструменты, работающие по синтаксическому дереву:

# ast-grep: шаблон на языке самого кода, $A — метапеременная
ast-grep --pattern 'http.Client{$$$}' --lang go

# semgrep: правила с семантикой, широко используется в статическом анализе
semgrep -e 'requests.get($URL, verify=False)' --lang python .

# comby: перепись по структурным шаблонам
comby 'try { :[body] } catch (Exception :[e]) { }' '' -matcher .java -d src/

Это же — рабочий способ ответить на вопрос «есть ли у нас в кодовой базе такой антипаттерн» одним запросом вместо ревью. Про сам статический анализ — глава про принципы и CI.

Локальные инструменты, которые быстрее любого веб-индекса

# ripgrep: рекурсивно, с учётом .gitignore, с типами файлов
rg -n --type go 'IdleConnTimeout'
rg -n -C3 'panic\(' internal/    # три строки контекста вокруг

# только имена файлов, где встречается
rg -l 'InitializeSdk'

# поиск с учётом границ слова и без учёта регистра
rg -w -i 'retry_policy'

# git grep умеет искать в произвольной ревизии, а не только в рабочем дереве
git grep -n 'MaxIdleConns' v1.4.0
git grep -n 'MaxIdleConns' $(git rev-list --all) -- '*.go' | head

# поиск по зависимостям, которые уже скачаны
rg -n 'def urlopen' "$(python -c 'import sysconfig;print(sysconfig.get_paths()["purelib"])')"
rg -n 'func Dial' "$(go env GOMODCACHE)"

Последние две строки стоят того, чтобы стать привычкой: исходники всех ваших зависимостей уже лежат у вас на диске, ровно в тех версиях, которые вы используете. Это индекс без проблемы датировки — самый точный источник из всех возможных, и он локальный.

Для навигации по большому дереву поверх текстового поиска полезны семантические инструменты: переход к определению и поиск ссылок через LSP в редакторе, ctags, go to references. Разница ощутима: текстовый поиск найдёт слово, семантический — именно этот символ, а не одноимённый из другого пакета.

Артефакты: когда исходников нет

Иногда искать приходится не в коде, а в собранном.

# читаемые строки в бинарнике: версии, пути сборки, сообщения об ошибках
strings ./service | rg -i 'version|built|commit'

# что попало в контейнерный образ и какими слоями
docker history --no-trunc myimage:tag
docker sbom myimage:tag          # состав образа, если доступно

# распакованный jar — обычный zip
unzip -l app.jar | rg -i 'META-INF/MANIFEST'

# по какому пути собирался Go-бинарник и с какими модулями
go version -m ./service

go version -m заслуживает отдельного упоминания: он печатает полный список модулей и версий, вшитых в бинарник. Это прямой ответ на вопрос «а что у нас реально в проде», который часто расходится с тем, что написано в манифесте.

Похожие возможности есть у многих экосистем: манифест в JAR, метаданные в wheel, SBOM для образов. Смысл один — артефакт знает про себя больше, чем ваша документация о нём.

Юридическая сторона: найденный код — не ваш код

Момент, который в статьях про поиск обычно опускают, а он важен.

Любой найденный фрагмент кода лицензирован. Копирование куска из проекта под GPL в закрытый продукт создаёт юридическую проблему; копирование без указания авторства из проекта под MIT нарушает условие атрибуции, потому что MIT его требует. Правило рабочей гигиены:

  • Смотреть на LICENSE репозитория до копирования, а не после.
  • Различать «понял идею» и «скопировал реализацию». Первое проблемой не является, второе может ею быть.
  • Для нетривиальных фрагментов оставлять в коде ссылку на источник и лицензию. Это одновременно и юридическая гигиена, и след для будущего читателя (глава 13).
  • Помнить, что то же относится к коду, сгенерированному моделью. Это отдельная и не до конца устоявшаяся тема, но игнорировать её нельзя; смежные риски цепочки поставок разбираются в главе про supply chain.

Типичные ошибки

  1. Считать пустую выдачу доказательством отсутствия. Индексируется ветка по умолчанию публичных репозиториев — это большой, но не полный корпус.
  2. Брать первый попавшийся сниппет. Отсев по признакам занимает минуту и отличает боевой код от чужого черновика.
  3. Не смотреть на тесты. Тесты библиотеки — лучший источник примеров, и он почти всегда игнорируется.
  4. Искать в вебе то, что лежит в node_modules или в модульном кэше. Локальные исходники точнее по версии и быстрее по времени.
  5. Путать распространённость с корректностью. Растиражированный копипастой антипаттерн выглядит как консенсус.
  6. Копировать без взгляда на лицензию.
  7. Останавливаться на найденной строке. Строка — это вход в расследование, а не ответ: дальше идут условие, история изменения и обсуждение.

Мини-итог

  • Код отвечает на пять вопросов, на которые не отвечает документация: как реально используют, откуда строка, существует ли символ, как выглядят боевые конфиги, кто уже это обходил.
  • GitHub code search — самый большой индекс; ключевые операторы: repo:, org:, path:, language:, symbol:, точная строка в кавычках и регулярные выражения между слэшами.
  • grep.app удобен для оценки распространённости паттерна, Debian Code Search даёт другой корпус, Software Heritage хранит исчезнувшее.
  • Реестры пакетов отвечают на вопросы про версии, заброшенность, обратные зависимости и документацию старых релизов.
  • Лучший источник примеров — тесты самой библиотеки; худший — сниппет, растиражированный копипастой.
  • Локальный поиск по уже скачанным зависимостям точнее любого веб-индекса, потому что это ровно ваши версии.
  • Артефакт знает про себя больше, чем документация о нём: strings, go version -m, манифесты, SBOM.
  • У найденного кода есть лицензия, и смотреть на неё надо до копирования.

Источники

  • GitHub Docs, «Understanding GitHub Code Search syntax» — docs.github.com/search-github/github-code-search.
  • grep.app — поиск регулярными выражениями по публичным репозиториям: grep.app.
  • Debian Code Search — codesearch.debian.net.
  • Software Heritage — архив исходного кода: softwareheritage.org.
  • Zoekt — открытый движок индексации кода: github.com/sourcegraph/zoekt.
  • ast-grep — структурный поиск и переписывание по синтаксическому дереву: ast-grep.github.io.
  • Semgrep — правила статического анализа с семантикой шаблонов: semgrep.dev.
  • Open Source Insights (deps.dev) — граф зависимостей, лицензии и уязвимости пакетов: deps.dev.

Что дальше

Мы научились доставать факты из кода. Теперь — про то, как выстраивать иерархию источников вообще и почему путь к первоисточнику короче, чем кажется. Первоисточник против пересказа.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов