Атомарные коммиты и ACDD: порядок в Git и AI-разработке

Что такое атомарный коммит, как определить его границы, писать сообщения, вести чистую историю Git и контролировать изменения, созданные AI.

11.01.2026 · 25 мин чтения · Обновлено 05.09.2026

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

Я называю его ACDD — Atomic Commit Driven Development. Если очень просто: сначала вы формулируете следующий законченный шаг, потом делаете только его, проверяете результат и создаёте коммит. Цикл выглядит так: мысль → фиксация → действие → проверка → коммит. Вроде бы ничего сложного. Сделать коммит вообще просто. Вопрос в том, что именно вы в него положили.

Где-то в одной IT-компании пришёл новый разработчик и засквошил историю основной ветки с сообщением legacy code. Не надо так. Это крайний случай, но в обычной работе всё выглядит не сильно лучше: wip, fix, temp, «немного поправил авторизацию». Открываешь такой коммит, а там новая функциональность, рефакторинг, обновление зависимостей, тесты, пара исправленных опечаток и оставленный на память отладочный вывод. Признавайтесь, делали так? Я тоже делал. Все делали. Но это не значит, что так обязательно продолжать.

Почему история Git превращается в свалку

Разработчики стараются поддерживать порядок в коде, архитектуре, тестах и документации. Мы спорим о названиях методов, границах модулей и количестве ответственностей у класса. Но история Git часто остаётся где-то за пределами инженерной дисциплины.

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

Большая задача, один коммит и тысячи строк изменений

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

Автору такого изменения всё понятно. Он только что написал код и ещё помнит, почему перенёс класс, изменил интерфейс, добавил таблицу, обновил документацию и заодно поправил старый метод. В его голове это одна задача, а для другого разработчика — большая пачка решений, которую ему передали одновременно.

Почему автору удобно, а ревьюеру тяжело

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

Я считаю это не очень честным обменом. Автор экономит время на разделении работы, а потом эту экономию оплачивает ревьюер, а иногда и несколько ревьюеров. Исследования code review подтверждают достаточно очевидную вещь: размер и сложность изменения влияют на когнитивную нагрузку и эффективность поиска дефектов. В эксперименте с 50 разработчиками более крупные и сложные изменения были связаны с более низкой эффективностью обнаружения некоторых типов ошибок. Это не означает, что любой большой дифф плохой, но если в нём находится несколько независимых смыслов, ревьюеру сначала придётся разделить их у себя в голове. Можно было сделать это заранее.

История Git отражает не только код, но и инженерное мышление

Код всегда кто-то читает. Даже если это вы сами через месяц. Историю Git тоже читают: через git log, git blame, pull request, поиск регрессии или попытку понять, почему система устроена именно так.

Хорошая история не обязана содержать каждое сомнение разработчика и каждую ошибочную попытку. Она должна показывать логику принятых решений: из какой точки мы вышли, какой шаг сделали и почему оказались в текущем состоянии. История Git должна отражать движение инженерной мысли, а не уровень усталости разработчика в конце дня.

AI ускоряет написание кода, но не его осмысление

С появлением AI-кодинга проблема стала заметнее. Теперь за несколько минут можно получить объём изменений, на который раньше ушло бы несколько часов. Но прочитать, проверить и принять ответственность за этот код всё равно должен человек.

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

Что такое атомарный коммит в Git

Атомарным обычно называют коммит, который содержит одно логически законченное изменение. Его можно понять, проверить и при необходимости откатить отдельно от остальных изменений. Ключевое слово здесь не «маленькое», а «одно».

Один коммит — одна причина изменения

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

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

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

Атомарный коммит не обязательно должен быть маленьким

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

Количество строк удобно измерять, поэтому вокруг него появляется много правил. Но атомарность измеряется не строками, а возможностью разделить изменение без потери смысла. Если коммит можно разложить на две независимые части и после этого обе останутся понятными и рабочими, скорее всего, стоило так и сделать.

Пять признаков атомарного коммита

Один законченный смысл

Коммит отвечает на один вопрос: что изменилось и зачем это изменение существует? Если для ответа приходится использовать «а ещё», внутри, вероятно, находится больше одного смысла.

Рабочее состояние проекта

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

Возможность независимого ревью

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

Безопасный откат и перенос изменения

Хороший атомарный коммит можно отменить через git revert или перенести через cherry-pick, не забрав вместе с ним случайный рефакторинг и половину другой функции. Это особенно важно, когда исправление нужно быстро перенести в релизную ветку или точечно убрать из продакшна.

Соответствие сообщения содержимому

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

Зачем нужны атомарные коммиты

Можно спокойно разрабатывать без атомарной истории. Огромное количество проектов именно так и живёт. Вопрос не в том, возможно ли это. Вопрос в цене, которую команда платит за потерянную структуру.

Чистая и понятная история Git

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

Более простое и внимательное code review

Небольшие законченные изменения проще проверять. Ревьюер сначала понимает цель коммита, затем смотрит, соответствует ли ей реализация. Ему не приходится самостоятельно распутывать несколько перемешанных причин. В рекомендациях Google по code review правильным размером изменения называется одно самодостаточное изменение. Там же отдельно советуют выносить заметный рефакторинг из функциональных изменений. При этом жёсткой универсальной границы по строкам нет: размер зависит от контекста.

Безопасные git revert, bisect и cherry-pick

Атомарность раскрывается, когда что-то пошло не так. Отменить один законченный шаг проще, чем вытаскивать исправление из коммита, где одновременно живут новая функция, рефакторинг и обновление конфигурации. С git bisect та же история: если каждый промежуточный коммит рабочий и содержит одно изменение, найти точку появления ошибки гораздо проще. Если половина коммитов не собирается, а остальные содержат по пять причин, инструмент формально работает, но пользы от него заметно меньше.

Быстрое возвращение в контекст после перерыва

Разработку постоянно прерывают: сообщения, встречи, вопросы коллег, еда, сон и иногда жизнь за пределами IDE. После перерыва требуется восстановить, что уже сделано и какой шаг был следующим. В исследовании 10 000 рабочих сессий 85 программистов только в небольшой части случаев разработчики начинали редактировать код менее чем через минуту после возвращения. Обычно сначала приходилось перемещаться по проекту и восстанавливать состояние задачи.

ACDD не отменяет переключение контекста, а коммит сам по себе тоже требует небольшого переключения на Git. Но законченный шаг уменьшает количество незавершённых мыслей, которые приходится восстанавливать. История показывает, где вы остановились, а сформулированный следующий коммит — куда собирались идти.

История проекта как коллективная память команды

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

Что такое ACDD — Atomic Commit Driven Development

Атомарные коммиты — не новшество, и сама идея формулировать коммит до написания кода тоже появилась не вчера. Когда я начал глубже собирать материалы по теме, нашёл статью Тоби Осборна о Commit Driven Development, опубликованную ещё в 2012 году. Он предлагал сначала написать сообщение будущего коммита, а затем работать, пока код не начнёт точно соответствовать этому сообщению.

Примерно тогда же Ариальдо Мартини описал предварительные сообщения коммитов. Он связывал их с фокусом, микрозадачами, небольшим Definition of Done и возвращением в контекст после перерыва. То есть я не первый разработчик, который пришёл к этой мысли, и это хорошо. Если разные люди в разное время находят похожий рабочий ритм, возможно, в нём действительно что-то есть.

Что ACDD добавляет к известным практикам

Я рассматриваю ACDD как более строгую связку нескольких идей:

  • намерение формулируется до изменения кода;
  • работа ограничивается одним инженерным смыслом;
  • результат должен быть законченным и проверенным;
  • коммит остаётся самостоятельной точкой истории;
  • следующий шаг начинается только после завершения текущего.

Просто часто сохранять изменения недостаточно. Сто маленьких коммитов wip не превращаются в ACDD. Красивые сообщения по Conventional Commits тоже ничего не гарантируют, если внутри каждого лежит случайный набор файлов.

Коммит как единица инженерного мышления

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

Главный вопрос звучит так:

Какой следующий коммит я собираюсь сделать?

Ответ заставляет остановиться ещё до первой строки кода. Что именно должно измениться? Где заканчивается действие? Как я пойму, что оно завершено? Какие проверки должны пройти?

Это небольшая проектная сессия. Иногда она занимает несколько секунд. Иногда показывает, что задача ещё непонятна и писать код рано.

Основной цикл ACDD

Намерение

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

Определение границы

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

Действие

Выполняется только сформулированное изменение. Всё найденное по пути записывается отдельно, если не требуется для завершения текущего шага.

Проверка

Разработчик смотрит diff, запускает тесты и сравнивает результат с первоначальным намерением. Если содержимое стало шире формулировки, изменение разделяется или граница формулируется заново.

Коммит

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

Чем ACDD отличается от других подходов

ACDD не пытается заменить TDD, Conventional Commits, проектирование или постановку задач. Эти практики работают на разных уровнях.

Атомарные коммиты — свойство истории, ACDD — процесс работы

Атомарность описывает результат: один коммит содержит одно законченное изменение. ACDD описывает путь: сначала определяется следующий коммит, затем под него выполняется работа. Можно создать атомарную историю задним числом через git add --patch и interactive rebase. Результат получится хорошим, но сама разработка не была направлена коммитами.

ACDD и Conventional Commits

Conventional Commits задаёт формат сообщения: тип, необязательный контекст, описание, тело и сноски. Это помогает людям и автоматическим инструментам понимать природу изменения, но не определяет его границу. Можно написать идеальный заголовок feat(auth): add OAuth support и положить внутрь пять независимых изменений. Conventional Commits отвечает на вопрос «как назвать изменение», а ACDD — «как его выделить, выполнить и проверить».

ACDD и Test Driven Development

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

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

ACDD и Spec-Driven Development

Spec-Driven Development сначала описывает требования, проект и последовательность задач. Например, такой подход используется в GitHub Spec Kit для работы с AI-агентами. Спецификация задаёт направление и целевой результат, а ACDD определяет, каким будет следующий безопасный шаг. Условно спецификация — это маршрут, а атомарные коммиты — отдельные участки пути.

Как работать по ACDD: пошаговый цикл атомарного коммита

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

До изменения кода

Ответьте на вопрос «какой следующий коммит я собираюсь сделать?»

Спросите себя не «какую задачу я сейчас решаю», а какой законченный результат должен появиться в следующей точке истории. Большая задача может звучать как «добавить восстановление пароля», но следующий коммит будет значительно конкретнее: «добавить модель токена восстановления», «реализовать создание токена», «добавить отправку письма» или «обработать смену пароля».

Сформулируйте намерение одним предложением

Например: «Добавить описание работы метода X». Эта формулировка уже задаёт область изменения. Если в процессе вы решили ещё и переименовать метод, появилась другая причина и, скорее всего, другой коммит.

Определите условия завершения

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

Во время работы

Удерживайте один поток внимания

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

Не добавляйте найденные по пути исправления

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

Пересматривайте границу, если изменение выросло

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

Перед созданием коммита

Просмотрите получившийся diff

Перед коммитом полезно посмотреть на изменения глазами ревьюера. Все ли строки относятся к заявленному намерению? Не осталось ли дебаг-кода? Не попал ли в diff автоматически изменённый файл?

Запустите тесты и проверки проекта

Каждый самостоятельный коммит должен по возможности оставлять проект рабочим. В моих репозиториях это правило формулируется прямо: каждый коммит должен собираться и проходить проверки отдельно.

Сравните содержимое с первоначальным намерением

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

Зафиксируйте изменение и переходите дальше

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

Как определить границы атомарного коммита

Хороший коммит заканчивается там, где заканчивается одна причина изменения. Формулировка звучит просто. На практике именно здесь начинаются основные споры.

Вопросы, которые помогают найти границу

Можно ли описать изменение одной причиной?

Если описание выглядит как «добавил метод и заодно обновил документацию другого модуля», причин две. Союзы «и», «а ещё», «попутно» часто показывают место разделения. Сам союз, конечно, ничего не доказывает, но это хороший повод ещё раз посмотреть на diff.

Можно ли проверить его независимо?

Ревьюер должен понимать, что именно проверяется в этом коммите. Если правильность первой части невозможно оценить без третьего следующего коммита, возможно, граница выбрана неверно. Зависимость между шагами допустима, но текущий шаг не должен быть бессмысленным обломком будущего решения.

Можно ли откатить его без отмены других изменений?

Представьте, что новая функция вызвала ошибку в продакшне. Можно ли отменить её одним revert, не возвращая старое имя класса и не убирая полезный рефакторинг? Если для отката приходится вручную выбирать строки из нескольких разных решений, граница коммита почти наверняка получилась слишком широкой.

Останется ли проект рабочим после разделения?

Не надо делить изменение только ради количества коммитов. Если первая половина не собирается, не тестируется и не имеет самостоятельного смысла, это не атом, а просто половина. Хорошая декомпозиция создаёт последовательность рабочих состояний, а не коллекцию красиво пронумерованных обломков.

Нужен ли ревьюеру следующий коммит, чтобы понять текущий?

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

Как разделять разные виды изменений

Новая функциональность

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

Исправление ошибки

Коммит должен содержать причину ошибки, исправление и доказательство результата. Обычно это регрессионный тест. Попутный рефакторинг лучше вынести до или после исправления, чтобы в диффе было видно само изменение поведения.

Рефакторинг

Рефакторинг меняет структуру, но сохраняет поведение, поэтому его полезно отделять от новой функциональности. Сначала можно подготовить код к изменению, а затем отдельно изменить поведение. В таком diff сразу видно, где перемещались конструкции, а где появилась новая логика.

Тесты и документация

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

Обновление зависимостей и конфигурации

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

Когда большой коммит всё ещё может быть атомарным

Миграции базы данных и изменения API

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

Автоматически сгенерированный код

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

Массовое переименование или форматирование

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

Изменения в нескольких репозиториях

Один Git-коммит не может охватить несколько репозиториев. Здесь атомарность становится организационной: нужны согласованный порядок публикации, обратная совместимость и ссылки между изменениями. Иногда настоящий атом находится на уровне релиза, а не отдельного репозитория.

Как писать сообщения коммитов

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

Хорошее сообщение описывает намерение изменения

Из диффа обычно можно узнать, какие строки добавлены и удалены, а сообщение должно помочь понять, зачем существует новая версия проекта. Заголовок кратко описывает результат. Тело сообщения объясняет причины, ограничения и неочевидные решения. Для простой опечатки отдельное философское эссе, конечно, не требуется.

Почему номера задачи недостаточно

Сообщение PROJ-1234 удобно связывает код с issue tracker, но ничего не говорит человеку, у которого сейчас нет доступа к задаче. Через несколько лет трекер может переехать, ссылка — исчезнуть, а репозиторий останется. Номер задачи полезен как дополнительная ссылка, но не как замена смысла.

Conventional Commits как формат сообщений

Базовая структура выглядит так:

<тип>[необязательный контекст]: <описание>

[необязательное тело]

[необязательные сноски]

Тип feat обозначает новую функциональность, fix — исправление ошибки, а BREAKING CHANGE или ! отмечает нарушение обратной совместимости. Именно эти элементы связаны с Semantic Versioning. Остальные типы можно определять по договорённости команды.

Часто используют:

  • docs — документация;
  • refactor — изменение структуры без нового поведения;
  • test — добавление или исправление тестов;
  • perf — производительность;
  • style — форматирование;
  • build — система сборки и зависимости;
  • ci — конфигурация CI;
  • chore — технические изменения, не попавшие в остальные категории.

Если одному коммиту одновременно подходят feat, fix и refactor, проблема может быть не в выборе типа. Возможно, внутри действительно находятся три изменения.

Как мысль превращается в сообщение коммита

Например, первой появляется мысль: «Добавлю описание работы метода X». Она превращается в предварительную формулировку «Добавлено описание работы метода X», после чего остаётся описать назначение метода, его ограничения и результат. На проверке мы убеждаемся, что в diff находится только нужная документация, и создаём коммит docs: описана работа метода X класса A. Вот и всё: маленькая мысль превратилась в законченный коммит, и можно переходить к следующей.

Commitlint как ограничитель, а не замена мышлению

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

Антипаттерны: какие коммиты разрушают историю Git

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

Коммит-монолит

Одна большая задача, несколько дней работы и один коммит. Внутри всё связано общей задачей, но не связано одной причиной изменения. Его сложно читать, проверять, откатывать и обсуждать.

Коммит-полиглот

Разработчик делает функцию, замечает плохое название, обновляет библиотеку, исправляет стили и пишет документацию к соседнему модулю. Каждая правка полезная, но вместе они говорят на разных языках. Это тот самый коммит «поправил по пути», после которого уже никто не может быстро объяснить общую причину изменений.

Коммит-обманка

Сообщение обещает одно изменение, а diff показывает другое. Иногда разработчик просто забыл обновить первоначальную формулировку. Иногда написал её уже после работы и вспомнил не всё.

Коммит-вселенная

«Переписал модуль». Под таким сообщением может находиться новая архитектура, изменённое поведение, удалённые функции и обновлённый публичный API. Внутри целый мир, но карты к нему нет.

Коммит-вопрос «что ты сделал?»

fix, wip, update, temp. Такое сообщение не экономит время. Оно переносит работу по восстановлению смысла на каждого будущего читателя.

Коммит-потерянная мысль

Изменение могло быть атомарным, но его причина нигде не сохранилась. Код показывает, что произошло. Почему это понадобилось, уже никто не помнит.

Атомарные коммиты в Pull Request и code review

Коммит, задача и Pull Request часто смешиваются в одну сущность. Из-за этого появляется правило «одна задача — один коммит», которое хорошо звучит, но плохо работает на больших задачах.

Одна задача может состоять из множества атомарных коммитов

Задача описывает цель, а коммиты — законченные шаги к этой цели. Если восстановление пароля потребовало восемь независимых решений, восемь понятных коммитов лучше одного большого. Один шаг — один коммит, а одна задача — связная последовательность шагов.

Один Pull Request должен рассказывать одну законченную историю

Коммит можно представить как абзац, Pull Request — как главу, а релиз — как более крупную часть истории проекта. Абзацы должны быть самостоятельными, но вместе вести к одному результату. Если половину PR можно выпустить независимо и она приносит законченную пользу, возможно, перед вами уже два PR.

Почему небольшие изменения проще ревьюить

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

Сколько строк должно быть в хорошем Pull Request

Универсального числа нет. Google приводит 100 строк как обычно разумный размер, а 1000 — как обычно слишком большой, но сразу уточняет, что всё зависит от характера изменений и количества файлов. Мне нравится ориентир до 350 строк значимых ручных изменений. Это не правило ACDD и не повод отклонять PR на 351 строку. Generated-код, удаление файлов и механическое форматирование считаются иначе. Цифра нужна как сигнал остановиться и спросить: можно ли сделать изменение меньше?

Должны ли тесты и реализация находиться в одном коммите

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

Как читать Pull Request по коммитам

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

Рабочая и опубликованная история Git

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

Допустимы ли WIP-коммиты

Локальный WIP-коммит может быть полезной страховкой. Лучше сохранить незавершённую работу, чем потерять её или держать неделю в одном рабочем каталоге. Но такой коммит является контрольной точкой, а не законченным шагом ACDD. Перед публикацией ветки его можно объединить с целевым коммитом, исправить сообщение или перестроить историю.

История работы и история принятых решений — не одно и то же

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

Когда использовать fixup и interactive rebase

Если после ревью нужно поправить конкретный атомарный коммит, удобно создать fixup, а перед слиянием присоединить его к нужному месту через interactive rebase. Так исправление оказывается рядом с причиной, а не отдельным хвостом «fix review comments». Переписывать безопасно собственную ветку, с которой никто больше не работает. Общую опубликованную ветку без договорённости лучше не трогать.

Нужно ли объединять коммиты через squash

Squash полезен, когда ветка состоит из экспериментальных и временных сохранений: он превращает шумную рабочую историю в одно понятное изменение. Но если ветка уже содержит последовательность самостоятельных атомарных решений, полный squash уничтожает этот уровень детализации и оставляет только итоговый diff. Здесь нет универсального ответа. Сначала нужно решить, какую историю команда считает полезной: один коммит на PR или последовательность законченных инженерных шагов.

ACDD при работе с AI и coding-агентами

AI способен написать код быстрее, чем человек успеет внимательно его прочитать. Это не означает, что AI всегда ускоряет разработку. Например, в исследовании METR опытные разработчики на знакомых open source проектах с исследованными инструментами в среднем потратили больше времени, хотя ожидали ускорения. Причина понятна: сгенерированный код всё равно нужно читать, исправлять и проверять. Иногда проще написать точное решение самостоятельно, чем объяснять инструменту все ограничения старой системы.

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

Почему большое изменение от AI опасно принимать целиком

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

Один цикл работы — один проверяемый коммит

При работе с агентом цикл может выглядеть так:

  1. Сформулировать одно намерение.
  2. Указать, что разрешено и что не нужно менять.
  3. Описать критерии завершения и проверки.
  4. Попросить выполнить только этот шаг.
  5. Просмотреть diff и результаты тестов.
  6. Принять коммит или отклонить изменение целиком.

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

Почему AI не должен самостоятельно определять границы ответственности

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

Как ACDD помогает сохранять понимание созданного кода

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

Плюсы и ограничения ACDD

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

Что ACDD даёт разработчику

  • меньше незавершённого контекста в голове;
  • понятную точку для продолжения работы;
  • видимый прогресс внутри большой задачи;
  • возможность безопасно откатывать отдельные решения;
  • привычку формулировать намерение до действия.

Небольшой законченный коммит даёт ощущение движения. Большая задача перестаёт быть одной стеной и превращается в последовательность ступеней.

Что ACDD даёт команде

  • более понятное code review;
  • историю инженерных решений рядом с кодом;
  • простые откаты и поиск регрессий;
  • возможность обсуждать отдельные шаги, а не весь diff целиком;
  • более предсказуемые границы изменений от AI-агентов.

Когда атомарность превращается в переизмельчение

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

Допустим ли ACDD во время исследования и прототипирования

Во время spike или знакомства с неизвестной библиотекой результат заранее не всегда понятен. В такой работе можно использовать временные контрольные точки и не пытаться сразу строить идеальную историю. Когда направление найдено, полезную часть эксперимента можно оформить в последовательность законченных изменений. Черновик имеет право быть грязным, но публиковать его как финальную историю необязательно.

Когда затраты на порядок становятся выше пользы

Если разработчик тратит больше времени на перестановку трёх очевидных коммитов, чем команда когда-либо потратит на их чтение, процесс перестал помогать цели. Порядок нужен для ускорения понимания и снижения риска, а не для соревнования по красоте git log.

Как начать применять ACDD

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

Попробуйте несколько недель и посмотрите:

  • насколько проще возвращаться в задачу после перерыва;
  • стали ли диффы понятнее;
  • уменьшилось ли количество случайных правок;
  • проще ли проходит code review;
  • можно ли безопасно откатить отдельное решение;
  • помогает ли история понять работу через месяц.

Если подход используется командой, минимальных правил достаточно:

  1. Один коммит содержит одну причину изменения.
  2. После каждого опубликованного коммита проект остаётся рабочим.
  3. Сообщение соответствует содержимому.
  4. Несвязанные правки разделяются.
  5. Границы PR обсуждаются до того, как diff вырос до нескольких тысяч строк.

Остальное можно добавлять по необходимости: Conventional Commits, commitlint, CI, шаблон сообщения и ориентир по размеру PR. Сначала договорённость, потом автоматизация.

ACDD — это не только про коммиты

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

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

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

Олег Пацай

Инженерный лидер — AI, архитектура и сложные системы

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

GitHub LinkedIn Telegram Связаться

Давайте делать сложное понятным.

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

LinkedIn Telegram Email