С появлением AI-агентов разработка программного обеспечения изменилась кардинально. Я, как и многие разработчики, начал активно использовать Claude, Cursor и другие инструменты для автоматизации написания кода. Результаты поначалу впечатляли: за один вечер, занимаясь системным анализом, проектированием архитектуры и промпт-инжинирингом, я мог сгенерировать до 100 000 строк кода.

Процесс был увлекательным. Не нужно было вручную писать реализацию - достаточно было описать требования, обсудить с AI архитектурное видение, уточнить детали, и код появлялся сам. Я мог работать по вечерам над своими pet-проектами, общаясь с искусственным интеллектом как с коллегой. Это был настоящий вайб-кодинг - приятный, творческий процесс, не отягощённый рутиной.

Проблемы начались не сразу. Первые несколько дней работа шла гладко: AI быстро генерировал код, тесты проходили, функциональность работала. Но затем, примерно через неделю-две активной разработки, я стал замечать тревожные симптомы.

Симптомы деградации кодовой базы

Агенты начали замедляться. То, что раньше занимало минуты, теперь требовало десятков минут. AI начинал зацикливаться на простых задачах, генерировать избыточный код, предлагать отрефакторить чуть ли не весь проект для исправления небольшого бага. Контекст становился слишком большим, и агент терялся в собственном коде.

Появились архитектурные антипаттерны. Последней каплей стал момент, когда при ревью кода я обнаружил такую конструкцию:

// в основной функции
data := processData(input)
result := transform(data, input)     // внутри вызывает processData и combine

// func transform(data, input) {
//     tmp := processData(input)
//     ...
//     result := combine(tmp, input)
//     ...
//     return reuslt
// }
//
// func combine(data, input) {
//     result := processData(input)  // та же функция, те же аргументы!
//     ...
//     return result
// }

Функция processData вызывалась трижды с одинаковыми аргументами, но это дублирование было скрыто внутри transform и combine. AI не отследил, что результат можно переиспользовать, и генерировал похожий код в разных функциях. Но это была лишь верхушка айсберга.

Копаясь глубже, я находил:

  • Функции с 15-20 параметрами, половина из которых передавалась просто насквозь

  • Циклические зависимости между пакетами, скрытые через интерфейсы

  • Дублирование бизнес-логики в трёх разных местах с небольшими вариациями

  • God объекты, которые знали обо всей системе

  • Слои абстракций, которые ничего не абстрагировали, а только усложняли код

Код становился неподдерживаемым. Через две недели проект превращался в то, что сложно назвать иначе как клоака. Добавление новой функциональности требовало всё больше времени. AI предлагал решения, которые работали, но ещё сильнее запутывали архитектуру. Каждое изменение тянуло за собой цепочку других изменений в неожиданных местах.

Я устал создавать проекты, которые живут две недели, а потом становятся legacy. Это были мои собственные pet-проекты, которые я делал для души. Я даже не говорю о production-системах - там такое вообще недопустимо. Хотелось заниматься разработкой и развитием продукта, а не археологическими раскопками в собственном коде.

Остановиться и разобраться

Я принял решение: нужно прекратить генерировать новые проекты, обречённые стать неподдерживаемыми, и разобраться в проблеме системно. Переключил фокус с кодогенерации на исследовательскую работу.

Главный вопрос исследования: как разрабатывать проекты с помощью AI-агентов так, чтобы они оставались развиваемыми и поддерживаемыми?

После анализа проблемы я пришёл к ключевому выводу: вайб-кодинг без формального контроля архитектуры - это путь в никуда. AI-агенты отлично генерируют код, который работает здесь и сейчас. Но они не видят общую картину архитектуры, не отслеживают накопление технического долга, не замечают появление антипаттернов.

Необходим инженерный подход: формальное описание архитектуры, автоматическая валидация, метрики качества. Архитектура должна быть явной, проверяемой, контролируемой. Только так можно предотвратить деградацию кодовой базы.

Я начинаю цикл статей про исследование контроля архитектуры, рассматривая её как графовую структуру данных. В этой первой статье я покажу, как автоматически строить два вида архитектурных графов: структурный (статический) и поведенческий (динамический). Все примеры основаны на реальном проекте archlint - инструменте для автоматического построения и валидации архитектурных графов, который я разработываю в процессе исследования.

В следующих статьях цикла рассмотрю валидацию архитектурных правил и метрики качества кода на основе теории графов.

Структурная архитектура: статический граф системы

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

Зачем нужна формал��ная модель структуры

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

С AI-агентами ситуация другая. Агент работает в ограниченном контексте, не имеет глобального видения системы, не помнит архитектурные решения, принятые неделю назад. Каждый раз, генерируя код, он видит лишь фрагмент системы в окне контекста.

Результат предсказуем: архитектура деградирует. Появляются циклические зависимости, дублирование кода, нарушается слоистая структура. Неформальные соглашения не работают - их некому соблюдать.

Формальное решение: сделать архитектуру явной, автоматически проверяемой. Нужна математическая модель, которая точно описывает структуру системы и позволяет автоматически валидировать её корректность.

Граф - формальная модель

Структурную архитектуру можно представить через математическую абстракцию - ориентированный граф G = (V, E), где:

  • V (vertices) - множество узлов, представляющих компоненты системы

  • E (edges) - множество рёбер, представляющих связи между компонентами

Каждый узел v ∈ V имеет:

  • id - уникальный идентификатор (например, полное имя пакета или функции)

  • type - тип компонента (package, struct, function, method, interface)

  • properties - дополнительные свойства (имя файла, строка кода, видимость)

Каждое ребро e ∈ E имеет:

  • from - узел-источник

  • to - узел-приёмник

  • type - семантика связи (contains, calls, uses, imports, embeds)

Эта простая модель оказывается достаточно мощной для описания реальных систем. Граф позволяет:

  • Визуализировать архитектуру на разных уровнях абстракции

  • Валидировать архитектурные правила (например, "слой UI не должен зависеть от слоя DB")

  • Вычислять метрики (связность, цикломатическая сложность, глубина зависимостей)

  • Отслеживать изменения во времени

Реальный пример: архитектура archlint

Рассмотрим структурную архитектуру проекта - archlint. Это инструмент для построения и анализа архитектурных графов, написанный на Go. Давайте посмотрим, как его структура выглядит в виде графа.

Пакетный уровень:

cmd/archlint          - главный бинарник для сбора архитектуры
cmd/tracelint         - линтер для проверки покрытия трейсингом
internal/analyzer     - анализ исходного кода Go через AST
internal/model        - модель графа архитектуры
internal/cli          - реализация CLI команд
internal/linter       - валидация корректности трейсинга
pkg/tracer            - библиотека для трейсинга выполнения
tests/testdata/sample - тестовые примеры с инструментацией

Типы данных:

Типы данных организованы по доменам:

// Модель графа
internal/model.Graph      - представление архитектурного графа
internal/model.Node       - узел графа (компонент системы)
internal/model.Edge       - ребро графа (связь между компонентами)

// Анализатор исходного кода
internal/analyzer.GoAnalyzer    - главный анализатор Go кода
internal/analyzer.PackageInfo   - информация о пакете
internal/analyzer.TypeInfo      - информация о типе (struct/interface)
internal/analyzer.FunctionInfo  - информация о функции
internal/analyzer.MethodInfo    - информация о методе
internal/analyzer.FieldInfo     - информация о поле структуры
internal/analyzer.CallInfo      - информация о вызове функции

// Трейсинг выполнения
pkg/tracer.Trace           - трассировка выполнения теста
pkg/tracer.Call            - отдельный вызов функции в trace
pkg/tracer.Context         - контекст выполнения (набор вызовов)
pkg/tracer.SequenceDiagram - sequence диаграмма из trace
pkg/tracer.SequenceCall    - вызов в sequence диаграмме
pkg/tracer.UMLConfig       - конфигурация для генерации UML

Функции:

Функциональность распределена по пакетам согласно принципу единственной ответственности. Например, в internal/cli:

internal/cli.Execute           - главная точка входа CLI
internal/cli.saveGraph         - сохранение графа в файл
internal/cli.saveContexts      - сохранение контекстов в файл
internal/cli.printContextsInfo - вывод информации о контекстах
internal/cli.runTrace          - выполнение trace команды

Связи между компонентами:

Граф связей показывает зависимости между пакетами и типами данных:

Эта диаграмма показывает верхнеуровневую архитектуру. Видно, что:

  • Два бинарника (cmd/archlint и cmd/tracelint) используют разные подсистемы

  • Анализатор не зависит от CLI, его можно использовать независимо

  • Модель графа (internal/model) - центральная структура данных

  • Tracer - это публичная библиотека (pkg/tracer), которую могут использовать другие проекты

Автоматическое построение графа из исходного кода

Теоретическая модель хороша, но нужна автоматизация. Вручную описывать граф из сотен компонентов и связей нереально. Более того, граф должен автоматически обновляться при каждом изменении кода.

Построение структурного графа происходит в четыре этапа:

Этап 1: Анализ исходного кода

Используя стандартный пакет go/ast, проходим по всем файлам проекта и строим абстрактное синтаксическое дерево (AST). AST даёт полную информацию о структуре кода: пакеты, импорты, типы, функции, методы, вызовы.

$ archlint collect . -o architecture.yaml

Команда рекурсивно анализирует все .go файлы в текущей директории и её поддиректориях.

Результат анализа:

Анализ кода: . (язык: go)
Найдено компонентов: 134
  - package: 8        # пакеты верхнего уровня
  - struct: 18        # структуры данных
  - type: 1           # type aliases
  - function: 55      # обычные функции
  - method: 28        # методы структур
  - external: 24      # внешние зависимости
Найдено связей: 189

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

Этап 2: Формирование узлов графа

Каждый найденный компонент становится узлом графа. Узлы имеют иерархическую структуру идентификаторов:

components:
  - id: internal/analyzer
    title: analyzer
    entity: package

  - id: internal/analyzer.GoAnalyzer
    title: GoAnalyzer
    entity: struct

  - id: internal/analyzer.GoAnalyzer.Analyze
    title: Analyze
    entity: method

Идентификатор следует соглашениям Go: package.Type.Method. Это позволяет однозначно идентифицировать любой компонент системы.

Этап 3: Формирование рёбер графа

Создаём рёбра, выражающие семантику связей между компонентами:

links:
  # Пакет содержит тип
  - from: internal/analyzer
    to: internal/analyzer.GoAnalyzer
    type: contains

  # Тип содержит метод
  - from: internal/analyzer.GoAnalyzer
    to: internal/analyzer.GoAnalyzer.Analyze
    type: contains

  # Функция вызывает другую функцию
  - from: internal/cli.Execute
    to: internal/analyzer.NewGoAnalyzer
    type: calls

  # Пакет импортирует другой пакет
  - from: cmd/archlint
    to: internal/cli
    type: imports

Разные типы рёбер позволяют различать семантику связей:

  • contains - отношение владения (пакет содержит тип, тип содержит метод)

  • calls - вызов функции или метода

  • uses - использование типа (например, в сигнатуре функции или поле структуры)

  • imports - импорт пакета

  • embeds - встраивание типа (embedding в Go)

Этап 4: Сохранение в формате YAML

На выходе получаем полный граф системы в формате YAML. Этот файл можно:

Визуализировать: Генерировать диаграммы с помощью PlantUML, Graphviz, или веб-интерфейсов вроде DocHub.

Валидировать: Проверять архитектурные правила. Например:

  • "Пакет internal/model не должен зависеть ни от чего, кроме стандартной библиотеки"

  • "Циклические зависимости между пакетами запрещены"

  • "Максимальная глубина вложенности вызовов - 5 уровней"

Анализировать: Вычислять метрики:

  • Связность графа (сколько компонентов связаны)

  • Цикломатическая сложность

  • Глубина дерева зависимостей

  • Coupling и cohesion метрики

Отслеживать изменения: Сравнивать версии графа, видеть эволюцию архитектуры во времени.

Структурный граф archlint - это полное, формальное описание статической структуры системы, которое автоматически обновляется и может быть проверено при каждом коммите.

Поведенческая архитектура: динамический граф выполнения

Структурная архитектура показывает, что система может делать - все потенциальные пути выполнения. Но она не отвечает на вопрос: что система реально делает? Какие компоненты используются в конкретных сценариях? Как данные проходят через систему?

Для ответа на эти вопросы нужна поведенческая архитектура - динамическая картина выполнения системы.

Зачем нужна поведенческая архитектура

Представьте большую систему со структурным графом из тысяч компонентов. Вы добавляете новую фичу. Какие компоненты реально используются? Какие пути выполнения проходит запрос?

Без поведенческой архитектуры вы можете только догадываться, читая код. С ней - вы видите точную последовательность вызовов, построенную из реального выполнения.

Поведенческая архитектура критически важна для:

  • Понимания сложных сценариев - видеть последовательность действий

  • Оптимизации производительности - находить узкие места в реальных путях выполнения

  • Выявления мёртвого кода - функции, которые не вызываются ни в одном сценарии

  • Документирования - автоматические sequence диаграммы вместо ручного рисования

  • Контроля покрытия - какие бизнес-сценарии покрыты acceptance тестами

Как acceptance тесты формируют поведенческую архитектуру

Ключевая идея: поведенческая архитектура не придумывается абстрактно - она извлекается из реального исполняемого кода. Источник - acceptance тесты.

Принцип "одна задача - один acceptance test":

При правильной декомпозиции работы каждая задача имеет acceptance test, который проверяет корректность реализации. Acceptance test описывает конкретный сценарий использования системы - он задаёт контекст выполнения.

Например:

  • Задача "Калькулятор с памятью" -> acceptance test TestCalculateWithMemory

  • Задача "Экспорт отчёта в PDF" -> acceptance test TestExportReportToPDF

  • Задача "OAuth авторизация" -> acceptance test TestOAuthLogin

Каждый такой тест задаёт отдельный контекст - изолированный сценарий использования системы.

Процесс построения поведенческого графа:

  1. Фича - бизнес-требование или задача

  2. Acceptance test - код, проверяющий фичу

  3. Trace - полная последовательность вызовов при выполнении теста

  4. Sequence diagram - визуализация последовательности взаимодействий

  5. Behavioral graph - граф с пронумерованными рёбрами

  6. Context - формализованный контекст выполнения фичи

Реальный пример: контекст "Calculate With Trace"

Рассмотрим реальный acceptance тест из проекта archlint.example:

func TestCalculateWithTrace(t *testing.T) {
    // Создаём директорию для трейсов
    traceDir := "traces"
    os.MkdirAll(traceDir, 0755)

    // Запускаем трассировку
    trace := tracer.StartTrace("TestCalculateWithTrace")
    defer func() {
        trace = tracer.StopTrace()
        if trace != nil {
            trace.Save(filepath.Join(traceDir, "test_calculate.json"))
        }
    }()

    // Выполняем сценарий acceptance теста
    calc := NewCalculator()    // создаём калькулятор
    result := calc.Calculate(5, 3)  // вычисляем (5 + 3) * 2 = 16

    // Проверяем acceptance критерий
    if result != 16 {
        t.Errorf("Expected 16, got %d", result)
    }
}

Тест выглядит как обычный Go test, но с добавлением трейсинга. tracer.StartTrace и tracer.StopTrace оборачивают выполнение сценария.

Инструментарий кода:

Все функции системы содержат точки трейсинга:

func NewCalculator() *Calculator {
    tracer.Enter("sample.NewCalculator")
    defer tracer.ExitSuccess("sample.NewCalculator")
    return &Calculator{memory: 0}
}

func (c *Calculator) Calculate(a, b int) int {
    tracer.Enter("sample.Calculator.Calculate")
    defer tracer.ExitSuccess("sample.Calculator.Calculate")

    sum := Add(a, b)           // trace зафиксирует вызов
    product := Multiply(sum, 2) // trace зафиксирует вызов
    c.AddToMemory(product)     // trace зафиксирует вызов
    return c.GetMemory()       // trace зафиксирует вызов
}

func Add(a, b int) int {
    tracer.Enter("sample.Add")
    defer tracer.ExitSuccess("sample.Add")
    return a + b
}

func Multiply(a, b int) int {
    tracer.Enter("sample.Multiply")
    defer tracer.ExitSuccess("sample.Multiply")
    return a * b
}

Трейсинг минимальный: tracer.Enter в начале функции, tracer.ExitSuccess в конце (через defer). Это не влияет на логику, только фиксирует факт вызова.

Результат трассировки:

После выполнения теста получаем JSON файл с полной последовательностью всех вызовов:

{
  "test_name": "TestCalculateWithTrace",
  "start_time": "2025-12-07T21:33:05.550942+03:00",
  "end_time": "2025-12-07T21:33:05.550946+03:00",
  "calls": [
    {
      "event": "enter",
      "function": "sample.NewCalculator",
      "timestamp": "2025-12-07T21:33:05.550942+03:00",
      "depth": 0
    },
    {
      "event": "exit_success",
      "function": "sample.NewCalculator",
      "timestamp": "2025-12-07T21:33:05.550942+03:00",
      "depth": 0
    },
    {
      "event": "enter",
      "function": "sample.Calculator.Calculate",
      "timestamp": "2025-12-07T21:33:05.550942+03:00",
      "depth": 0
    },
    {
      "event": "enter",
      "function": "sample.Add",
      "timestamp": "2025-12-07T21:33:05.550943+03:00",
      "depth": 1
    },
    {
      "event": "exit_success",
      "function": "sample.Add",
      "timestamp": "2025-12-07T21:33:05.550943+03:00",
      "depth": 1
    },
    {
      "event": "enter",
      "function": "sample.Multiply",
      "timestamp": "2025-12-07T21:33:05.550943+03:00",
      "depth": 1
    },
    ...
  ]
}

Каждое событие содержит:

  • event - тип события (enter/exit_success/exit_error)

  • function - полное имя функции

  • timestamp - точное время вызова

  • depth - уровень вложенности (0 = top-level, 1 = первый вызов, и т.д.)

Поле depth позволяет восстановить иерархию вызовов: какая функция из какой была вызвана.

Автоматическая генерация контекста:

Из trace файла автоматически генерируется контекст:

$ archlint trace ./traces -o contexts.yaml

Результат - формальное описание контекста в YAML:

contexts:
  tests.testcalculatewithtrace:
    title: Calculate With Trace
    location: Tests/Calculate With Trace
    presentation: plantuml
    components:
      - sample.new_calculator
      - sample.calculator.calculate
      - sample.add
      - sample.multiply
      - sample.calculator.add_to_memory
      - sample.calculator.get_memory
    uml:
      file: output/traces/test_calculate.puml

Контекст включает:

  • title - человекочитаемое название

  • location - место в иерархии контекстов

  • components - список всех компонентов, участвующих в сценарии

  • uml.file - путь к автоматически сгенерированной PlantUML диаграмме

Визуализации: sequence диаграмма и граф

Из одного trace можно получить два разных представления: sequence диаграмму и поведенческий граф.

Sequence диаграмма показывает последовательность взаимодействий между компонентами во времени:

Sequence диаграмма удобна для понимания последовательности действий: кто кого вызывает, в каком порядке, какие данные передаются. Это классическое UML представление, знакомое всем разработчикам.

Поведенческий граф представляет те же данные в виде графа с пронумерованными рёбрами:

Граф компактнее sequence диаграммы и лучше подходит для анализа. Номера на рёбрах показывают порядок вызовов.

Ключевые особенности поведенческого графа:

  1. Рёбра пронумерованы - сохраняется порядок выполнения

  2. Мультиграф - между двумя узлами может быть несколько рёбер (если функция вызывалась несколько раз)

  3. Контекстно-зависимый - показывает конкретный сценарий, не все возможные пути

  4. Подграф структурного графа - каждый узел поведенческого графа существует в структурном графе

Метрики покрытия поведенческой архитектуры

Acceptance тесты напрямую определяют полноту поведенческой архитектуры. Каждый acceptance test создаёт один контекст. Совокупность всех контекстов - это полная поведенческая архитектура системы.

Ключевые метрики:

Покрытие компонентов:

coverage = (вызванные компоненты) / (всего компонентов) * 100%

Например, если в структурном графе N компонентов, а в acceptance тестах вызвано M компонентов, то покрытие = M/N * 100%.

Количество контекстов:

Чем б��льше acceptance тестов, тем больше контекстов, тем полнее покрытие различных сценариев использования.

Покрытие критических путей:

Не все компоненты одинаково важны. Можно пометить критические бизнес-сценарии и отслеживать их покрытие отдельно.

Обнаружение мёртвого кода:

Если компонент присутствует в структурном графе, но не встречается ни в одном контексте - это кандидат на удаление. Либо код мёртвый, либо не хватает acceptance тестов.

В реальном проекте нужно стремиться к 80%+ покрытию критических путей acceptance тестами.

Заключение: два взгляда на архитектуру

В этой статье я показал, как представить архитектуру программной системы через графовую структуру данных. Два вида графов дают два разных, но взаимодополняющих взгляда на систему:

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

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

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

Что дальше

Сами по себе графы - это только фундамент. Интересное начинается, когда мы используем их для контроля качества архитектуры.

В следующих статьях цикла я покажу:

  1. Валидация архитектурных правил - как проверять ограничения на графе: запрет циклических зависимостей, контроль слоистой архитектуры, ограничения на глубину вызовов

  2. Метрики теории графов - как измерять качество архитектуры через связность, центральность, модульность и другие метрики

  3. Контроль AI-генерируемого кода - как использовать графы и метрики для предотвращения архитектурной деградации при разработке с AI-агентами

Инструменты

Все инструменты для построения архитектурных графов доступны в открытом репозитории: github.com/mshogin/archlint

Инструмент работает с Go-проектами, но подход универсален и применим к любому языку программирования.

Проект включает:

  • Анализатор Go кода для построения структурных графов

  • Библиотеку трейсинга для построения поведенческих графов

  • Генераторы визуализаций (PlantUML, Mermaid)

  • Примеры интеграции в CI/CD

Это результат исследовательской работы по вопросу "как разрабатывать с AI-агентами, сохраняя поддерживаемость". Надеюсь, эти инструменты помогут и вам не превращать pet-проекты в "клоаку", а создавать развиваемые системы.

Комментарии (43)


  1. Keeper22
    09.12.2025 19:17

    Поздравляю, вы открыли каскадную модель управления проектом, она же "водопад".


    1. AnonimYYYs
      09.12.2025 19:17

      Еще немного и изобретет uml-диаграммы классов...


    1. mshogin Автор
      09.12.2025 19:17

      учту


  1. skvoo
    09.12.2025 19:17

    Дружище, все работает, не надо делать кликбейт заголовки


    1. xirustam
      09.12.2025 19:17

      Поделитесь секретом, как? У меня работает только по мелочи, если что-то сложное, то начинается жесть похожая на то, что описал автор в начале статьи. Самое противное, что ИИ пришет код в лоб: как ты его попросил, так и пишет - прямо бесит, что ты всё за него должен думать.


      1. sidewinder1
        09.12.2025 19:17

        Принцип работы LLM: Garbage in - Garbage out


      1. Wesha
        09.12.2025 19:17

        как ты его попросил, так и пишет

        Сдаётся мне, Пятачок, пчОлы начали что-то подозревать!


    1. flancer
      09.12.2025 19:17

      У всего есть свои пределы. Просто вы ещё не дошли до пределов вайб-кодинга, а Андрей Карпаты уже да. Причем где-то за месяц: в феврале - придумал, а в марте - дошёл.


      1. cupraer
        09.12.2025 19:17

        Понятия не имею, кто такой этот Андрей, но «There's no "full-stack" product with batteries included» — однозначно классифицирует его как человека с узким кругозором, оставшего от каравана на 6 лет.


        1. DarthVictor
          09.12.2025 19:17

          Автор термина вайб-кодинг.


        1. flancer
          09.12.2025 19:17

          Я в курсе, что ваша узкая специализация позволяет вам уверенно позиционировать себя лишь в узкой области. Просто не выходите за её пределы.


  1. flancer
    09.12.2025 19:17

    Мне кажется, что контроль результата работы LLM - это "путь тестирования". Тесты могут сказать, что работает неправильно, но они не говорят, как сделать, чтобы работало правильно.

    IMHO, задача "парного кодирования с LLM" - держать агентов в определённых рамках и не давать им "проявлять творчество". Лично я ставлю на контекст (spec driven).


    1. mshogin Автор
      09.12.2025 19:17

      да, здесь согласен. идея spec driven очень близка. сейчас тестирую этот подход в том числе. однако вопрос с качеством кода все также остается. ревьюить все изменения утомляет и на редактирование кода достаточно быстро выдаешь право делать это автоматом.


      1. flancer
        09.12.2025 19:17

        ревьюить все изменения утомляет

        Полностью согласен. Я вообще пришёл к выводу, что человек не должен ревьюить код агента. Агент тупо быстрее на порядки. Я иду по пути проверки результата: работает - значит, не важно, как оно там внутри сложено. В следующую итерацию агент может переписать код вдоль и поперёк, главное - чтобы код выполнял свою функцию.

        Я не говорю, что я вообще не смотрю на то, что делает агент. Как раз смотрю. Мне важно понимать, почему агент "вырвался из загона контекста". Кстати, он в сессии, зачастую, и сам может об этом рассказать, если его спросить. А потом важно переставить заборчики контекста так, чтобы он в следующий раз не вырывался.

        У такого пути тоже есть свои ограничения, но, мне кажется, хорош любой путь, который позволяет дойти до границ. Главное - не за'loop'иться. Вот там-то границ как раз и нет :)


  1. Rerurk
    09.12.2025 19:17

    Что то определенно стало не так. Тоже начал замечать "халтуру"


  1. empenoso
    09.12.2025 19:17

    Чем диаграмму создали?


    1. mshogin Автор
      09.12.2025 19:17

      предпочитаю plantuml, однако в последнее время в чаще и чаще начинаю использовать mermaid