<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Статьи on Михаил Шогин</title><link>https://mshogin.ru/post/</link><description>Recent content in Статьи on Михаил Шогин</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>Михаил Шогин</copyright><lastBuildDate>Tue, 24 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mshogin.ru/post/index.xml" rel="self" type="application/rss+xml"/><item><title>Экосистема линтеров для AI-агентов: контроль качества, стоимости и безопасности</title><link>https://mshogin.ru/blog/ai-linter-ecosystem/</link><pubDate>Tue, 24 Mar 2026 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/ai-linter-ecosystem/</guid><description>&lt;h2 id="проблема"&gt;Проблема
&lt;/h2&gt;&lt;p&gt;AI-агенты генерируют код быстро, но за скорость приходится платить. В прямом смысле - токены стоят денег. И в переносном - качество кода деградирует без контроля.&lt;/p&gt;
&lt;p&gt;Я столкнулся с тремя проблемами одновременно:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Архитектура деградирует&lt;/strong&gt; - агент пишет код, который работает, но нарушает слоевую архитектуру, создает циклические зависимости и god-классы&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Расходы растут&lt;/strong&gt; - сложный промпт уходит в Opus за $15/1M токенов, хотя Haiku за $0.80 справился бы&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контент не фильтруется&lt;/strong&gt; - в production-промптах появляется то, что там быть не должно&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Каждую из этих проблем можно решить отдельным инструментом. Но когда инструменты работают вместе - они усиливают друг друга.&lt;/p&gt;
&lt;h2 id="пайплайн"&gt;Пайплайн
&lt;/h2&gt;&lt;p&gt;Запрос проходит через цепочку линтеров, каждый отвечает за свою область:&lt;/p&gt;
&lt;div class="mermaid"&gt;
graph LR
A[Промпт] --&gt; B[seclint]
B --&gt;|safe| C[promptlint]
B --&gt;|blocked| X[Reject]
C --&gt;|haiku| D1[Agent: Haiku]
C --&gt;|sonnet| D2[Agent: Sonnet]
C --&gt;|opus| D3[Agent: Opus]
D1 --&gt; E[archlint]
D2 --&gt; E
D3 --&gt; E
E --&gt;|pass| F[costlint]
E --&gt;|fail| C
F --&gt; G[Done]
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;seclint&lt;/strong&gt; - проверяет контент на безопасность (6+/12+/16+/18+)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;promptlint&lt;/strong&gt; - оценивает сложность, выбирает модель&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Агент&lt;/strong&gt; - выполняет задачу на выбранной модели&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;archlint&lt;/strong&gt; - проверяет результат на архитектурные нарушения&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;costlint&lt;/strong&gt; - записывает стоимость, считает cache hit rate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если archlint отклоняет результат - задача возвращается на более мощную модель. costlint фиксирует стоимость этой эскалации.&lt;/p&gt;
&lt;h2 id="инструменты"&gt;Инструменты
&lt;/h2&gt;&lt;h3 id="archlint---архитектурный-линтер"&gt;archlint - архитектурный линтер
&lt;/h3&gt;&lt;p&gt;Сканирует Go-проекты на структурные нарушения.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;archlint scan --format json ./project/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Что находит:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Нарушения слоев (handler вызывает repository напрямую)&lt;/li&gt;
&lt;li&gt;God-классы (&amp;gt;20 методов или &amp;gt;15 зависимостей)&lt;/li&gt;
&lt;li&gt;Циклические зависимости&lt;/li&gt;
&lt;li&gt;Нарушения Interface Segregation (интерфейсы &amp;gt;5 методов)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Метрики: fan-out, coupling (Ca/Ce), количество компонентов и связей.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mshogin/archlint" target="_blank" rel="noopener"
&gt;mshogin/archlint&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="promptlint---роутер-по-сложности-промптов"&gt;promptlint - роутер по сложности промптов
&lt;/h3&gt;&lt;p&gt;Оценивает сложность промпта и выбирает модель. Без LLM, чистые метрики, &amp;lt;10ms.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Fix typo in README&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; promptlint analyze --output-model
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# haiku&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Design microservices with CQRS&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; promptlint analyze --output-model
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# opus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Сигналы: длина, количество предложений, доменные ключевые слова, тип действия (fix/create/refactor), наличие кода.&lt;/p&gt;
&lt;p&gt;Интеграция с ccproxy - реальный роутинг запросов Claude Code через прокси.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mikeshogin/promptlint" target="_blank" rel="noopener"
&gt;mikeshogin/promptlint&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="costlint---анализ-затрат-на-токены"&gt;costlint - анализ затрат на токены
&lt;/h3&gt;&lt;p&gt;Считает стоимость, анализирует кэширование, проводит A/B тесты между моделями.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;costlint report --source telemetry.jsonl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cost Report:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Total requests: 342
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; By model:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; opus: 48 requests, ~$12.60
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sonnet: 195 requests, ~$6.50
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; haiku: 99 requests, ~$0.44
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; With optimal routing: ~$8.20 (savings: 58%)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Метрики кэширования: cache hit rate, block reuse, content entropy, Jaccard similarity.
A/B тесты: сплит трафика 30/30/40, per-group метрики стоимости и качества.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mikeshogin/costlint" target="_blank" rel="noopener"
&gt;mikeshogin/costlint&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="seclint--promptsec---контент-фильтр"&gt;seclint / promptsec - контент-фильтр
&lt;/h3&gt;&lt;p&gt;Возрастной рейтинг промптов: 6+, 12+, 16+, 18+.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Help with math homework&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; seclint rate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# {&amp;#34;rating&amp;#34;: &amp;#34;6+&amp;#34;, &amp;#34;safe&amp;#34;: true}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Explain SQL injection for security course&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; seclint rate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# {&amp;#34;rating&amp;#34;: &amp;#34;16+&amp;#34;, &amp;#34;safe&amp;#34;: true, &amp;#34;flags&amp;#34;: [&amp;#34;security_tools&amp;#34;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Учитывает образовательный контекст - объяснение SQL-инъекций для курса по безопасности не блокируется, а получает рейтинг 16+ вместо 18+.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mikeshogin/seclint" target="_blank" rel="noopener"
&gt;mikeshogin/seclint&lt;/a&gt; / &lt;a class="link" href="https://github.com/mikeshogin/promptsec" target="_blank" rel="noopener"
&gt;mikeshogin/promptsec&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="общие-принципы"&gt;Общие принципы
&lt;/h2&gt;&lt;p&gt;Все инструменты следуют одним правилам:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt; - единый стек, единый билд&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Без LLM&lt;/strong&gt; - чистые метрики, regex, keyword matching. &amp;lt;10ms на запрос&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI + HTTP&lt;/strong&gt; - каждый инструмент работает как команда и как сервер&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSONL телеметрия&lt;/strong&gt; - единый формат логов для анализа&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipeline-friendly&lt;/strong&gt; - exit codes, stdout, pipes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="числа"&gt;Числа
&lt;/h2&gt;&lt;p&gt;На тестовой нагрузке (342 запроса за неделю):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Метрика&lt;/th&gt;
&lt;th&gt;До&lt;/th&gt;
&lt;th&gt;После&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Расход на токены&lt;/td&gt;
&lt;td&gt;$19.54&lt;/td&gt;
&lt;td&gt;$8.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Архитектурные нарушения&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Запросы на дорогую модель&lt;/td&gt;
&lt;td&gt;100% opus&lt;/td&gt;
&lt;td&gt;14% opus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Время роутинга&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&amp;lt;10ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Экономия 58% на API без потери качества - дешевые задачи уходят на Haiku, архитектурные задачи остаются на Opus.&lt;/p&gt;
&lt;h2 id="оркестрация"&gt;Оркестрация
&lt;/h2&gt;&lt;p&gt;Инструменты работают автономно, но максимальный эффект дают вместе. Для оркестрации используется &lt;a class="link" href="https://github.com/kgatilin/myhome" target="_blank" rel="noopener"
&gt;myhome&lt;/a&gt; - daemon-based управление AI-агентами с workflow stages и scheduled tasks.&lt;/p&gt;
&lt;p&gt;Связка:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;myhome запускает агента&lt;/li&gt;
&lt;li&gt;promptlint выбирает модель (pre-route hook)&lt;/li&gt;
&lt;li&gt;archlint проверяет результат (quality gate stage)&lt;/li&gt;
&lt;li&gt;costlint считает стоимость (telemetry consumer)&lt;/li&gt;
&lt;li&gt;seclint фильтрует входящие промпты (pre-filter)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="начать"&gt;Начать
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Установить все&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/mshogin/archlint@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/mikeshogin/promptlint/cmd/promptlint@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/mikeshogin/costlint/cmd/costlint@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install github.com/mikeshogin/seclint/cmd/seclint@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Запустить роутинг&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;promptlint serve &lt;span class="m"&gt;8090&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;seclint serve &lt;span class="m"&gt;8091&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Проверить архитектуру&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;archlint scan --format json ./my-project/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Посмотреть затраты&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;costlint report --source ~/.promptlint/telemetry.jsonl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="ссылки"&gt;Ссылки
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/mshogin/archlint" target="_blank" rel="noopener"
&gt;archlint&lt;/a&gt; - архитектурный линтер&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/mikeshogin/promptlint" target="_blank" rel="noopener"
&gt;promptlint&lt;/a&gt; - роутер по сложности&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/mikeshogin/costlint" target="_blank" rel="noopener"
&gt;costlint&lt;/a&gt; - анализ затрат&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/mikeshogin/seclint" target="_blank" rel="noopener"
&gt;seclint&lt;/a&gt; / &lt;a class="link" href="https://github.com/mikeshogin/promptsec" target="_blank" rel="noopener"
&gt;promptsec&lt;/a&gt; - контент-фильтр&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kgatilin/myhome" target="_blank" rel="noopener"
&gt;myhome&lt;/a&gt; - оркестрация агентов&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/mshogin/archlint/blob/main/ECOSYSTEM.md" target="_blank" rel="noopener"
&gt;ECOSYSTEM.md&lt;/a&gt; - карта интеграции&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Три блокера, которые мешают техническому лидеру расти</title><link>https://mshogin.ru/blog/leadership-blockers/</link><pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/leadership-blockers/</guid><description>&lt;img src="https://mshogin.ru/blog/leadership-blockers/cover.svg" alt="Featured image of post Три блокера, которые мешают техническому лидеру расти" /&gt;&lt;p&gt;AI меняет IT быстрее, чем мы успеваем адаптироваться. Роли трансформируются. Навыки, за которые платили вчера, обесцениваются сегодня.&lt;/p&gt;
&lt;p&gt;Чтобы сохранить уровень жизни - нужно расти. Но рост упирается не в технологии. Не в стек. Не в сертификаты.&lt;/p&gt;
&lt;p&gt;Он упирается в мышление.&lt;/p&gt;
&lt;p&gt;Сильные специалисты застревают. Не потому что не хватает знаний. А потому что есть внутренние блокеры, которые сложно увидеть самому.&lt;/p&gt;
&lt;p&gt;Я работаю с архитектурой систем больше пятнадцати лет. И последние несколько лет замечаю: самые серьезные баги - не в коде. Они в головах людей, которые этот код пишут и управляют теми, кто пишет.&lt;/p&gt;
&lt;p&gt;Вот три паттерна, которые я вижу чаще всего. В других. И в себе.&lt;/p&gt;
&lt;h2 id="блокер-1-защита-территории"&gt;Блокер 1: Защита территории
&lt;/h2&gt;&lt;p&gt;У каждого лидера есть зона ответственности. И любой вход в нее воспринимается как атака. Это инстинкт - как хищник обходит свои границы.&lt;/p&gt;
&lt;h3 id="как-это-выглядит"&gt;Как это выглядит
&lt;/h3&gt;&lt;p&gt;Представь ситуацию. Ты архитектор. Системный аналитик из твоей команды начинает принимать архитектурные решения самостоятельно. Делает это неплохо, но без согласования с тобой. Ты видишь результат - решения рабочие, но ощущаешь внутри раздражение, которое сложно объяснить рационально.&lt;/p&gt;
&lt;p&gt;На совещании ты начинаешь давить. Не потому что решение плохое - а потому что оно принято без тебя. Формально ты говоришь про качество и процессы. Реально - защищаешь территорию.&lt;/p&gt;
&lt;p&gt;Другой пример. Тимлид замечает, что senior в команде начинает брать на себя архитектурные вопросы. Вместо того чтобы дать пространство и помочь вырасти, тимлид усиливает контроль. Больше ревью. Больше согласований. Больше вопросов в стиле &amp;ldquo;а ты подумал про&amp;hellip;&amp;rdquo;. Формально - забота о качестве. Реально - страх стать ненужным.&lt;/p&gt;
&lt;h3 id="почему-это-блокер"&gt;Почему это блокер
&lt;/h3&gt;&lt;p&gt;Проблема не в защите. Защищать границы - нормально.&lt;/p&gt;
&lt;p&gt;Проблема в том, КАК ты защищаешь. Из уверенности в своих силах - или из страха, что отберут? Первое - лидерство. Второе - агрессия, которую люди чувствуют. И уходят.&lt;/p&gt;
&lt;p&gt;Если провести аналогию с системами: это как микросервис, который вместо того чтобы предоставлять API для взаимодействия, закрывает все порты и отвечает 403 на любой запрос. Технически он работает. Но система вокруг него деградирует.&lt;/p&gt;
&lt;h3 id="навык"&gt;Навык
&lt;/h3&gt;&lt;p&gt;Различать реальную угрозу и фантомную. Один вопрос себе: &amp;ldquo;я сейчас защищаю качество - или защищаю свое эго?&amp;rdquo;&lt;/p&gt;
&lt;h2 id="блокер-2-жажда-признания"&gt;Блокер 2: Жажда признания
&lt;/h2&gt;&lt;p&gt;На самом деле это про обратную связь.&lt;/p&gt;
&lt;h3 id="как-это-выглядит-1"&gt;Как это выглядит
&lt;/h3&gt;&lt;p&gt;Ты сделал отличную работу. Спроектировал систему, которая выдерживает нагрузку. Провел review процесс, который поднял качество кода в команде. Решил проблему, которую до тебя не могли решить полгода.&lt;/p&gt;
&lt;p&gt;И тишина. Никто не подошел и не сказал &amp;ldquo;классная работа&amp;rdquo;. Руководитель молчит. Команда приняла результат как должное.&lt;/p&gt;
&lt;p&gt;В этой тишине мозг начинает додумывать: &amp;ldquo;наверное, я плохо работаю&amp;rdquo;. Или хуже: &amp;ldquo;наверное, они не ценят то, что я делаю&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Знакомая история: архитектор выстроил процесс API-review для системных аналитиков. Качество спецификаций выросло. Количество багов на этапе интеграции упало. Но руководитель ни разу не сказал &amp;ldquo;хорошая работа&amp;rdquo;. И в какой-то момент архитектор начал задаваться вопросом - а стоило ли вообще тратить на это время.&lt;/p&gt;
&lt;p&gt;Еще пример. Senior-разработчик потратил месяц на рефакторинг критического модуля. Код стал чище, тесты - надежнее, время деплоя сократилось вдвое. На ретро об этом даже не упомянули. Senior начал демотивироваться - не потому что работа была бессмысленной, а потому что никто не заметил.&lt;/p&gt;
&lt;h3 id="почему-это-блокер-1"&gt;Почему это блокер
&lt;/h3&gt;&lt;p&gt;Во взрослой жизни никто не придет и не скажет &amp;ldquo;ты молодец&amp;rdquo;. Это делали родители. А на работе - тишина. И в этой тишине мозг начинает додумывать.&lt;/p&gt;
&lt;p&gt;Если смотреть на это как на систему: у тебя нет observability для собственной ценности. Нет метрик, нет дашборда, нет алертов. Ты работаешь вслепую и полагаешься на единственный индикатор - внешнюю похвалу. А этот индикатор ненадежен. Он зависит от настроения руководителя, от культуры компании, от тысячи факторов, которые ты не контролируешь.&lt;/p&gt;
&lt;h3 id="навык-1"&gt;Навык
&lt;/h3&gt;&lt;p&gt;Либо научиться просить обратную связь - это не слабость, это зрелость. Либо выстроить внутреннюю опору, которая не зависит от чужих слов.&lt;/p&gt;
&lt;p&gt;Практически: завести свой лог достижений. Раз в неделю записывать, что сделал и какой эффект это дало. Через полгода у тебя будет объективная картина - независимо от того, заметил руководитель или нет.&lt;/p&gt;
&lt;h2 id="блокер-3-опыт-как-ловушка"&gt;Блокер 3: Опыт как ловушка
&lt;/h2&gt;&lt;p&gt;Большой опыт создает иллюзию: &amp;ldquo;я больше знаю - мне должны&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="как-это-выглядит-2"&gt;Как это выглядит
&lt;/h3&gt;&lt;p&gt;Совещание. Ты - самый опытный в комнате. Junior предлагает решение. Ты видишь в нем проблемы. И вместо того чтобы задать вопрос - &amp;ldquo;а что будет при нагрузке 10x?&amp;rdquo; - ты говоришь: &amp;ldquo;нет, так не делают, я десять лет занимаюсь этим и знаю&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Формально ты прав. Решение действительно может не выдержать нагрузку. Но ты убил две вещи: инициативу junior-а и возможность для команды научиться думать самостоятельно.&lt;/p&gt;
&lt;p&gt;Другой пример. Опытный тимлид приходит в новую команду. У него за плечами три успешных проекта с определенным стеком. Команда использует другой подход. Вместо того чтобы разобраться, почему выбран именно этот подход, тимлид начинает продавливать свой. Не потому что он лучше - а потому что знакомый. И потому что признать чужой подход = признать, что твой опыт не универсален.&lt;/p&gt;
&lt;p&gt;Я сам ловлю себя на этом. Иногда давлю опытом вместо того чтобы слушать. И каждый раз, когда ловлю себя на этом, задаю вопрос: я сейчас учу - или доказываю?&lt;/p&gt;
&lt;h3 id="почему-это-блокер-2"&gt;Почему это блокер
&lt;/h3&gt;&lt;p&gt;Мир трансформируется. Старые шаблоны рушатся. В эпоху AI вчерашний эксперт сегодня учится заново. Опыт - ценность, но не валюта для обмена на покорность.&lt;/p&gt;
&lt;p&gt;Если как систему: это legacy-сервис с hardcoded конфигурацией. Когда-то он работал отлично. Но мир вокруг изменился, а он все еще ожидает запросы в старом формате. И вместо того чтобы адаптировать API - требует, чтобы все остальные адаптировались под него.&lt;/p&gt;
&lt;h3 id="навык-2"&gt;Навык
&lt;/h3&gt;&lt;p&gt;Относиться к коллегам как к равным. Независимо от разницы в опыте. Это не значит игнорировать свой опыт - это значит делиться им через вопросы, а не через давление.&lt;/p&gt;
&lt;h2 id="три-блокера-три-навыка-ни-один---про-технологии"&gt;Три блокера. Три навыка. Ни один - про технологии
&lt;/h2&gt;&lt;p&gt;Защита территории, жажда признания, ловушка опыта. Эти паттерны не решаются чтением книг или курсами. Они встроены в архитектуру мышления так глубоко, что сложно увидеть самому.&lt;/p&gt;
&lt;p&gt;Как в любом проекте: чтобы увидеть архитектурные проблемы, нужен взгляд снаружи. Тот, кто внутри системы, видит отдельные симптомы. Тот, кто снаружи - видит паттерн.&lt;/p&gt;
&lt;p&gt;Лидерство начинается не с архитектуры систем, а с архитектуры мышления. И если ты чувствуешь, что застрял - возможно, дело не в том, что ты мало знаешь. А в том, что пора посмотреть на свое мышление как на систему и провести ему честный аудит.&lt;/p&gt;</description></item><item><title>Фаулер про LLM и разработчиков: cognitive debt, supervisory programming и кто на самом деле под ударом</title><link>https://mshogin.ru/blog/fowler-llm-developers/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/fowler-llm-developers/</guid><description>&lt;img src="https://mshogin.ru/cover.svg" alt="Featured image of post Фаулер про LLM и разработчиков: cognitive debt, supervisory programming и кто на самом деле под ударом" /&gt;&lt;p&gt;Мартин Фаулер опубликовал заметки с встречи senior-разработчиков, посвященной LLM. Не очередной хайп про &amp;ldquo;AI заменит всех&amp;rdquo; - а трезвый разбор от людей, которые пишут код десятилетиями.&lt;/p&gt;
&lt;p&gt;Несколько тезисов зацепили настолько, что захотелось разобрать их подробнее. С примерами из собственной практики.&lt;/p&gt;
&lt;h2 id="mid-level-под-ударом-а-не-джуны"&gt;Mid-level под ударом, а не джуны
&lt;/h2&gt;&lt;p&gt;Обычно все переживают за junior-разработчиков: их-то заменят первыми. Фаулер с группой пришли к обратному выводу.&lt;/p&gt;
&lt;p&gt;Джуны адаптивны. Они росли с LLM, умеют ими пользоваться, открыты к новому. Сеньоры понимают архитектуру, видят систему целиком, эффективно управляют агентами - примерно как управляют джунами.&lt;/p&gt;
&lt;p&gt;А mid-level? Они сформировались без LLM, но еще не набрали достаточно опыта, чтобы эффективно ими управлять. Застряли между двумя мирами.&lt;/p&gt;
&lt;p&gt;На практике я вижу это так: mid-level разработчик умеет писать код. Хорошо умеет. Но когда LLM генерирует код быстрее - &amp;ldquo;умение писать код&amp;rdquo; перестает быть конкурентным преимуществом. А понимание &amp;ldquo;зачем этот код нужен&amp;rdquo;, &amp;ldquo;как он вписывается в систему&amp;rdquo;, &amp;ldquo;какие trade-offs мы принимаем&amp;rdquo; - это уже территория сеньоров.&lt;/p&gt;
&lt;p&gt;Один из участников рассказал показательную историю: сеньоры в их компании были резко против LLM. Но когда их заставили поработать руками - треть моментально стала pro-LLM. Практический опыт важнее теоретических страхов. Как пошутили на встрече, некоторые негативные мнения о LLM &amp;ldquo;остались в январе&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="что-делать-mid-level"&gt;Что делать mid-level
&lt;/h3&gt;&lt;p&gt;Если ты mid-level - у тебя есть окно. Не в том, чтобы учиться промптить. А в том, чтобы быстрее набирать архитектурное мышление: понимание систем, trade-offs, бизнес-контекста. То, что LLM пока делает плохо.&lt;/p&gt;
&lt;h2 id="cognitive-debt-опаснее-technical-debt"&gt;Cognitive debt опаснее technical debt
&lt;/h2&gt;&lt;p&gt;Это, на мой взгляд, самый ценный тезис. Фаулер ссылается на исследование Margaret-Anne Storey и проводит параллель с technical debt.&lt;/p&gt;
&lt;p&gt;Её кейс: студенческие команды строили продукт, генерируя код с помощью LLM. К 7-8 неделе одна команда уперлась в стену - любое изменение ломало что-то неожиданное. Сначала обвинили технический долг. Но реальная проблема была в другом: никто в команде не мог объяснить, почему были приняты те или иные решения. Shared understanding - общее понимание системы - фрагментировалось и исчезло.&lt;/p&gt;
&lt;p&gt;Фаулер разделяет это на два слоя:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cruft&lt;/strong&gt; (хлам в коде) -&amp;gt; в когнитивной сфере это &lt;strong&gt;ignorance&lt;/strong&gt; (невежество) - незнание кода и домена&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debt&lt;/strong&gt; (долг как метафора стоимости) -&amp;gt; либо платишь &amp;ldquo;проценты&amp;rdquo; (каждое изменение дороже), либо &amp;ldquo;гасишь тело&amp;rdquo; (инвестируешь в понимание)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Это точное попадание в то, что я вижу при приемке проектов. Не раз сталкивался с ситуацией: код написан грамотно, тесты есть, линтеры проходят - но никто не может объяснить, почему выбрана такая декомпозиция сервисов. Или зачем нужен этот промежуточный слой. Документация говорит &amp;ldquo;что&amp;rdquo;, но не говорит &amp;ldquo;зачем&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;С LLM-агентами cognitive debt будет масштабироваться: код генерируется быстрее, чем команда успевает его осмыслить. Скорость создания кода перестает быть ограничителем. Ограничителем становится скорость понимания.&lt;/p&gt;
&lt;h3 id="как-с-этим-работать"&gt;Как с этим работать
&lt;/h3&gt;&lt;p&gt;Несколько практик, которые помогают:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ADR (Architecture Decision Records)&lt;/strong&gt; - фиксируем не &amp;ldquo;что решили&amp;rdquo;, а &amp;ldquo;почему решили и какие варианты отвергли&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Коучинговые вопросы на ревью&lt;/strong&gt; - вместо &amp;ldquo;используй Strategy pattern&amp;rdquo; спрашивать &amp;ldquo;какие варианты ты рассмотрел? какие trade-offs?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Обязательный контекст в PR&lt;/strong&gt; - не просто diff, а &amp;ldquo;зачем&amp;rdquo; это изменение нужно&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если LLM генерирует код - пусть генерирует и ADR к нему. Хотя бы черновик для ревью.&lt;/p&gt;
&lt;h2 id="devex-и-agent-experience---это-круг"&gt;DevEx и Agent Experience - это круг
&lt;/h2&gt;&lt;p&gt;Laura Tacho сказала фразу, которая заслуживает стать мемом:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Venn Diagram of Developer Experience and Agent Experience is a circle&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Всё, за что годами боролись в developer experience - гладкий тулинг, понятная документация, чистая модульность, осмысленные имена - оказывается, помогает и LLM-агентам. Хорошая модульность и descriptive naming так же полезны для трансформера, как и для &amp;ldquo;более мягких нейронных сетей&amp;rdquo; (мозгов).&lt;/p&gt;
&lt;p&gt;И горькая ирония: менеджмент готов инвестировать в &amp;ldquo;smooth path для LLM&amp;rdquo;, но не готов был делать это для людей. Экзекьютивам не жалко денег на роботов, но жалко на разработчиков.&lt;/p&gt;
&lt;p&gt;Из моего опыта: чистая архитектура с явными зависимостями, маленькими интерфейсами и четкими слоями - это ровно то, что позволяет LLM-агенту эффективно работать с кодобазой. Когда я настраивал Claude Code для работы со своими проектами, разница была заметна сразу: в проекте с чистой архитектурой агент находит нужный контекст за секунды. В проекте-монолите с неявными зависимостями - путается, галлюцинирует, предлагает изменения не в тех файлах.&lt;/p&gt;
&lt;p&gt;Вывод неутешительный и простой: если ваша кодобаза - хаос для людей, она будет хаосом и для агентов. Инвестиции в DevEx теперь имеют двойной ROI.&lt;/p&gt;
&lt;h2 id="supervisory-programming-и-менеджерское-выгорание"&gt;Supervisory programming и менеджерское выгорание
&lt;/h2&gt;&lt;p&gt;Камилла Фурнье заметила:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The part of &amp;ldquo;everyone becomes a manager&amp;rdquo; in AI that I didn&amp;rsquo;t really think about until now was the mental fatigue of context switching&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Тренд &amp;ldquo;один программист управляет несколькими агентами&amp;rdquo; несет с собой ровно ту же болезнь, от которой годами страдают менеджеры: усталость от переключения контекста. Держать в голове 5 параллельных задач, ревьюить результаты из разных контекстов, ловить ошибки в коде, который ты не писал - это менеджмент, а не программирование.&lt;/p&gt;
&lt;p&gt;Фаулер осторожно предполагает, что два человека + агенты могут быть эффективнее, чем один человек + много агентов. Парное программирование нового формата: два мозга ловят ошибки &amp;ldquo;джинна&amp;rdquo; лучше, чем один.&lt;/p&gt;
&lt;p&gt;Мне эта мысль близка. В коучинге есть концепция: сам себе коучем быть нельзя, нужен внешний наблюдатель. С кодом похоже - второй человек видит то, что ты пропустил. А когда агенты генерируют код быстрее, чем один человек может ревьюить - второй мозг не роскошь, а необходимость.&lt;/p&gt;
&lt;h3 id="workload-creep"&gt;Workload creep
&lt;/h3&gt;&lt;p&gt;Фаулер также цитирует исследование из Harvard Business Review: в компании на 200 человек после внедрения AI сотрудники стали работать быстрее, брать больше задач, работать больше часов - часто без просьбы. Звучит как мечта менеджера. Но за первоначальным всплеском приходит: cognitive fatigue, выгорание, ухудшение качества решений.&lt;/p&gt;
&lt;p&gt;Это классический паттерн. Новый инструмент дает эйфорию -&amp;gt; берешь больше -&amp;gt; не замечаешь, как нагрузка выросла -&amp;gt; burnout.&lt;/p&gt;
&lt;h2 id="размер-команд"&gt;Размер команд
&lt;/h2&gt;&lt;p&gt;Уменьшатся ли команды? Фаулер склоняется к &amp;ldquo;нет&amp;rdquo;. Two-pizza teams (5-8 человек) останутся примерно того же размера - но будут делать значительно больше. Есть что-то фундаментальное в размере команды, что балансирует выгоды сотрудничества с издержками координации. LLM не едят пиццу, но они и не добавляют в team dynamics.&lt;/p&gt;
&lt;h2 id="будущее-ide"&gt;Будущее IDE
&lt;/h2&gt;&lt;p&gt;Отдельная тема - будущее IDE. LLM не заменяют IDE, а встраиваются в них. Переименовать функцию через LLM - это как забивать гвоздь микроскопом. Но LLM может оркестрировать инструменты IDE: увидеть, что &amp;ldquo;person&amp;rdquo; нужно переименовать в &amp;ldquo;contact&amp;rdquo; во всех контекстах (функции, поля, документация, тесты), и использовать детерминистические рефакторинги IDE для каждого из них.&lt;/p&gt;
&lt;p&gt;Мне как пользователю Emacs это особенно резонирует. IDE - это мощный инструмент, но мало кто использует его на полную. LLM может стать тем слоем, который знает возможности IDE лучше пользователя и подсказывает, когда использовать LLM, когда - детерминистический рефакторинг, а когда - их комбинацию.&lt;/p&gt;
&lt;h2 id="итого"&gt;Итого
&lt;/h2&gt;&lt;p&gt;Главные мысли из текста Фаулера:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Mid-level под ударом&lt;/strong&gt; - не джуны, как все думали. Окно для роста: быстрее набирать архитектурное мышление&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cognitive debt &amp;gt; technical debt&lt;/strong&gt; - с LLM код генерируется быстрее, чем осмысляется. Ограничитель теперь - понимание, а не написание&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DevEx = Agent Experience&lt;/strong&gt; - инвестиции в чистую архитектуру дают двойной ROI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supervisory programming = менеджерское выгорание&lt;/strong&gt; - context switching утомляет. Пара людей + агенты может быть лучше одиночки + много агентов&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workload creep&lt;/strong&gt; - эйфория от AI -&amp;gt; перегрузка -&amp;gt; burnout. Классика&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Текст Фаулера ценен тем, что он не про хайп и не про страх. Он про трезвую оценку: что меняется, что остается, и какие новые проблемы приходят вместе с новыми возможностями.&lt;/p&gt;
&lt;p&gt;А ты замечаешь cognitive debt в своих проектах?&lt;/p&gt;</description></item><item><title>Архитектура памяти для AI-агентов: как я научил Claude Code помнить</title><link>https://mshogin.ru/blog/memory-architecture-for-ai-agents/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/memory-architecture-for-ai-agents/</guid><description>&lt;img src="https://mshogin.ru/blog/memory-architecture-for-ai-agents/cover.svg" alt="Featured image of post Архитектура памяти для AI-агентов: как я научил Claude Code помнить" /&gt;&lt;h2 id="боль-агент-с-амнезией"&gt;Боль: агент с амнезией
&lt;/h2&gt;&lt;p&gt;Каждый, кто работает с AI-агентами, знает ощущение: ты объяснил контекст проекта, показал структуру, обсудил решения - а в следующей сессии агент всё забыл. Опять здрасьте. Опять &amp;ldquo;расскажите про ваш проект&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Это как работать с архитектором, у которого каждое утро стирается память. Он талантлив, быстр, но каждый день - новый человек.&lt;/p&gt;
&lt;p&gt;У меня 12+ активных контекстов: рабочие проекты (Wildberries), личные проекты (aitrader, archlint), блог, коучинговая практика, обучение, целеполагание. Переключение между ними без памяти превращалось в ритуал: 5-10 минут на &amp;ldquo;загрузку контекста&amp;rdquo; в начале каждой сессии.&lt;/p&gt;
&lt;p&gt;Стало ясно: нужна архитектура памяти.&lt;/p&gt;
&lt;h2 id="как-устроена-человеческая-память"&gt;Как устроена человеческая память
&lt;/h2&gt;&lt;p&gt;Прежде чем проектировать, я посмотрел на то, как работает память у людей. Параллельно с архитектурой я изучаю коучинг - и там постоянно всплывает тема того, как люди обрабатывают информацию. Заметил структурное сходство.&lt;/p&gt;
&lt;div class="mermaid"&gt;
flowchart TD
A["Входящие данные"] --&gt; B["Рабочая память\n(Short-Term)\nАктивные задачи,\nтекущий контекст"]
B --&gt; C{"Фильтр ценности\n(еженедельный ревью)"}
C --&gt;|"Ценно"| D["Долговременная память\n(Long-Term)\nПаттерны, принципы, уроки"]
C --&gt;|"Обработано"| E["Архив\n(забыто, но доступно)"]
&lt;/div&gt;
&lt;p&gt;У людей так же:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Рабочая память&lt;/strong&gt; (Short-Term) - то, с чем работаешь прямо сейчас. Ограничена: ~7 элементов одновременно. Через 3-6 месяцев без обращения - забывается.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Долговременная память&lt;/strong&gt; (Long-Term) - дистиллированные знания. Не &amp;ldquo;что я делал во вторник&amp;rdquo;, а &amp;ldquo;какой паттерн я увидел за три месяца работы&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Архив&lt;/strong&gt; - не удалено, но не на поверхности. Доступно при целенаправленном поиске.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ключевой момент: переход из Short-Term в Long-Term - не автоматический. Нужна осознанная обработка. В коучинге это рефлексия. В моей системе - еженедельный ревью.&lt;/p&gt;
&lt;h2 id="архитектура-решения"&gt;Архитектура решения
&lt;/h2&gt;&lt;h3 id="три-уровня-памяти"&gt;Три уровня памяти
&lt;/h3&gt;&lt;p&gt;Я реализовал три уровня, зеркалирующих человеческую модель:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/my/org-roam/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-- Short-Term-Memory/ # Рабочая память (3-6 месяцев)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Wildberries/ # Рабочий контекст
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Coaching/ # Коучинговые сессии
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- daily/ # Ежедневные заметки
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- todos.md # Задачи
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-- Long-Term-Memory/ # Долговременная память
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Coaching/ # Методики, паттерны, кейсы
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Architecture/ # Архитектурные решения
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Posts/ # Публикации
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| +-- Personal/ # Личные принципы
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-- Archive/ # Архив (по месяцам)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-- 2026/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-- 01-January/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-- 02-February/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Short-Term&lt;/strong&gt; - inbox для всего нового. Сырые заметки, текущие задачи, эксперименты. Максимум 50 активных файлов. Если больше - пора делать ревью.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Long-Term&lt;/strong&gt; - база знаний. Сюда попадает только то, что прошло фильтр ценности:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Переиспользуемость (применимо 3+ раз)&lt;/li&gt;
&lt;li&gt;Обучающая ценность (ага-момент, важная ошибка)&lt;/li&gt;
&lt;li&gt;Референсная ценность (пример для копирования)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Archive&lt;/strong&gt; - завершенные проекты, обработанные заметки. Не удалено, но убрано с глаз.&lt;/p&gt;
&lt;h3 id="контексты-переключение-за-секунду"&gt;Контексты: переключение за секунду
&lt;/h3&gt;&lt;p&gt;Вторая проблема - переключение между проектами. Решение: система контекстов.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;contexts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;coaching&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;coaching&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Коучинговые сессии и практика развития&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;directory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/my/coaching&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;short_term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/my/org-roam/Short-Term-Memory/Coaching&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;long_term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/my/org-roam/Long-Term-Memory/Coaching&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;archlint&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;archlint&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ArchLint - линтер архитектуры Go проектов&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;directory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/my/archlint&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;short_term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;long_term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Каждый контекст знает:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;directory&lt;/strong&gt; - рабочая директория проекта&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.short_term&lt;/strong&gt; - где хранить текущие заметки&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;memory.long_term&lt;/strong&gt; - где хранить дистиллированные знания&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Переключение: одна команда &lt;code&gt;/w coaching&lt;/code&gt; - и агент знает где он, что помнить, какие файлы читать.&lt;/p&gt;
&lt;h3 id="индекс-активностей-навигация-по-всему"&gt;Индекс активностей: навигация по всему
&lt;/h3&gt;&lt;p&gt;Контексты решают проблему &amp;ldquo;где я&amp;rdquo;. Но остается вопрос: &amp;ldquo;где искать информацию по теме X?&amp;rdquo;. Для этого - глобальный индекс активностей.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### Коучинг / Практика
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Keywords: сессия, клиент, практика, рефлексия
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Файлы:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; Практика: ~/my/org-roam/Long-Term-Memory/Coaching/03-Практика/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; Гайды сессий: ~/my/org-roam/Long-Term-Memory/Coaching/session-guides/INDEX.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### Здоровье и психология
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Keywords: психолог, здоровье, границы, стыд
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Файлы:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; Темы: ~/my/org-roam/Long-Term-Memory/Темы для психолога.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Связанные активности: Коучинг / Запросы для проработки
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Принцип работы:&lt;/p&gt;
&lt;div class="mermaid"&gt;
flowchart TD
A["Промпт пользователя"] --&gt; B["Поиск по keywords\nв memory-index.md"]
B --&gt; C{"Найдена\nактивность?"}
C --&gt;|"Да"| D["Работай в её контексте"]
C --&gt;|"Нет"| E["Спроси пользователя,\nпредложи варианты"]
&lt;/div&gt;
&lt;h3 id="кросс-контекстный-поиск-связи-между-контекстами"&gt;Кросс-контекстный поиск: связи между контекстами
&lt;/h3&gt;&lt;p&gt;Самая интересная архитектурная задача. Информация часто пересекает границы контекстов.&lt;/p&gt;
&lt;p&gt;Пример: тема &amp;ldquo;границы&amp;rdquo; может всплыть в коучинге (запрос клиента), в психологии (тема для терапии), в рабочем контексте (обратная связь коллеге). Как не потерять связь?&lt;/p&gt;
&lt;p&gt;Решение: &lt;strong&gt;кросс-ссылки на уровне глобального индекса&lt;/strong&gt;, а не дублирование информации между контекстными индексами.&lt;/p&gt;
&lt;div class="mermaid"&gt;
flowchart TD
G["memory-index.md\n(глобальный индекс)"] --&gt; C1["Coaching\nINDEX.md"]
G --&gt; C2["Здоровье\n(файлы)"]
G --&gt; C3["Работа\nINDEX.md"]
G --&gt; C4["..."]
C1 -.-|"кросс-ссылка"| C2
C2 -.-|"кросс-ссылка"| C3
style G fill:#0f3460,color:#fff
style C1 fill:#533483,color:#fff
style C2 fill:#533483,color:#fff
style C3 fill:#533483,color:#fff
style C4 fill:#333,color:#999
&lt;/div&gt;
&lt;p&gt;Правило: каждый контекстный индекс отвечает только за свой контекст. Связь между контекстами - через поле &amp;ldquo;Связанные активности&amp;rdquo; в глобальном индексе.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Кросс-ссылки между активностями
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Активность A | Активность B | Связь |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|--------------------------|------------------------|-------------------------------------|
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Коучинг / Запросы | Здоровье и психология | Запрос &lt;span class="ni"&gt;#11&lt;/span&gt; = Тема &lt;span class="ni"&gt;#2&lt;/span&gt; (границы) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Семья | Коучинг / Запросы | Запрос &lt;span class="ni"&gt;#3&lt;/span&gt; (темы с супругой) |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Антипаттерн: дублировать ссылки на &amp;ldquo;Темы для психолога&amp;rdquo; внутри Coaching/INDEX.md. Это нарушает single responsibility - индекс коучинга не должен знать про психологию. Связь - только через глобальный уровень.&lt;/p&gt;
&lt;h2 id="жизненный-цикл-знаний"&gt;Жизненный цикл знаний
&lt;/h2&gt;&lt;p&gt;Данные без обработки - мусор. Система памяти работает, только если есть процесс трансформации.&lt;/p&gt;
&lt;div class="mermaid"&gt;
flowchart LR
subgraph ST ["Short-Term (сырые данные)"]
A1["Заметки по сессии\nс клиентом X"]
A2["Тикеты проекта\nархитектуры"]
A3["Исследование\nTarjan алгоритма"]
end
subgraph LT ["Long-Term (знания)"]
B1["Паттерн: Работа с\nпрокрастинацией\nчерез ценности"]
B2["ADR-0042: Выбор\nмежду монолитом\nи микросервисами"]
B3["Библиотека\nалгоритмов\nпоиска циклов"]
end
A1 --&gt;|"трансформация"| B1
A2 --&gt;|"трансформация"| B2
A3 --&gt;|"трансформация"| B3
&lt;/div&gt;
&lt;h3 id="критерии-перехода-в-long-term"&gt;Критерии перехода в Long-Term
&lt;/h3&gt;&lt;p&gt;Не всё заслуживает долговременной памяти. Четыре фильтра:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Переиспользуемость&lt;/strong&gt; - применимо 3+ раз (методики, шаблоны, чек-листы)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Обучающая ценность&lt;/strong&gt; - ага-момент, важная ошибка, смена парадигмы&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Референсная ценность&lt;/strong&gt; - пример для копирования (код, кейс, формат)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Паттерн&lt;/strong&gt; - повторяющаяся проблема + решение&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Всё остальное - одноразовые заметки, устаревшие TODO, рутинные логи - удаляется или уходит в архив.&lt;/p&gt;
&lt;h3 id="еженедельный-ревью"&gt;Еженедельный ревью
&lt;/h3&gt;&lt;p&gt;Раз в неделю - 30-40 минут на обработку Short-Term:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Просмотреть файлы за неделю&lt;/li&gt;
&lt;li&gt;Выделить ценное для Long-Term&lt;/li&gt;
&lt;li&gt;Трансформировать: сырые данные -&amp;gt; структурированные знания&lt;/li&gt;
&lt;li&gt;Архивировать обработанное&lt;/li&gt;
&lt;li&gt;Удалить мусор&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Метрики здоровой системы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Short-Term: 30-50 активных файлов&lt;/li&gt;
&lt;li&gt;Long-Term: +2-5 новых записей в неделю&lt;/li&gt;
&lt;li&gt;Archive: регулярное пополнение&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="параллель-с-коучингом"&gt;Параллель с коучингом
&lt;/h2&gt;&lt;p&gt;Когда я проектировал эту систему, я заметил параллели с тем, что изучаю в коучинге.&lt;/p&gt;
&lt;h3 id="short-term--рабочая-осознанность"&gt;Short-Term = Рабочая осознанность
&lt;/h3&gt;&lt;p&gt;Когда человек разбирается в сложной ситуации, он сначала выгружает всё, что на поверхности: факты, эмоции, людей, обстоятельства. Это рабочая память - хаотичная, перегруженная, но актуальная. Short-Term Memory в моей системе работает так же: собирает всё, что сейчас активно, без фильтрации.&lt;/p&gt;
&lt;h3 id="long-term--мудрость-через-рефлексию"&gt;Long-Term = Мудрость через рефлексию
&lt;/h3&gt;&lt;p&gt;Long-Term - это не &amp;ldquo;запомнить всё&amp;rdquo;. Это &amp;ldquo;понять, что важно&amp;rdquo;. Лучшие инсайты приходят не в момент обсуждения, а позже - когда человек рефлексирует и видит паттерн. Еженедельный ревью - это та самая рефлексия, только для базы знаний.&lt;/p&gt;
&lt;h3 id="кросс-контекст--системное-мышление"&gt;Кросс-контекст = Системное мышление
&lt;/h3&gt;&lt;p&gt;Проблема редко живет в одном контексте. Одна и та же тема может всплыть и на работе, и в личных проектах, и в обучении. Кросс-ссылки в системе памяти - это способ видеть одну тему с разных сторон, расширить перспективу.&lt;/p&gt;
&lt;h3 id="architect-of-thinking"&gt;Architect of Thinking
&lt;/h3&gt;&lt;p&gt;Изучая коучинг, я разработал авторский фреймворк на стыке архитектуры и работы с мышлением - Architect of Thinking. Формула:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Контекст -&amp;gt; Цель -&amp;gt; Карта -&amp;gt; Узел -&amp;gt; Решение -&amp;gt; Шаг&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Это ровно то, как архитектор работает с системой: сначала изучи (контекст), определи цель, построй карту, найди bottleneck (узел), спроектируй решение, сделай первый шаг. Тот же подход - к человеческим задачам.&lt;/p&gt;
&lt;h2 id="результаты"&gt;Результаты
&lt;/h2&gt;&lt;h3 id="до-системы-памяти"&gt;До системы памяти:
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;5-10 минут на &amp;ldquo;загрузку контекста&amp;rdquo; каждую сессию&lt;/li&gt;
&lt;li&gt;Потеря решений между сессиями&lt;/li&gt;
&lt;li&gt;Дублирование работы&lt;/li&gt;
&lt;li&gt;Невозможность связать темы между контекстами&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="после"&gt;После:
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Переключение контекста за 1 команду&lt;/li&gt;
&lt;li&gt;Знания накапливаются и структурируются&lt;/li&gt;
&lt;li&gt;Связи между контекстами через кросс-ссылки&lt;/li&gt;
&lt;li&gt;Еженедельный ревью превращает данные в знания&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="что-дальше"&gt;Что дальше
&lt;/h2&gt;&lt;p&gt;Система живая - она развивается вместе с моими проектами. Следующие шаги:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Автоматизация ревью (AI-помощник для еженедельной обработки Short-Term)&lt;/li&gt;
&lt;li&gt;Семантический поиск по Long-Term Memory&lt;/li&gt;
&lt;li&gt;Версионирование знаний (как знания меняются со временем)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Но главный урок не технический. Проектируя память для AI-агента, я лучше понял, как работает моя собственная. И это, пожалуй, самый ценный побочный эффект.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Исходный код и конфигурации описаны для Claude Code (Anthropic CLI). Подход применим к любому AI-агенту с файловой системой.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Spec-Driven Development: контроль AI-кодогенерации</title><link>https://mshogin.ru/blog/spec-driven-development/</link><pubDate>Tue, 09 Dec 2025 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/spec-driven-development/</guid><description>&lt;img src="https://mshogin.ru/blog/spec-driven-development/cover.svg" alt="Featured image of post Spec-Driven Development: контроль AI-кодогенерации" /&gt;&lt;h2 id="в-этой-статье"&gt;В этой статье
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="#%d0%bf%d1%80%d0%be%d0%b1%d0%bb%d0%b5%d0%bc%d0%b0" &gt;Проблема&lt;/a&gt;: большие MR и размытые требования&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%d0%b8%d0%b4%d0%b5%d1%8f" &gt;Идея&lt;/a&gt;: спецификации как контракт&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%d0%bc%d0%b5%d1%82%d0%be%d0%b4" &gt;Метод&lt;/a&gt;: структура и размеры спецификаций&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%d1%87%d1%82%d0%be-%d0%b4%d0%b5%d0%bb%d0%b0%d0%b5%d1%82-ai" &gt;Что делает AI&lt;/a&gt;: редактор и исполнитель&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%d1%8d%d0%ba%d1%81%d0%bf%d0%b5%d1%80%d0%b8%d0%bc%d0%b5%d0%bd%d1%82" &gt;Эксперимент&lt;/a&gt;: 85% воспроизводимости за 20 минут&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%d0%be%d0%b3%d1%80%d0%b0%d0%bd%d0%b8%d1%87%d0%b5%d0%bd%d0%b8%d1%8f" &gt;Ограничения&lt;/a&gt; и &lt;a class="link" href="#%d0%b8%d1%82%d0%be%d0%b3%d0%b8" &gt;Итоги&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="проблема"&gt;Проблема
&lt;/h2&gt;&lt;h3 id="большие-mr"&gt;Большие MR
&lt;/h3&gt;&lt;p&gt;4000 строк в одном MR. Три часа на ревью, 12 замечаний, исправления - ещё 800 строк. На четвёртом заходе я закрыл вкладку и понял: проблема не в коде, а в том, что никто не знал, что именно нужно было написать.&lt;/p&gt;
&lt;p&gt;Если ты работаешь с большими кодовыми базами, ситуация знакомая. Большие MR - симптом. Когда непонятно, что именно нужно сделать, разработчик пишет больше кода, чем требуется. Добавляет на всякий случай. Покрывает сценарии, которые никто не просил. MR растёт не потому что задача большая, а потому что границы размыты.&lt;/p&gt;
&lt;p&gt;Другая причина - иллюзия, что проще сделать всё в одной задаче, чем декомпозировать. Кажется, что разбиение создаёт лишнюю работу. На практике монолитный MR на 4000 строк никто не может нормально проверить, и баги просачиваются в продакшн.&lt;/p&gt;
&lt;h3 id="ai-кодогенерация"&gt;AI-кодогенерация
&lt;/h3&gt;&lt;p&gt;С AI эта проблема становится критичнее. Агент пишет код быстро, но если требования размыты - генерирует то же самое: много кода на всякий случай.&lt;/p&gt;
&lt;p&gt;Я пользуюсь AI ежедневно. Claude Code - основной инструмент, в который можно подключить разные модели: Anthropic, DeepSeek, GPT-семейство, локальные через Ollama. И в какой-то момент заметил закономерность: чем точнее формулирую задачу, тем лучше результат. Промпты становились всё более структурированными - простые инструкции, потом шаблоны, потом что-то похожее на техническое задание.&lt;/p&gt;
&lt;p&gt;Где проблема? Сначала я думал, что ленюсь давать AI детальные инструкции. Потом решил, что AI Agent собирает недостаточный контекст - нужен RAG или что-то подобное. В итоге понял, что проблема в обоих местах: создавать полные инструкции кажется оверкилом, а как помочь агенту собрать нужный контекст - непонятно.&lt;/p&gt;
&lt;p&gt;Но главное - нечего проверять. Нет артефакта, на который можно указать и сказать: здесь написано одно, а сделано другое.&lt;/p&gt;
&lt;h2 id="идея"&gt;Идея
&lt;/h2&gt;&lt;p&gt;Идея не новая - в индустрии давно говорят о spec-first подходе. Я долго хотел попробовать и наконец решил проверить.&lt;/p&gt;
&lt;p&gt;Если формализовать требования в виде спецификации до начала кодирования, то:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI Agent будет генерировать более предсказуемый код&lt;/li&gt;
&lt;li&gt;Результат можно будет валидировать против спецификации&lt;/li&gt;
&lt;li&gt;Архитектура останется контролируемой&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Когда AI Agent генерирует сотни строк кода за минуту, единственный способ контролировать результат - иметь формальное описание того, что должно получиться, и инструмент, который проверит соответствие реализации спецификации. О втором - в отдельной статье.&lt;/p&gt;
&lt;h2 id="метод"&gt;Метод
&lt;/h2&gt;&lt;p&gt;Я решил проверить гипотезу на реальном проекте - инструменте для построения архитектурных графов из Go кода. Правило простое: ни одной строчки кода без спецификации.&lt;/p&gt;
&lt;p&gt;Первая спецификация - &lt;a class="link" href="https://github.com/mshogin/archlint/blob/main/specs/done/0001-init-project.md" target="_blank" rel="noopener"
&gt;создать пустой проект&lt;/a&gt; на Go со стандартным layout. Вторая - &lt;a class="link" href="https://github.com/mshogin/archlint/blob/main/specs/done/0003-data-model.md" target="_blank" rel="noopener"
&gt;модель графа&lt;/a&gt;. Третья - &lt;a class="link" href="https://github.com/mshogin/archlint/blob/main/specs/done/0004-go-analyzer.md" target="_blank" rel="noopener"
&gt;анализатор Go кода&lt;/a&gt;. За несколько дней накопилось 10 завершённых спецификаций.&lt;/p&gt;
&lt;h3 id="структура-хранения"&gt;Структура хранения
&lt;/h3&gt;&lt;p&gt;Kanban-подобная организация через файловую систему:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;specs/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── todo/ # очередь задач
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── 0010-feature-x.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── 0020-feature-y.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── inprogress/ # в работе (максимум одна - WIP limit)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── 0005-current.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── done/ # выполнено
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 0001-init-project.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 0003-data-model.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 0004-go-analyzer.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Приоритизация через числовой префикс: меньше число - выше приоритет. Переход между состояниями - перемещение файла между директориями.&lt;/p&gt;
&lt;div class="mermaid"&gt;
graph LR
TODO["todo/"] --&gt; INPROGRESS["inprogress/"]
INPROGRESS --&gt; DONE["done/"]
&lt;/div&gt;
&lt;h3 id="размеры-спецификаций"&gt;Размеры спецификаций
&lt;/h3&gt;&lt;p&gt;Классификация по времени на написание спецификации (T-shirt sizing):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;S (Small)&lt;/strong&gt; - до 10 минут&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M (Medium)&lt;/strong&gt; - 10-20 минут&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L (Large)&lt;/strong&gt; - больше 20 минут&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Размер определяет глубину проработки. S-задача: Problem Statement и 5 Acceptance Criteria. L-задача: полные UML/C4 диаграммы, детальные Requirements, 15+ критериев приёмки. Корреляция между размером спецификации и предсказуемостью результата - прямая.&lt;/p&gt;
&lt;p&gt;Если начинаешь с нуля - попробуй с S-спецификации. Это минимальные затраты на эксперимент.&lt;/p&gt;
&lt;h3 id="пример-s-спецификации-инициализация-проекта"&gt;Пример S-спецификации: инициализация проекта
&lt;/h3&gt;&lt;div class="expandable-code" id="expandable-1777685812527455000"&gt;
&lt;pre class="expandable-code-pre" style="max-height: 18em; overflow: hidden;"&gt;&lt;code class="language-markdown"&gt;
# Spec 0001: Initialize Standard Golang Project Layout
**Metadata:**
- Priority: 0001
- Status: Done
- Effort: S
## Overview
### Problem Statement
Необходимо создать базовую структуру Go проекта для инструмента
archlint согласно стандартным практикам разработки.
### Solution Summary
Инициализировать Go module и создать минимальную структуру проекта.
## Requirements
### R1: Go Module Initialization
- Инициализировать Go module с именем github.com/mshogin/archlint
### R2: Minimal Directory Structure
- Создать cmd/archlint/ для точки входа
- Создать internal/ для приватного кода
- Создать pkg/ для публичных библиотек
## Acceptance Criteria
- [x] AC1: go.mod создан с module path github.com/mshogin/archlint
- [x] AC2: cmd/archlint/main.go существует и компилируется
- [x] AC3: Директории internal/ и pkg/ существуют
&lt;/code&gt;&lt;/pre&gt;
&lt;button class="expandable-code-btn" onclick="toggleExpandable('expandable-1777685812527455000')"&gt;Показать полностью&lt;/button&gt;
&lt;/div&gt;
&lt;style&gt;
.expandable-code {
position: relative;
margin-bottom: 1em;
}
.expandable-code-pre {
margin-bottom: 0 !important;
transition: max-height 0.3s ease;
}
.expandable-code.expanded .expandable-code-pre {
max-height: none !important;
}
.expandable-code-btn {
display: block;
width: 100%;
padding: 0.5em;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
cursor: pointer;
font-size: 0.9em;
color: var(--accent-color);
}
.expandable-code-btn:hover {
background: var(--border-color);
}
.expandable-code.expanded .expandable-code-btn {
display: none;
}
&lt;/style&gt;
&lt;script&gt;
function toggleExpandable(id) {
var el = document.getElementById(id);
el.classList.add('expanded');
}
&lt;/script&gt;
&lt;p&gt;Минимум деталей, максимум конкретики. AI получает чёткие границы задачи.&lt;/p&gt;
&lt;h3 id="пример-m-спецификации-команда-collect"&gt;Пример M-спецификации: команда collect
&lt;/h3&gt;&lt;p&gt;Средние задачи требуют диаграмм. Я экспериментировал с Sequence-диаграммами - отправлял их агенту вместе с требованиями. Заметил, что по ним AI Agent выдаёт в целом то, что ожидается.&lt;/p&gt;
&lt;div class="expandable-code" id="expandable-1777685812527475000"&gt;
&lt;pre class="expandable-code-pre" style="max-height: 18em; overflow: hidden;"&gt;&lt;code class="language-markdown"&gt;
# Spec 0006: Implement Collect Command
**Metadata:**
- Priority: 0006
- Status: Done
- Effort: M
## Overview
### Problem Statement
Необходимо реализовать команду collect для сбора архитектуры
из исходного кода и сохранения графа в YAML файл.
### Solution Summary
Создать подкоманду collect, которая использует GoAnalyzer
для анализа кода и сохраняет результат в YAML формате.
## Architecture
### Sequence Flow (PlantUML)
@startuml
title Sequence: Collect Command
actor User
participant "collectCmd" as CC
participant "GoAnalyzer" as GA
participant "saveGraph" as SG
User -&gt; CC: archlint collect . -o arch.yaml
CC -&gt; GA: Analyze(dir)
GA --&gt; CC: *Graph
CC -&gt; SG: saveGraph(graph)
SG --&gt; CC: nil
CC --&gt; User: "Graph saved to arch.yaml"
@enduml
## Requirements
### R1: Command Definition
var collectCmd = &amp;cobra.Command{
Use: "collect [директория]",
Short: "Сбор архитектуры из исходного кода",
Args: cobra.ExactArgs(1),
RunE: runCollect,
}
### R2: Flags
-o, --output: выходной YAML файл (default: architecture.yaml)
-l, --language: язык программирования (default: go)
## Acceptance Criteria
- [x] AC1: Команда принимает директорию как аргумент
- [x] AC2: Флаги -o и -l работают
- [x] AC3: Результат сохраняется в YAML
- [x] AC4: Выводится статистика по компонентам
&lt;/code&gt;&lt;/pre&gt;
&lt;button class="expandable-code-btn" onclick="toggleExpandable('expandable-1777685812527475000')"&gt;Показать полностью&lt;/button&gt;
&lt;/div&gt;
&lt;style&gt;
.expandable-code {
position: relative;
margin-bottom: 1em;
}
.expandable-code-pre {
margin-bottom: 0 !important;
transition: max-height 0.3s ease;
}
.expandable-code.expanded .expandable-code-pre {
max-height: none !important;
}
.expandable-code-btn {
display: block;
width: 100%;
padding: 0.5em;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
cursor: pointer;
font-size: 0.9em;
color: var(--accent-color);
}
.expandable-code-btn:hover {
background: var(--border-color);
}
.expandable-code.expanded .expandable-code-btn {
display: none;
}
&lt;/style&gt;
&lt;script&gt;
function toggleExpandable(id) {
var el = document.getElementById(id);
el.classList.add('expanded');
}
&lt;/script&gt;
&lt;p&gt;Sequence-диаграмма определяет порядок вызовов. AI следует ей буквально.&lt;/p&gt;
&lt;h3 id="пример-l-спецификации-анализатор-go-кода"&gt;Пример L-спецификации: анализатор Go кода
&lt;/h3&gt;&lt;p&gt;Большие задачи требуют нескольких диаграмм - Data Model и Sequence:&lt;/p&gt;
&lt;div class="expandable-code" id="expandable-1777685812527489000"&gt;
&lt;pre class="expandable-code-pre" style="max-height: 18em; overflow: hidden;"&gt;&lt;code class="language-markdown"&gt;
# Spec 0004: Implement Go Code Analyzer
**Metadata:**
- Priority: 0004
- Status: Done
- Effort: L
## Overview
### Problem Statement
Необходимо реализовать анализатор Go кода, который парсит исходный код
с помощью AST и строит граф зависимостей между компонентами.
### Solution Summary
Создать GoAnalyzer в пакете internal/analyzer, который использует
go/ast и go/parser для анализа Go файлов и построения графа.
## Architecture
### Data Model
@startuml
class GoAnalyzer {
-packages: map[string]*PackageInfo
-types: map[string]*TypeInfo
-functions: map[string]*FunctionInfo
-nodes: []model.Node
-edges: []model.Edge
--
+NewGoAnalyzer() *GoAnalyzer
+Analyze(dir string) (*model.Graph, error)
-parseFile(filename string) error
-buildGraph()
}
class PackageInfo {
+Name: string
+Path: string
+Imports: []string
}
class TypeInfo {
+Name: string
+Package: string
+Kind: string
+Fields: []FieldInfo
}
GoAnalyzer "1" *-- "*" PackageInfo
GoAnalyzer "1" *-- "*" TypeInfo
@enduml
### Sequence Diagram
@startuml
title Sequence: Code Analysis
actor User
participant "GoAnalyzer" as GA
participant "go/parser" as GP
participant "buildGraph" as BG
User -&gt; GA: Analyze(dir)
loop For each .go file
GA -&gt; GP: ParseFile(filename)
GP --&gt; GA: *ast.File
GA -&gt; GA: Extract packages, types, functions
end
GA -&gt; BG: buildGraph()
BG --&gt; GA: *Graph
GA --&gt; User: *Graph, nil
@enduml
## Requirements
### R1: AST Parsing
- Парсить все .go файлы в директории
- Извлекать packages, types, functions, methods
### R2: Graph Building
- Создавать Node для каждого компонента
- Создавать Edge для каждой связи (import, calls, uses)
### R3: External Dependencies
- Идентифицировать внешние зависимости
- Помечать их как entity: external
## Acceptance Criteria
- [x] AC1: Анализатор корректно парсит Go код
- [x] AC2: Все типы компонентов извлекаются
- [x] AC3: Все типы связей определяются
- [x] AC4: Внешние зависимости идентифицируются
- [x] AC5: Граф сериализуется в YAML
&lt;/code&gt;&lt;/pre&gt;
&lt;button class="expandable-code-btn" onclick="toggleExpandable('expandable-1777685812527489000')"&gt;Показать полностью&lt;/button&gt;
&lt;/div&gt;
&lt;style&gt;
.expandable-code {
position: relative;
margin-bottom: 1em;
}
.expandable-code-pre {
margin-bottom: 0 !important;
transition: max-height 0.3s ease;
}
.expandable-code.expanded .expandable-code-pre {
max-height: none !important;
}
.expandable-code-btn {
display: block;
width: 100%;
padding: 0.5em;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
cursor: pointer;
font-size: 0.9em;
color: var(--accent-color);
}
.expandable-code-btn:hover {
background: var(--border-color);
}
.expandable-code.expanded .expandable-code-btn {
display: none;
}
&lt;/style&gt;
&lt;script&gt;
function toggleExpandable(id) {
var el = document.getElementById(id);
el.classList.add('expanded');
}
&lt;/script&gt;
&lt;p&gt;Для L-задач несколько диаграмм - необходимость. Data Model, Sequence, Component - вместе они задают архитектуру приложения и управляют зависимостями между компонентами.&lt;/p&gt;
&lt;h2 id="что-делает-ai"&gt;Что делает AI
&lt;/h2&gt;&lt;p&gt;AI ускоряет работу, но архитектуру я не делегирую: решения фиксируются в спецификации, проходят ревью и проверяются валидаторами. Однако решения редко рождаются из воздуха: я приношу первичные варианты и ограничения, агент предлагает альтернативы и подсвечивает слепые зоны. Это влияет на ход мысли, и я это осознаю. Финальное &amp;ldquo;да/нет&amp;rdquo; и ответственность лежит на мне.&lt;/p&gt;
&lt;p&gt;У агента две зоны ответственности.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specification Editor&lt;/strong&gt;
Я диктую голосом сырой поток мыслей (диктую быстрее, чем печатаю). Агент приводит его к &lt;a class="link" href="https://github.com/mshogin/archlint/blob/main/templates/spec-template.md" target="_blank" rel="noopener"
&gt;моему шаблону спецификации&lt;/a&gt;: раскладывает по секциям, уточняет недосказанное, формулирует требования и критерии приемки так, чтобы их можно было проверить. После этого я ревьюю и фиксирую спецификацию как исходный контракт.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implementation Executor&lt;/strong&gt;
Когда спецификация согласована, я отдаю её агенту на реализацию. Агент пишет код по спецификации, а я проверяю результат: ревью, валидация, итерации до тех пор, пока архитектура не станет чистой и предсказуемой.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="mermaid"&gt;
graph LR
A["Идеи/варианты (я)"] --&gt; B["AI дополняет и формализует"]
B --&gt; C["Спецификация"]
C --&gt; D["Ревью и решения (я)"]
D --&gt; E["Реализация (AI)"]
&lt;/div&gt;
&lt;h2 id="эксперимент"&gt;Эксперимент
&lt;/h2&gt;&lt;p&gt;За несколько дней - 10 завершённых спецификаций и работающий проект. Код соответствует архитектуре из диаграмм.&lt;/p&gt;
&lt;p&gt;Чтобы проверить, насколько спецификации самодостаточны, я провёл эксперимент: дал Claude Code пустую директорию и 10 спецификаций из archlint - без доступа к исходному коду. Задача: воссоздать проект с нуля.&lt;/p&gt;
&lt;p&gt;Результат за 20 минут:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;85.5%&lt;/strong&gt; успешность воспроизведения&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100%&lt;/strong&gt; структурная идентичность (директории, файлы, типы)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;23 мутации&lt;/strong&gt; в деталях реализации&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Структура проекта воспроизведена полностью. Все acceptance criteria из спецификаций выполнены. Проект компилируется и проходит тесты.&lt;/p&gt;
&lt;p&gt;Мутации возникли там, где спецификации описывали что делать, но не как. Критический пример: алгоритм построения sequence-диаграммы был реализован иначе - функционально эквивалентно, но с другой логикой обхода стека вызовов. Ещё одна категория мутаций - стилистические: язык комментариев, порядок функций в файлах, именование переменных.&lt;/p&gt;
&lt;p&gt;Вывод для улучшения спецификаций: для критических алгоритмов нужен псевдокод или конкретные примеры входов/выходов. Спецификация что + как даёт более точное воспроизведение, чем только что.&lt;/p&gt;
&lt;p&gt;Полный отчёт с каталогом мутаций: &lt;a class="link" href="https://github.com/mshogin/archlint-reproduction" target="_blank" rel="noopener"
&gt;github.com/mshogin/archlint-reproduction&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="идемпотентность-спецификаций"&gt;Идемпотентность спецификаций
&lt;/h2&gt;&lt;p&gt;Чтобы спецификации оставались воспроизводимыми, все изменения должны проходить через них. Никаких доработок в режиме copilot, никаких чатов с &amp;ldquo;поправь вот это&amp;rdquo;. Каждое изменение - обновление спецификации, затем реализация.&lt;/p&gt;
&lt;p&gt;Это основной вызов. Хочется быстро поправить баг в диалоге, а не возвращаться к спеке. Но каждая такая правка - потеря воспроизводимости.&lt;/p&gt;
&lt;p&gt;Trade-off очевиден:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Нужен результат здесь и сейчас - режим copilot быстрее&lt;/li&gt;
&lt;li&gt;Нужна воспроизводимость - только через спецификации&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Выбор зависит от контекста. Прототип или эксперимент - copilot. Продуктовый код с долгим жизненным циклом - спецификации.&lt;/p&gt;
&lt;h2 id="ограничения"&gt;Ограничения
&lt;/h2&gt;&lt;p&gt;Подход не решает проблемы автоматически. Он не заменяет понимание предметной области, не придумывает требования и не гарантирует хороший дизайн. Он делает ошибки видимыми раньше и заставляет держать архитектуру в рамке.&lt;/p&gt;
&lt;p&gt;Цена: время на написание спецификаций, их ревью и итерации после валидации. Если относиться к спекам формально, всё скатывается обратно в хаос. Честно спроси себя: готов ли ты тратить 10-30 минут на спецификацию, чтобы агент реализовал её за 5-20 минут?&lt;/p&gt;
&lt;p&gt;Детали реализации варьируются. Эксперимент с воспроизводимостью показал 23 мутации - алгоритмы интерпретируются по-разному, стилистика кода отличается. Для критических участков нужен псевдокод, а не только описание.&lt;/p&gt;
&lt;p&gt;Я думаю, что подход работает хорошо там, где есть поставленный, сформированный и рабочий процесс. В процессах с фокусом на дисциплину, на понятные зоны ответственности, ревью, критерии готовности. Можно смотреть на этот процесс как на конвейер, доставляющий ПО 24/7.&lt;/p&gt;
&lt;h2 id="итоги"&gt;Итоги
&lt;/h2&gt;&lt;p&gt;Гипотеза подтвердилась:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AI генерирует более предсказуемый код - да, при наличии диаграмм&lt;/li&gt;
&lt;li&gt;Результат можно валидировать - да, 85.5% воспроизводимость&lt;/li&gt;
&lt;li&gt;Архитектура остаётся контролируемой - да, 100% структурная идентичность&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Суть простая: без спецификации нечего проверять, со спецификацией - есть артефакт для валидации. Не нужен идеальный AI или идеальный промпт.&lt;/p&gt;
&lt;p&gt;Эксперимент продолжается.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Шаблоны и примеры:&lt;/strong&gt; &lt;a class="link" href="https://github.com/mshogin/archlint" target="_blank" rel="noopener"
&gt;github.com/mshogin/archlint&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Если пробуешь spec-driven подход или уже используешь - расскажи в комментариях, что работает, а что нет. Пишу о практике AI-кодогенерации и архитектуре в Telegram: &lt;a class="link" href="https://t.me/MikeShogin" target="_blank" rel="noopener"
&gt;@MikeShogin&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Структурная и поведенческая архитектура: графовый подход к контролю сложности</title><link>https://mshogin.ru/blog/architecture-graphs/</link><pubDate>Tue, 02 Dec 2025 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/architecture-graphs/</guid><description>&lt;img src="https://mshogin.ru/blog/architecture-graphs/cover.svg" alt="Featured image of post Структурная и поведенческая архитектура: графовый подход к контролю сложности" /&gt;&lt;h2 id="почему-вайб-кодинг-не-работает"&gt;Почему вайб-кодинг не работает
&lt;/h2&gt;&lt;p&gt;С появлением AI-агентов разработка программного обеспечения изменилась кардинально. Я, как и многие разработчики, начал активно использовать Claude, Cursor и другие инструменты для автоматизации написания кода. Результаты поначалу впечатляли: за один вечер, занимаясь системным анализом, проектированием архитектуры и промпт-инжинирингом, я мог сгенерировать до 100 000 строк кода.&lt;/p&gt;
&lt;p&gt;Процесс был увлекательным. Не нужно было вручную писать реализацию - достаточно было описать требования, обсудить с AI архитектурное видение, уточнить детали, и код появлялся сам. Я мог работать по вечерам над своими pet-проектами, общаясь с искусственным интеллектом как с коллегой. Это был настоящий вайб-кодинг - приятный, творческий процесс, не отягощённый рутиной.&lt;/p&gt;
&lt;p&gt;Проблемы начались не сразу. Первые несколько дней работа шла гладко: AI быстро генерировал код, тесты проходили, функциональность работала. Но затем, примерно через неделю-две активной разработки, я стал замечать тревожные симптомы.&lt;/p&gt;
&lt;h3 id="симптомы-деградации-кодовой-базы"&gt;Симптомы деградации кодовой базы
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Агенты начали замедляться.&lt;/strong&gt; То, что раньше занимало минуты, теперь требовало десятков минут. AI начинал зацикливаться на простых задачах, генерировать избыточный код, предлагать отрефакторить чуть ли не весь проект для исправления небольшого бага. Контекст становился слишком большим, и агент терялся в собственном коде.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Появились архитектурные антипаттерны.&lt;/strong&gt; Последней каплей стал момент, когда при ревью кода я обнаружил такую конструкцию:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// в основной функции&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// внутри вызывает processData и combine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// func transform(data, input) {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// tmp := processData(input)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// result := combine(tmp, input)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// return reuslt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// func combine(data, input) {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// result := processData(input) // та же функция, те же аргументы!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// return result&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Функция &lt;code&gt;processData&lt;/code&gt; вызывалась трижды с одинаковыми аргументами, но это дублирование было скрыто внутри &lt;code&gt;transform&lt;/code&gt; и &lt;code&gt;combine&lt;/code&gt;. AI не отследил, что результат можно переиспользовать, и генерировал похожий код в разных функциях. Но это была лишь верхушка айсберга.&lt;/p&gt;
&lt;p&gt;Копаясь глубже, я находил:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Функции с 15-20 параметрами, половина из которых передавалась просто насквозь&lt;/li&gt;
&lt;li&gt;Циклические зависимости между пакетами, скрытые через интерфейсы&lt;/li&gt;
&lt;li&gt;Дублирование бизнес-логики в трёх разных местах с небольшими вариациями&lt;/li&gt;
&lt;li&gt;God объекты, которые знали обо всей системе&lt;/li&gt;
&lt;li&gt;Слои абстракций, которые ничего не абстрагировали, а только усложняли код&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Код становился неподдерживаемым.&lt;/strong&gt; Через две недели проект превращался в то, что сложно назвать иначе как клоака. Добавление новой функциональности требовало всё больше времени. AI предлагал решения, которые работали, но ещё сильнее запутывали архитектуру. Каждое изменение тянуло за собой цепочку других изменений в неожиданных местах.&lt;/p&gt;
&lt;p&gt;Я устал создавать проекты, которые живут две недели, а потом становятся legacy. Это были мои собственные pet-проекты, которые я делал для души. Я даже не говорю о production-системах - там такое вообще недопустимо. Хотелось заниматься разработкой и развитием продукта, а не археологическими раскопками в собственном коде.&lt;/p&gt;
&lt;h3 id="остановиться-и-разобраться"&gt;Остановиться и разобраться
&lt;/h3&gt;&lt;p&gt;Я принял решение: нужно прекратить генерировать новые проекты, обречённые стать неподдерживаемыми, и разобраться в проблеме системно. Переключил фокус с кодогенерации на исследовательскую работу.&lt;/p&gt;
&lt;p&gt;Главный вопрос исследования: &lt;strong&gt;как разрабатывать проекты с помощью AI-агентов так, чтобы они оставались развиваемыми и поддерживаемыми?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;После анализа проблемы я пришёл к ключевому выводу: вайб-кодинг без формального контроля архитектуры - это путь в никуда. AI-агенты отлично генерируют код, который работает здесь и сейчас. Но они не видят общую картину архитектуры, не отслеживают накопление технического долга, не замечают появление антипаттернов.&lt;/p&gt;
&lt;p&gt;Необходим инженерный подход: формальное описание архитектуры, автоматическая валидация, метрики качества. Архитектура должна быть явной, проверяемой, контролируемой. Только так можно предотвратить деградацию кодовой базы.&lt;/p&gt;
&lt;p&gt;Я начинаю цикл статей про исследование контроля архитектуры, рассматривая её как графовую структуру данных. В этой первой статье я покажу, как автоматически строить два вида архитектурных графов: структурный (статический) и поведенческий (динамический). Все примеры основаны на реальном проекте &lt;a class="link" href="https://github.com/mshogin/archlint" target="_blank" rel="noopener"
&gt;archlint&lt;/a&gt; - инструменте для автоматического построения и валидации архитектурных графов, который я разработываю в процессе исследования.&lt;/p&gt;
&lt;p&gt;В следующих статьях цикла рассмотрю валидацию архитектурных правил и метрики качества кода на основе теории графов.&lt;/p&gt;
&lt;h2 id="структурная-архитектура-статический-граф-системы"&gt;Структурная архитектура: статический граф системы
&lt;/h2&gt;&lt;p&gt;Когда мы говорим об архитектуре программной системы, мы часто имеем в виду её структуру: из каких компонентов состоит система, как они организованы, как связаны друг с другом. Структурная архитектура - это статическая картина, фиксирующая потенциальные связи между элементами кода.&lt;/p&gt;
&lt;h3 id="зачем-нужна-формальная-модель-структуры"&gt;Зачем нужна формальная модель структуры
&lt;/h3&gt;&lt;p&gt;В традиционной разработке структурная архитектура существует в виде диаграмм в документации, соглашений в команде, знаний в головах опытных разработчиков. Этого хватает, когда код пишут люди, которые понимают контекст и держат в голове общую картину.&lt;/p&gt;
&lt;p&gt;С AI-агентами ситуация другая. Агент работает в ограниченном контексте, не имеет глобального видения системы, не помнит архитектурные решения, принятые неделю назад. Каждый раз, генерируя код, он видит лишь фрагмент системы в окне контекста.&lt;/p&gt;
&lt;p&gt;Результат предсказуем: архитектура деградирует. Появляются циклические зависимости, дублирование кода, нарушается слоистая структура. Неформальные соглашения не работают - их некому соблюдать.&lt;/p&gt;
&lt;p&gt;Формальное решение: сделать архитектуру явной, автоматически проверяемой. Нужна математическая модель, которая точно описывает структуру системы и позволяет автоматически валидировать её корректность.&lt;/p&gt;
&lt;h3 id="граф---формальная-модель"&gt;Граф - формальная модель
&lt;/h3&gt;&lt;p&gt;Структурную архитектуру можно представить через математическую абстракцию - ориентированный граф G = (V, E), где:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V (vertices)&lt;/strong&gt; - множество узлов, представляющих компоненты системы&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E (edges)&lt;/strong&gt; - множество рёбер, представляющих связи между компонентами&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Каждый узел v ∈ V имеет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;id&lt;/strong&gt; - уникальный идентификатор (например, полное имя пакета или функции)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;type&lt;/strong&gt; - тип компонента (package, struct, function, method, interface)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;properties&lt;/strong&gt; - дополнительные свойства (имя файла, строка кода, видимость)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Каждое ребро e ∈ E имеет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;from&lt;/strong&gt; - узел-источник&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;to&lt;/strong&gt; - узел-приёмник&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;type&lt;/strong&gt; - семантика связи (contains, calls, uses, imports, embeds)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Эта простая модель оказывается достаточно мощной для описания реальных систем. Граф позволяет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Визуализировать&lt;/strong&gt; архитектуру на разных уровнях абстракции&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Валидировать&lt;/strong&gt; архитектурные правила (например, &amp;ldquo;слой UI не должен зависеть от слоя DB&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Вычислять метрики&lt;/strong&gt; (связность, цикломатическая сложность, глубина зависимостей)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Отслеживать изменения&lt;/strong&gt; во времени&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="реальный-пример-архитектура-archlint"&gt;Реальный пример: архитектура archlint
&lt;/h3&gt;&lt;p&gt;Рассмотрим структурную архитектуру проекта - archlint. Это инструмент для построения и анализа архитектурных графов, написанный на Go. Давайте посмотрим, как его структура выглядит в виде графа.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Пакетный уровень:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmd/archlint - главный бинарник для сбора архитектуры
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmd/tracelint - линтер для проверки покрытия трейсингом
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;internal/analyzer - анализ исходного кода Go через AST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;internal/model - модель графа архитектуры
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;internal/cli - реализация CLI команд
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;internal/linter - валидация корректности трейсинга
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg/tracer - библиотека для трейсинга выполнения
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tests/testdata/sample - тестовые примеры с инструментацией
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Типы данных:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Типы данных организованы по доменам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Модель графа&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Graph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;представление&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;архитектурного&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;графа&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;узел&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;графа&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;компонент&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;системы&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Edge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ребро&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;графа&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;связь&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;между&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;компонентами&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Анализатор исходного кода&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GoAnalyzer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;главный&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;анализатор&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;кода&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PackageInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;пакете&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;типе&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FunctionInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;функции&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MethodInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;методе&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FieldInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;поле&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;структуры&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CallInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;вызове&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;функции&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Трейсинг выполнения&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;трассировка&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;выполнения&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;теста&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;отдельный&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;вызов&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;функции&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;в&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;контекст&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;выполнения&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;набор&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;вызовов&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SequenceDiagram&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sequence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;диаграмма&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;из&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SequenceCall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;вызов&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;в&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sequence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;диаграмме&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UMLConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;конфигурация&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;для&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;генерации&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UML&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Функции:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Функциональность распределена по пакетам согласно принципу единственной ответственности. Например, в &lt;code&gt;internal/cli&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Execute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;главная&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;точка&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;входа&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveGraph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;сохранение&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;графа&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;в&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;файл&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveContexts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;сохранение&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;контекстов&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;в&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;файл&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printContextsInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;вывод&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;информации&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;о&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;контекстах&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runTrace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;выполнение&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;команды&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Связи между компонентами:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Граф связей показывает зависимости между пакетами и типами данных:&lt;/p&gt;
&lt;div class="mermaid"&gt;
graph TB
cmdArchlint[cmd/archlint&lt;br/&gt;main binary]
cmdTracelint[cmd/tracelint&lt;br/&gt;linter binary]
analyzer[internal/analyzer&lt;br/&gt;code analysis]
model[internal/model&lt;br/&gt;graph model]
cli[internal/cli&lt;br/&gt;CLI commands]
linter[internal/linter&lt;br/&gt;trace validation]
tracer[pkg/tracer&lt;br/&gt;tracing library]
cmdArchlint --&gt;|uses| cli
cmdTracelint --&gt;|uses| linter
cli --&gt;|uses| analyzer
cli --&gt;|uses| tracer
analyzer --&gt;|produces| model
tracer --&gt;|uses| model
linter --&gt;|uses| tracer
style cmdArchlint fill:#e1f5ff
style cmdTracelint fill:#e1f5ff
style analyzer fill:#fff4e1
style model fill:#f0f0f0
style tracer fill:#d4edda
&lt;/div&gt;
&lt;p&gt;Эта диаграмма показывает верхнеуровневую архитектуру. Видно, что:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Два бинарника (&lt;code&gt;cmd/archlint&lt;/code&gt; и &lt;code&gt;cmd/tracelint&lt;/code&gt;) используют разные подсистемы&lt;/li&gt;
&lt;li&gt;Анализатор не зависит от CLI, его можно использовать независимо&lt;/li&gt;
&lt;li&gt;Модель графа (&lt;code&gt;internal/model&lt;/code&gt;) - центральная структура данных&lt;/li&gt;
&lt;li&gt;Tracer - это публичная библиотека (&lt;code&gt;pkg/tracer&lt;/code&gt;), которую могут использовать другие проекты&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="автоматическое-построение-графа-из-исходного-кода"&gt;Автоматическое построение графа из исходного кода
&lt;/h3&gt;&lt;p&gt;Теоретическая модель хороша, но нужна автоматизация. Вручную описывать граф из сотен компонентов и связей нереально. Более того, граф должен автоматически обновляться при каждом изменении кода.&lt;/p&gt;
&lt;p&gt;Построение структурного графа происходит в четыре этапа:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Этап 1: Анализ исходного кода&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Используя стандартный пакет &lt;code&gt;go/ast&lt;/code&gt;, проходим по всем файлам проекта и строим абстрактное синтаксическое дерево (AST). AST даёт полную информацию о структуре кода: пакеты, импорты, типы, функции, методы, вызовы.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ archlint collect . -o architecture.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Команда рекурсивно анализирует все &lt;code&gt;.go&lt;/code&gt; файлы в текущей директории и её поддиректориях.&lt;/p&gt;
&lt;p&gt;Результат анализа:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Анализ кода: . (язык: go)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Найдено компонентов: 134
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - package: 8 # пакеты верхнего уровня
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - struct: 18 # структуры данных
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - type: 1 # type aliases
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - function: 55 # обычные функции
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - method: 28 # методы структур
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - external: 24 # внешние зависимости
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Найдено связей: 189
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Важный момент: учитываются и внешние зависимости. Это пакеты из стандартной библиотеки Go и сторонних модулей, которые использует проект. Полная картина зависимостей включает и их.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Этап 2: Формирование узлов графа&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Каждый найденный компонент становится узлом графа. Узлы имеют иерархическую структуру идентификаторов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;analyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;package&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.GoAnalyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GoAnalyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;struct&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.GoAnalyzer.Analyze&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Analyze&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Идентификатор следует соглашениям Go: &lt;code&gt;package.Type.Method&lt;/code&gt;. Это позволяет однозначно идентифицировать любой компонент системы.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Этап 3: Формирование рёбер графа&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Создаём рёбра, выражающие семантику связей между компонентами:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Пакет содержит тип&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.GoAnalyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;contains&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Тип содержит метод&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.GoAnalyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.GoAnalyzer.Analyze&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;contains&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Функция вызывает другую функцию&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/cli.Execute&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/analyzer.NewGoAnalyzer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;calls&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Пакет импортирует другой пакет&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cmd/archlint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal/cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;imports&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Разные типы рёбер позволяют различать семантику связей:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;contains&lt;/code&gt; - отношение владения (пакет содержит тип, тип содержит метод)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;calls&lt;/code&gt; - вызов функции или метода&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uses&lt;/code&gt; - использование типа (например, в сигнатуре функции или поле структуры)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;imports&lt;/code&gt; - импорт пакета&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeds&lt;/code&gt; - встраивание типа (embedding в Go)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Этап 4: Сохранение в формате YAML&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;На выходе получаем полный граф системы в формате YAML. Этот файл можно:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Визуализировать:&lt;/strong&gt; Генерировать диаграммы с помощью PlantUML, Graphviz, или веб-интерфейсов вроде DocHub.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Валидировать:&lt;/strong&gt; Проверять архитектурные правила. Например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Пакет &lt;code&gt;internal/model&lt;/code&gt; не должен зависеть ни от чего, кроме стандартной библиотеки&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Циклические зависимости между пакетами запрещены&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Максимальная глубина вложенности вызовов - 5 уровней&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Анализировать:&lt;/strong&gt; Вычислять метрики:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Связность графа (сколько компонентов связаны)&lt;/li&gt;
&lt;li&gt;Цикломатическая сложность&lt;/li&gt;
&lt;li&gt;Глубина дерева зависимостей&lt;/li&gt;
&lt;li&gt;Coupling и cohesion метрики&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Отслеживать изменения:&lt;/strong&gt; Сравнивать версии графа, видеть эволюцию архитектуры во времени.&lt;/p&gt;
&lt;p&gt;Структурный граф archlint - это полное, формальное описание статической структуры системы, которое автоматически обновляется и может быть проверено при каждом коммите.&lt;/p&gt;
&lt;h2 id="поведенческая-архитектура-динамический-граф-выполнения"&gt;Поведенческая архитектура: динамический граф выполнения
&lt;/h2&gt;&lt;p&gt;Структурная архитектура показывает, что система может делать - все потенциальные пути выполнения. Но она не отвечает на вопрос: что система реально делает? Какие компоненты используются в конкретных сценариях? Как данные проходят через систему?&lt;/p&gt;
&lt;p&gt;Для ответа на эти вопросы нужна поведенческая архитектура - динамическая картина выполнения системы.&lt;/p&gt;
&lt;h3 id="зачем-нужна-поведенческая-архитектура"&gt;Зачем нужна поведенческая архитектура
&lt;/h3&gt;&lt;p&gt;Представьте большую систему со структурным графом из тысяч компонентов. Вы добавляете новую фичу. Какие компоненты реально используются? Какие пути выполнения проходит запрос?&lt;/p&gt;
&lt;p&gt;Без поведенческой архитектуры вы можете только догадываться, читая код. С ней - вы видите точную последовательность вызовов, построенную из реального выполнения.&lt;/p&gt;
&lt;p&gt;Поведенческая архитектура критически важна для:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Понимания сложных сценариев&lt;/strong&gt; - видеть последовательность действий&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Оптимизации производительности&lt;/strong&gt; - находить узкие места в реальных путях выполнения&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Выявления мёртвого кода&lt;/strong&gt; - функции, которые не вызываются ни в одном сценарии&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Документирования&lt;/strong&gt; - автоматические sequence диаграммы вместо ручного рисования&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контроля покрытия&lt;/strong&gt; - какие бизнес-сценарии покрыты acceptance тестами&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="как-acceptance-тесты-формируют-поведенческую-архитектуру"&gt;Как acceptance тесты формируют поведенческую архитектуру
&lt;/h3&gt;&lt;p&gt;Ключевая идея: поведенческая архитектура не придумывается абстрактно - она извлекается из реального исполняемого кода. Источник - acceptance тесты.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Принцип &amp;ldquo;одна задача - один acceptance test&amp;rdquo;:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;При правильной декомпозиции работы каждая задача имеет acceptance test, который проверяет корректность реализации. Acceptance test описывает конкретный сценарий использования системы - он задаёт &lt;strong&gt;контекст выполнения&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Задача &amp;ldquo;Калькулятор с памятью&amp;rdquo; -&amp;gt; acceptance test &lt;code&gt;TestCalculateWithMemory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Задача &amp;ldquo;Экспорт отчёта в PDF&amp;rdquo; -&amp;gt; acceptance test &lt;code&gt;TestExportReportToPDF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Задача &amp;ldquo;OAuth авторизация&amp;rdquo; -&amp;gt; acceptance test &lt;code&gt;TestOAuthLogin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Каждый такой тест задаёт отдельный контекст - изолированный сценарий использования системы.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Процесс построения поведенческого графа:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="mermaid"&gt;
flowchart LR
feature[Фича:&lt;br/&gt;Calculate with Memory]
test[Acceptance Test:&lt;br/&gt;TestCalculateWithMemory]
trace[Trace File:&lt;br/&gt;test_calculate.json]
seq[Sequence Diagram:&lt;br/&gt;PlantUML]
behgraph[Behavioral Graph:&lt;br/&gt;Numbered Edges]
context[Context:&lt;br/&gt;CalculateWithMemory]
feature --&gt; test
test --&gt; trace
trace --&gt; seq
trace --&gt; behgraph
seq --&gt; context
behgraph --&gt; context
style feature fill:#e1f5ff
style test fill:#fff4e1
style trace fill:#f0f0f0
style context fill:#d4edda
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Фича&lt;/strong&gt; - бизнес-требование или задача&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acceptance test&lt;/strong&gt; - код, проверяющий фичу&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trace&lt;/strong&gt; - полная последовательность вызовов при выполнении теста&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sequence diagram&lt;/strong&gt; - визуализация последовательности взаимодействий&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavioral graph&lt;/strong&gt; - граф с пронумерованными рёбрами&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context&lt;/strong&gt; - формализованный контекст выполнения фичи&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="реальный-пример-контекст-calculate-with-trace"&gt;Реальный пример: контекст &amp;ldquo;Calculate With Trace&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;Рассмотрим реальный acceptance тест из проекта archlint.example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;TestCalculateWithTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Создаём директорию для трейсов&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traceDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;traces&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;traceDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Запускаем трассировку&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;TestCalculateWithTrace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopTrace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;traceDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test_calculate.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Выполняем сценарий acceptance теста&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;calc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewCalculator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// создаём калькулятор&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// вычисляем (5 + 3) * 2 = 16&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Проверяем acceptance критерий&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Expected 16, got %d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Тест выглядит как обычный Go test, но с добавлением трейсинга. &lt;code&gt;tracer.StartTrace&lt;/code&gt; и &lt;code&gt;tracer.StopTrace&lt;/code&gt; оборачивают выполнение сценария.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Инструментарий кода:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Все функции системы содержат точки трейсинга:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewCalculator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Calculator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.NewCalculator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExitSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.NewCalculator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Calculator.Calculate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExitSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Calculator.Calculate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trace зафиксирует вызов&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trace зафиксирует вызов&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trace зафиксирует вызов&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetMemory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trace зафиксирует вызов&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExitSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Multiply&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExitSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sample.Multiply&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Трейсинг минимальный: &lt;code&gt;tracer.Enter&lt;/code&gt; в начале функции, &lt;code&gt;tracer.ExitSuccess&lt;/code&gt; в конце (через defer). Это не влияет на логику, только фиксирует факт вызова.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Результат трассировки:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;После выполнения теста получаем JSON файл с полной последовательностью всех вызовов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;test_name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;TestCalculateWithTrace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;start_time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550942+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;end_time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550946+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;calls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;enter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.NewCalculator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550942+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;exit_success&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.NewCalculator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550942+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;enter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.Calculator.Calculate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550942+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;enter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.Add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550943+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;exit_success&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.Add&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550943+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;enter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sample.Multiply&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2025-12-07T21:33:05.550943+03:00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;depth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Каждое событие содержит:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;event&lt;/code&gt; - тип события (enter/exit_success/exit_error)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function&lt;/code&gt; - полное имя функции&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timestamp&lt;/code&gt; - точное время вызова&lt;/li&gt;
&lt;li&gt;&lt;code&gt;depth&lt;/code&gt; - уровень вложенности (0 = top-level, 1 = первый вызов, и т.д.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Поле &lt;code&gt;depth&lt;/code&gt; позволяет восстановить иерархию вызовов: какая функция из какой была вызвана.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Автоматическая генерация контекста:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Из trace файла автоматически генерируется контекст:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ archlint trace ./traces -o contexts.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Результат - формальное описание контекста в YAML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tests.testcalculatewithtrace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Calculate With Trace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Tests/Calculate With Trace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;plantuml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.new_calculator&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.calculator.calculate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.add&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.multiply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.calculator.add_to_memory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sample.calculator.get_memory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;output/traces/test_calculate.puml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Контекст включает:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;title&lt;/strong&gt; - человекочитаемое название&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;location&lt;/strong&gt; - место в иерархии контекстов&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;components&lt;/strong&gt; - список всех компонентов, участвующих в сценарии&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uml.file&lt;/strong&gt; - путь к автоматически сгенерированной PlantUML диаграмме&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="визуализации-sequence-диаграмма-и-граф"&gt;Визуализации: sequence диаграмма и граф
&lt;/h3&gt;&lt;p&gt;Из одного trace можно получить два разных представления: sequence диаграмму и поведенческий граф.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sequence диаграмма&lt;/strong&gt; показывает последовательность взаимодействий между компонентами во времени:&lt;/p&gt;
&lt;div class="mermaid"&gt;
sequenceDiagram
participant Test
participant Calculator
participant Add
participant Multiply
participant Memory
Test-&gt;&gt;Calculator: Calculate(5, 3)
Calculator-&gt;&gt;Add: Add(5, 3)
Add--&gt;&gt;Calculator: 8
Calculator-&gt;&gt;Multiply: Multiply(8, 2)
Multiply--&gt;&gt;Calculator: 16
Calculator-&gt;&gt;Memory: AddToMemory(16)
Calculator-&gt;&gt;Memory: GetMemory()
Memory--&gt;&gt;Calculator: 16
Calculator--&gt;&gt;Test: 16
&lt;/div&gt;
&lt;p&gt;Sequence диаграмма удобна для понимания последовательности действий: кто кого вызывает, в каком порядке, какие данные передаются. Это классическое UML представление, знакомое всем разработчикам.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Поведенческий граф&lt;/strong&gt; представляет те же данные в виде графа с пронумерованными рёбрами:&lt;/p&gt;
&lt;div class="mermaid"&gt;
graph TD
test[TestCalculateWithTrace]
newCalc[NewCalculator]
calc[Calculator.Calculate]
add[Add]
mult[Multiply]
addMem[AddToMemory]
getMem[GetMemory]
test --&gt;|1| newCalc
test --&gt;|2| calc
calc --&gt;|3| add
calc --&gt;|4| mult
calc --&gt;|5| addMem
calc --&gt;|6| getMem
style test fill:#e1f5ff
style calc fill:#fff4e1
style add fill:#f0f0f0
style mult fill:#f0f0f0
&lt;/div&gt;
&lt;p&gt;Граф компактнее sequence диаграммы и лучше подходит для анализа. Номера на рёбрах показывают порядок вызовов.&lt;/p&gt;
&lt;p&gt;Ключевые особенности поведенческого графа:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Рёбра пронумерованы&lt;/strong&gt; - сохраняется порядок выполнения&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Мультиграф&lt;/strong&gt; - между двумя узлами может быть несколько рёбер (если функция вызывалась несколько раз)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контекстно-зависимый&lt;/strong&gt; - показывает конкретный сценарий, не все возможные пути&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Подграф структурного графа&lt;/strong&gt; - каждый узел поведенческого графа существует в структурном графе&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="метрики-покрытия-поведенческой-архитектуры"&gt;Метрики покрытия поведенческой архитектуры
&lt;/h3&gt;&lt;p&gt;Acceptance тесты напрямую определяют полноту поведенческой архитектуры. Каждый acceptance test создаёт один контекст. Совокупность всех контекстов - это полная поведенческая архитектура системы.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ключевые метрики:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Покрытие компонентов:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;coverage = (вызванные компоненты) / (всего компонентов) * 100%
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Например, если в структурном графе N компонентов, а в acceptance тестах вызвано M компонентов, то покрытие = M/N * 100%.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Количество контекстов:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Чем больше acceptance тестов, тем больше контекстов, тем полнее покрытие различных сценариев использования.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Покрытие критических путей:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Не все компоненты одинаково важны. Можно пометить критические бизнес-сценарии и отслеживать их покрытие отдельно.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Обнаружение мёртвого кода:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Если компонент присутствует в структурном графе, но не встречается ни в одном контексте - это кандидат на удаление. Либо код мёртвый, либо не хватает acceptance тестов.&lt;/p&gt;
&lt;p&gt;В реальном проекте нужно стремиться к 80%+ покрытию критических путей acceptance тестами.&lt;/p&gt;
&lt;h2 id="заключение-два-взгляда-на-архитектуру"&gt;Заключение: два взгляда на архитектуру
&lt;/h2&gt;&lt;p&gt;В этой статье я показал, как представить архитектуру программной системы через графовую структуру данных. Два вида графов дают два разных, но взаимодополняющих взгляда на систему:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Структурный граф&lt;/strong&gt; показывает, как организован код: какие компоненты существуют и как они могут взаимодействовать. Это статическая картина потенциальных связей. Построение автоматизировано через анализ AST исходного кода.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Поведенческий граф&lt;/strong&gt; показывает, как система работает в реальности: какие компоненты вызываются в конкретных сценариях и в какой последовательности. Это динамическая картина фактических путей выполнения. Построение автоматизировано через трассировку acceptance тестов.&lt;/p&gt;
&lt;p&gt;Ключевое отличие: структурный граф содержит ВСЕ возможные связи, поведенческий - только те, что реально используются в бизнес-сценариях.&lt;/p&gt;
&lt;h3 id="что-дальше"&gt;Что дальше
&lt;/h3&gt;&lt;p&gt;Сами по себе графы - это только фундамент. Интересное начинается, когда мы используем их для контроля качества архитектуры.&lt;/p&gt;
&lt;p&gt;В следующих статьях цикла я покажу:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Валидация архитектурных правил&lt;/strong&gt; - как проверять ограничения на графе: запрет циклических зависимостей, контроль слоистой архитектуры, ограничения на глубину вызовов&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Метрики теории графов&lt;/strong&gt; - как измерять качество архитектуры через связность, центральность, модульность и другие метрики&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Контроль AI-генерируемого кода&lt;/strong&gt; - как использовать графы и метрики для предотвращения архитектурной деградации при разработке с AI-агентами&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="инструменты"&gt;Инструменты
&lt;/h3&gt;&lt;p&gt;Все инструменты для построения архитектурных графов доступны в открытом репозитории: &lt;a class="link" href="https://github.com/mshogin/archlint" target="_blank" rel="noopener"
&gt;github.com/mshogin/archlint&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Инструмент работает с Go-проектами, но подход универсален и применим к любому языку программирования.&lt;/p&gt;
&lt;p&gt;Проект включает:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Анализатор Go кода для построения структурных графов&lt;/li&gt;
&lt;li&gt;Библиотеку трейсинга для построения поведенческих графов&lt;/li&gt;
&lt;li&gt;Генераторы визуализаций (PlantUML, Mermaid)&lt;/li&gt;
&lt;li&gt;Примеры интеграции в CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Это результат исследовательской работы по вопросу &amp;ldquo;как разрабатывать с AI-агентами, сохраняя поддерживаемость&amp;rdquo;. Надеюсь, эти инструменты помогут и вам не превращать pet-проекты в &amp;ldquo;клоаку&amp;rdquo;, а создавать развиваемые системы.&lt;/p&gt;</description></item><item><title>Работа с хаосом в архитектуре</title><link>https://mshogin.ru/blog/working-with-chaos/</link><pubDate>Mon, 28 Apr 2025 00:00:00 +0000</pubDate><guid>https://mshogin.ru/blog/working-with-chaos/</guid><description>&lt;img src="https://mshogin.ru/blog/working-with-chaos/cover.svg" alt="Featured image of post Работа с хаосом в архитектуре" /&gt;&lt;p&gt;Когда работаешь с реальными системами, быстро понимаешь: большую часть времени ты не начинаешь с чистого листа.&lt;/p&gt;
&lt;p&gt;Ты начинаешь с истории, компромиссов, наполовину реализованных идей и меняющихся приоритетов.&lt;/p&gt;
&lt;p&gt;Поначалу это кажется беспорядком. Но со временем понимаешь кое-что важное: ясность не дается - она создается.&lt;/p&gt;
&lt;h2 id="чему-научила-меня-работа-с-хаотичными-архитектурами"&gt;Чему научила меня работа с хаотичными архитектурами
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Слушать, прежде чем судить.&lt;/strong&gt; Каждое решение было принято в определенном контексте. Понять этот контекст важнее, чем спешить с выводами.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Искать паттерны, не навязывая их.&lt;/strong&gt; Паттерны есть почти везде, но нужно дать им проявиться, а не форсировать их появление.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Строить небольшие опорные точки в нестабильной среде.&lt;/strong&gt; Когда вокруг много неопределенности, важны маленькие устойчивые шаги.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;И главное - оставаться терпеливым к сложности.&lt;/strong&gt; Сложность - это не враг. Это реальность, с которой нужно научиться работать.&lt;/p&gt;
&lt;h2 id="каждая-беспорядочная-система-хранит-историю"&gt;Каждая беспорядочная система хранит историю
&lt;/h2&gt;&lt;p&gt;И архитектура в лучшем своем проявлении - это понимание этой истории и помощь в написании следующей главы чуть лучше.&lt;/p&gt;
&lt;p&gt;Если вы тоже работаете посреди хаоса: вы не делаете что-то не так. Скорее всего, вы находитесь именно там, где начинается настоящая архитектура.&lt;/p&gt;</description></item></channel></rss>