Мир до AI-ассистентов: детерминизм
Традиционная разработка программного обеспечения -- это мир детерминизма. Один и тот же исходный код, прошедший через один и тот же компилятор с одними и теми же настройками, всегда порождает один и тот же результат. Линтер, получив одинаковый файл, всегда выдаст одинаковые предупреждения. Тесты при одинаковом состоянии дают одинаковый результат.
Детерминированный мир:
Исходный код → Компилятор → Бинарный файл
(всегда один) (правила) (всегда тот же)
Код + Линтер → Предупреждения
(те же) (те же)
Код + Тесты → Результат
(те же) (тот же: pass/fail)
Свойства детерминированной разработки
| Свойство | Описание | Пример |
|---|---|---|
| Воспроизводимость | Результат идентичен при повторном запуске | gcc main.c дает одинаковый бинарник |
| Предсказуемость | Можно предсказать результат до запуска | Синтаксическая ошибка всегда будет найдена |
| Верифицируемость | Легко проверить корректность | Тесты либо проходят, либо нет |
| Отладка | Ошибку можно воспроизвести и изолировать | Breakpoint всегда срабатывает одинаково |
Инженеры десятилетиями строили инструменты и процессы вокруг этих свойств. CI/CD-пайплайны рассчитывают на воспроизводимость. Code review рассчитывает на предсказуемость. Тестовые фреймворки рассчитывают на верифицируемость. Отладчики рассчитывают на воспроизводимость багов.
Инструменты детерминированного мира
# Compiler -- deterministic transformation
gcc -O2 -o app main.c # Always the same binary
# Linter -- deterministic analysis
phpstan analyse src/ # Always the same warnings
# Formatter -- deterministic transformation
prettier --write src/ # Always the same formatting
# Tests -- deterministic verification
phpunit --testsuite=unit # Same pass/fail results
Все эти инструменты объединяет одно: им можно доверять на уровне процесса. Если линтер сказал "ошибок нет" -- значит, ошибок (которые он знает) действительно нет. Если тесты прошли -- значит, проверяемое поведение работает.
Появление AI-ассистентов: недетерминизм
AI-ассистенты для разработки (GitHub Copilot, Claude Code, Cursor, Windsurf) работают принципиально иначе. Это вероятностные модели -- они не применяют жесткие правила, а генерируют наиболее вероятный вывод на основе статистических паттернов.
Недетерминированный мир:
Промпт + Контекст → LLM → Код (вариант A)
(тот же) (модель) ↘ Код (вариант B)
↘ Код (вариант C)
Один и тот же запрос может породить разный код
при каждом вызове.
Что делает AI-ассистент
Когда вы просите AI написать функцию, происходит следующее:
- Токенизация -- ваш промпт разбивается на токены
- Обработка контекста -- модель "видит" ваш промпт, контекст файлов, историю чата
- Генерация -- модель последовательно выбирает наиболее вероятные следующие токены
- Выбор -- на каждом шаге есть параметр temperature, влияющий на "креативность" выбора
Ключевое отличие: на шаге 4 модель выбирает из распределения вероятностей, а не применяет правила. Это означает:
Один и тот же промпт может породить разный код при каждом вызове.
Практический пример
Допустим, вы просите AI: "Напиши функцию валидации email на PHP".
При первом вызове:
<?php
declare(strict_types=1);
// Attempt 1: using filter_var
function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
При втором вызове с тем же промптом:
<?php
declare(strict_types=1);
// Attempt 2: using regex
function validateEmail(string $email): bool
{
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
return preg_match($pattern, $email) === 1;
}
При третьем:
<?php
declare(strict_types=1);
// Attempt 3: comprehensive validation
final class EmailValidator
{
public function validate(string $email): bool
{
if (empty($email) || strlen($email) > 254) {
return false;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
// Check DNS MX record
$domain = substr($email, strrpos($email, '@') + 1);
return checkdnsrr($domain, 'MX');
}
}
Все три варианта -- валидные ответы на один и тот же вопрос. Но у каждого разные свойства: производительность, точность, обработка крайних случаев, архитектурный стиль. AI не скажет вам, какой вариант подходит для вашей конкретной ситуации.
Фундаментальное напряжение
AI-ассистенты создают фундаментальное напряжение в инженерной практике:
┌─────────────────────────────────────────────────────────────┐
│ ФУНДАМЕНТАЛЬНОЕ НАПРЯЖЕНИЕ │
│ │
│ СКОРОСТЬ НАДЕЖНОСТЬ │
│ ┌──────────┐ ┌──────────┐ │
│ │ AI может │ │ AI может │ │
│ │ написать │ │ написать │ │
│ │ код за │ VS │ код с │ │
│ │ секунды │ │ багами, │ │
│ │ │ │ дырами, │ │
│ │ │ │ долгом │ │
│ └──────────┘ └──────────┘ │
│ │
│ "Быстро и полезно" "Уверенно и неправильно" │
└─────────────────────────────────────────────────────────────┘
AI "уверенно неправильный"
Одна из самых опасных особенностей AI-ассистентов -- они не сигнализируют о своей неуверенности. Компилятор скажет "ошибка в строке 42". Линтер предупредит о потенциальной проблеме. AI-ассистент напишет код, который выглядит правильно, читается уверенно, но может содержать серьезные ошибки.
Примеры типичных проблем:
| Категория | Пример "уверенной ошибки" AI |
|---|---|
| Безопасность | SQL-запрос без параметризации, потому что в обучающих данных много примеров с конкатенацией строк |
| Производительность | N+1 запрос в цикле, потому что "это самый очевидный способ" |
| Крайние случаи | Отсутствие проверки на null, пустой массив, слишком длинную строку |
| Совместимость | Использование API, которое было deprecated или изменено в новой версии |
| Бизнес-логика | Математически правильный код, который не учитывает бизнес-правило |
Ключевой инсайт: AI не знает, чего он не знает. Он не скажет "я не уверен в этом решении" или "здесь может быть проблема с производительностью при 10K записей". Он просто напишет код.
Почему это важно для инженеров
1. Хрупкий код (Fragile Code)
AI-генерированный код часто работает "на первый взгляд", но ломается при нестандартных входных данных:
<?php
declare(strict_types=1);
// AI-generated: looks correct, fragile in practice
function parseUserAge(string $input): int
{
// AI didn't consider: negative numbers, overflow, non-numeric strings,
// leading/trailing whitespace, empty strings, "NaN", "Infinity"
return (int) $input;
}
// Production-ready version: explicit handling
function parseUserAgeSafe(string $input): int
{
$trimmed = trim($input);
if ($trimmed === '') {
throw new \InvalidArgumentException('Age cannot be empty');
}
if (!ctype_digit($trimmed)) {
throw new \InvalidArgumentException(
sprintf('Age must be a positive integer, got: "%s"', $input)
);
}
$age = (int) $trimmed;
if ($age < 0 || $age > 150) {
throw new \InvalidArgumentException(
sprintf('Age must be between 0 and 150, got: %d', $age)
);
}
return $age;
}
2. Безопасность (Security Holes)
AI обучался на огромном количестве кода, включая небезопасный. Он может воспроизвести уязвимые паттерны:
// AI-generated: vulnerable to path traversal
func serveFile(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file")
// Attacker can send: ?file=../../../etc/passwd
data, err := os.ReadFile("/uploads/" + filename)
if err != nil {
http.Error(w, "Not found", 404)
return
}
w.Write(data)
}
// Production-ready: sanitized path
func serveFileSafe(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file")
// Clean the path to prevent traversal
cleaned := filepath.Clean(filename)
// Ensure it doesn't escape the uploads directory
if strings.Contains(cleaned, "..") {
http.Error(w, "Forbidden", 403)
return
}
fullPath := filepath.Join("/uploads", cleaned)
// Verify the resolved path is still under /uploads
if !strings.HasPrefix(fullPath, "/uploads/") {
http.Error(w, "Forbidden", 403)
return
}
data, err := os.ReadFile(fullPath)
if err != nil {
http.Error(w, "Not found", 404)
return
}
w.Write(data)
}
3. Перегрузка Code Review
Когда AI генерирует код в 10 раз быстрее, объем кода на review возрастает пропорционально. Но человеческая способность к review -- конечный ресурс:
Без AI:
Разработчик пишет ~200 строк/день → Review ~200 строк → Управляемо
С AI:
Разработчик генерирует ~2000 строк/день → Review ~2000 строк → Перегрузка
Результат: "Выглядит нормально, approve" → Баги в production
4. Технический долг
AI-генерированный код, принятый без должной проверки, создает технический долг, который растет быстрее обычного:
- Дублирование логики (AI не видит, что похожая функция уже существует)
- Несогласованные паттерны (каждый промпт может породить свой стиль)
- Отсутствие документации (AI пишет код, но не объясняет "почему")
- Устаревшие зависимости (AI обучался на данных определенного периода)
Что меняется в инженерных практиках
Переход к недетерминированным инструментам требует пересмотра ключевых практик:
Тестирование
Раньше: тесты проверяют, что ваш код работает
Теперь: тесты проверяют, что AI-генерированный код работает
И что он продолжает работать после рефакторинга AI
Значение тестов: ↑↑↑ (выросло многократно)
Тесты становятся не просто проверкой, а контрактом между вашими требованиями и AI-генерированным кодом. Без тестов вы не можете знать, работает ли код корректно, даже если он "выглядит правильно".
Отладка
Раньше: вы написали код → вы знаете, как он работает → вы знаете, где искать баг
Теперь: AI написал код → вы должны понять, как он работает → потом искать баг
Сложность отладки: ↑↑ (выросла)
Code Review
Раньше: проверяете код коллеги (знаете его стиль, контекст)
Теперь: проверяете код AI (может быть любым, непредсказуемый стиль)
Объем review: ↑↑↑ (AI генерирует быстро, много кода)
Нужна новая стратегия: фокус на контрактах, интерфейсах, безопасности
Архитектура
Раньше: архитектура определяет структуру кода
Теперь: архитектура ПЛЮС правила для AI определяют структуру кода
(CLAUDE.md, .cursorrules, AGENTS.md)
Значение архитектурных решений: ↑↑ (AI без контекста нарушит архитектуру)
Тезис Энрико Папалини
Энрико Папалини в книге "AI-Assisted Software Engineering" формулирует центральный тезис:
Инженерная дисциплина при работе с AI становится БОЛЕЕ важной, а не менее.
Это контринтуитивно. Кажется, что AI упрощает разработку и нужно меньше дисциплины. На самом деле:
| Без AI | С AI |
|---|---|
| Пишешь код сам -- контролируешь каждую строку | AI пишет код -- нужно проверять каждую строку |
| Скорость ограничена -- меньше кода -- меньше ошибок | Скорость огромная -- больше кода -- больше потенциальных ошибок |
| Детерминированные инструменты -- предсказуемый результат | Недетерминированные инструменты -- нужна верификация |
| Стандартные процессы достаточны | Нужны новые процессы и guardrails |
Guardrails -- ограждения для AI
Книга предлагает подход "embrace non-determinism with proper guardrails" -- принять недетерминизм, но установить ограждения:
┌────────────────────────────────────────────────────┐
│ GUARDRAILS │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ AI-генерированный код │ │
│ │ │ │
│ │ Быстро, продуктивно, но непредсказуемо │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Авто-тесты│ │ Линтеры │ │ Security │ │
│ │ │ │ │ │ Scan │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Code │ │ Type │ │ Perf │ │
│ │Review │ │ Check │ │ Bench │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Guardrails = автоматические проверки, которые │
│ ловят ошибки AI до того, как они попадут в prod │
└────────────────────────────────────────────────────┘
Ключевые выводы
-
AI-ассистенты -- недетерминированные инструменты. Один и тот же промпт может породить разный код. Это фундаментально отличает их от компиляторов, линтеров и тестов.
-
AI "уверенно неправильный". Он не сигнализирует о неуверенности, не предупреждает о крайних случаях, не учитывает ваш контекст автоматически.
-
Скорость генерации -- палка о двух концах. Больше кода за единицу времени означает больше потенциальных ошибок за единицу времени.
-
Инженерная дисциплина важнее, чем когда-либо. Тесты, review, security -- все становится критически важным, когда значительная часть кода генерируется вероятностной моделью.
-
Нужны новые процессы. Традиционные CI/CD, code review, тестирование должны быть адаптированы для мира AI-генерированного кода.
Главный тезис: AI-ассистенты -- это мощные инструменты, которые требуют новой инженерной дисциплины. Не меньше дисциплины, а БОЛЬШЕ. Не слепое доверие, а верификация. Не отказ от процессов, а их эволюция.