Как часто вы ловили себя на мысли:
- Какой смысл в этом коде?
- Этот вариант не устарел?
- Этот комментарий устарел? Я не думаю, что это описывает то, что я вижу.
- Этот пул-реквест огромен, с чего мне начать?
- Откуда взялся этот баг?
Данные вопросы характерны для совместно разработанного исходного кода. Хотя есть способы частично устранить проблемы (комментарии к коду, руководства по стилю, требования к документации), мы по-прежнему неизбежно тратим часы на то, чтобы просто попытаться понять код. К счастью, инструменты, необходимые для решения этих проблем, всегда были под рукой!
Коммиты в Git-репозиториях — это больше, чем просто точки сохранения или журналы изменений в более крупном проекте. Процитируем гитхабовский «Git Guides»:
[Коммиты] — это зафиксированные снимки состояния (snapshots) всего вашего репозитория в определенное время… основанные на логических единицах изменений. Спустя некоторые коммиты будут рассказывать историю вашего репозитория и то, как он стал таким, какой он есть сейчас.
Коммиты — это хронологическая запись о том, как и почему появилась каждая строка кода. Они даже приходят с удобочитаемыми сообщениями! В результате история коммитов в репозитории — лучший инструмент, который разработчик может использовать для объяснения и понимания кода.
По моему опыту, коммиты наиболее эффективны, когда они максимально доработаны для отправки рецензентам, другим участникам и даже себе в будущем. В этом посте будут:
- Рекомендации по организации и пересмотру коммитов.
- Практические подходы к применению данных руководящих принципов.
- Примеры грамотно составленной истории коммитов.
Как писать коммиты лучше
Разработка программного обеспечения требует большого творчества, поэтому ваши коммиты будут отражать предысторию ваших изменений, ваши цели и ваш личный стиль. Приведенные ниже рекомендации помогут вам сделать ваши коммиты эффективными средствами коммуникации.
Пока вы читаете эти рекомендации, не беспокойтесь о том, как вы сможете использовать все эти советы во время написания кода. Вы можете постепенно включить их в свой процесс разработки, каждый совет можно применять итеративно после того, как вы написали весь свой код.
Структурируйте описание
Как и в вашем любимом романе, серия коммитов имеет повествовательную структуру, которая контекстуализирует «сюжет» вашего изменения в коде. До какой-либо доработки описание ветки обычно отражает импровизированный поток сознания. Например:
- Коммит компонента A, за которым следует еще один компонента B, за которым следует один завершающий компонент A.
- Мульти-коммит с несколькими коммитами в попытке снова и снова получить правильный синтаксис CI.
- Коммит, исправляющий опечатку из предыдущего коммита.
- Коммит со смешанным набором всех отзывов.
- Коммит слияния, разрешающий конфликты с веткой
main
.
Несмотря на точный пересказ вашего пути, подобная ветвь рассказывает «историю», которая не является ни последовательной, ни запоминающейся.
Проблема
Бессистемные коммиты без четкой линии повествования могут повлиять и на того, кто будет оценивать ваш код (рецензент), и на самого разработчика.
Проверка коммита за коммитом — это самый простой способ не перегружаться изменениями в достаточно большом пул-реквесте. Если эти коммиты не рассказывают единую, удобную для понимания историю, рецензенту придется постоянно переключаться, поскольку авторские коммиты перескакивают с темы на тему. Чтобы убедиться, что более ранние коммиты правильно настроены для более поздних (например, проверка правильности использования вновь созданной функции), рецензент, получается, должен собрать описание сам; для каждого коммита нужно выяснить, какие более ранние изменения устанавливают соответствующий фоновый контекст, и переключаться между ними, что утомительно. В качестве альтернативы они будут смутно помнить некоторые детали и предположат, что предыдущие коммиты «настраивают» последующие, что, в свою очередь, создаст сложности для определения потенциальных проблем.
Но как легкомысленное отношение к созданию описания может навредить разработчику? Разработчик при работе над новым проектом часто хочет превратить проект во что-то функциональное. Этот подход, колеблющийся между «забавным» и «разочаровывающим», в конечном итоге дает хорошие результаты, но он далеко не эффективен. Действовать бездумно — без четкого следования плану — значит делать этот процесс медленнее, чем он должен быть.
Решение
Составьте план и реорганизуйте свои коммиты, чтобы соответствовать ему.
Повествование, рассказанное вашими коммитами, помогает передать смысл ваших изменений. Кроме того, как и любая история, повествование может быть любой структуры и формы.
Ваша ветка — это ваша история. Хотя повествование зависит от вас, вот несколько редакционных советов о том, как его организовать:
ДЕЛАЙТЕ | НЕ ДЕЛАЙТЕ |
Напишите план и включите его в описание пул-реквеста. | Составьте план в самом конце — попробуйте использовать его, чтобы направлять свою работу! |
Придерживайтесь одной высокоуровневой концепции для каждой ветви. | Усложните процесс и сделайте его хаотичным. |
Добавьте свой коммит «реализовать функцию» сразу после рефакторинга, который его устанавливает. | Перемещайтесь между темами в своей ветке. |
Относитесь к коммитам как к «строительным блокам» разных типов: исправление ошибок, рефакторинг, стилистическое изменение, фича и т. д. | Смешайте несколько типов «строительных блоков» в одном коммите. |
Как это сделать?
Ветку, используемую в этом примере, можно найти здесь — скопируйте ее и продолжайте практиковаться!
Предположим, я работаю над скриптом, который позволяет мне загрузить изображение, внести в него некоторые изменения (например, изменить цвета), а затем либо отобразить его, либо сохранить измененное изображение в новом месте. Моя ветка, которую я назвал feature/image-modifier
, в настоящее время выглядит так (отображается с последними коммитами внизу):
Прежде чем модифицировать ветку, я хочу наметить линию повествования. В данном случае моя «история» такова:
- Создайте базовый сценарий (без опций, просто прочитайте и отобразите изображение).
- Добавить
–output
. - Добавьте параметры обработки изображений (
--invert
и--grey
/--gray
). - Добавьте GitHub Actions CI для базового анализа.
Чтобы изменить порядок коммитов в соответствии с этим планом, я буду использовать интерактивную перебазировку, вызываемую с помощью git rebase -i
.
git rebase -i main
После запуска перебазирования мой редактор по умолчанию открывается git-rebase-todo
со списком коммитов, упорядоченных от самого старого (вверху) до самого нового (внизу). Теперь я просто вырезаю и вставляю строки в новом порядке, который я хочу реализовать для ветки:
До
После
Как только я сохраняю и закрываю свой редактор, начинается перебазирование. Я столкнулся с некоторыми незначительными конфликтами перебазирования, но я могу их разрешить и запустить git rebase --continue
, пока все оставшиеся коммиты не будут применены к ветке в их новом порядке:
Изменение размера и стабилизация коммитов
Хотя структура серии коммитов может рассказать общую историю авторской функции, именно код в каждом коммите создает программное обеспечение. Сам код может быть сложным, насыщенным и загадочным, но для совместной работы другие должны его понимать.
Проблема
Разбор кода усугубляется тем, что одновременно предоставляется либо слишком много информации, либо недостаточно. Слишком много – и вашему читателю придется прочитать и понять несколько концептуально разных тем, которые могут быть перепутаны, неверно истолкованы или просто пропущены; слишком мало – и у вашего читателя сложится неполное представление об изменениях.
Для рецензента одно из больших преимуществ проверки от коммита к коммиту заключается в том, что, подобно отдельным лекциям в рамках семестрового курса, их развитие происходит с небольшими, легко усваиваемыми изменениями. Когда большой коммит плохо структурирован, рецензент может не определить сомнительные архитектурные решения, потому что они объединяют несвязанные темы, или даже пропустить ошибку, потому что она находится в разделе, который, по-видимому, не имеет отношения к затронутой функции.
Вы можете подумать, что проблемы рецензентов будут решены с помощью как можно меньшего количества коммитов, но незавершенное изменение не позволяет им полностью оценить его во время прочтения. Когда более поздний коммит «завершает» изменение, рецензенту может быть нелегко установить связи с более ранним контекстом. Это усугубляется, когда более поздний коммит отменяет что-то из более раннего частичного коммита. «Отток» в этих ситуациях приводит к тому, что понимать такой код становится сложнее, отсюда вытекают те же последствия, что и при работе со слишком большими коммитами.
Небольшие коммиты также представляют более ощутимые проблемы. Наиболее очевидным является невозможность откатить ваш репозиторий до коммита (например, при отладке странного взаимодействия функций). Незавершенные изменения часто не удается собрать, поэтому разработчик застрянет в поиске ближайших коммитов в поисках исправления. Точно так же ошибка, сведенная к массовому коммиту, требует разбора ее смешанных изменений, что потенциально более сложная задача, чем во время первоначального обзора, т.к. разработчик начинает забывать некоторые детали проекта с течением времени.
Решение
Сделайте каждый коммит одновременно «маленьким» и «атомарным».
Чтобы наилучшим образом рассказать историю, коммиты должны свести к минимуму усилия, необходимые для понимания изменений, которые они вносят. Поскольку усилия связаны с получением «нужного» количества информации, ключ к хорошему коммиту — соответствовать установленным верхним и нижним границам этой информации.
Маленький коммит — это коммит с минимальной областью действия: он выполняет всего одну функцию. Это часто коррелирует с минимизацией модифицированных линий конуса, но это необязательное требование. Например, переименование часто используемой функции может привести к изменению сотен строк кода, но ее ограниченная область действия упрощает как объяснение, так и просмотр.
Коммит является атомарным, если он представляет собой стабильную, независимую единицу изменений. Репозиторий должен по-прежнему создаваться, проходить тесты и в целом функционировать, даже если он откатывается к этому точному коммиту без необходимости внесения других изменений. В атомарном коммите ваш читатель будет иметь все необходимое для оценки изменений в самом коммите.
Как это сделать?
Вернемся к предыдущему скрипту «модификатор изображения». Там, где мы в последний раз его оставили, ветка feature/image-modifier
выглядела так:
Коммиты можно разделять и объединять, чтобы настроить размер и объем. Я начну с идентификации коммитов, которые я хочу разделить:
— 3f1e287 (Добавили --invert и --grey)
— хотя этот коммит небольшой, он содержит две разные «вещи»: --invert
и –grey
. В соответствии с принципом малоразмерности масштаба я хочу создать по одному коммиту для каждой опции.
— 60f352d (Добавили requirements.txt + другие исправления сборки)
— добавление requirements.txt
необходимо для успешного запуска скрипта в любом контексте, тогда как другие исправления сборки относятся конкретно к определению GitHub Action CI. Поскольку они касаются совершенно разных вопросов, относящихся к разным аспектам повествования, я разделю их.
Чтобы разделить их, я буду следовать методу, описанному в документации Git, и перебазирую коммиты, которые я хочу разделить, помеченные значком edit
или e
:
До
После
После сохранения и закрытия моего редактора rebase применяет коммиты, пока не достигнет первого кандидата на разделение. Для каждого разделения я отменяю первоначальный коммит с помощью git reset
, затем делю его содержимое на два новых соответствующего размера (используя git add -p
и git commit
), затем, наконец, продолжаю перебазирование с помощью git rebase --continue
. Когда перебазирование завершено, коммиты разделяются, как указано ранее:
Следующий шаг — объединить коммиты, которые являются неполными или слишком маленькими по иным причинам:
— 6a885eb (WIP)
и 692f477 (Завершили скрипт)
— первый является неполным коммитом, а последний завершает его, чтобы сформировать минимальную стабильную версию сценария.
– 692f477 (Завершили скрипт)
и 54b3e6d (Создаем requirements.txt)
– requirements.txt
– необходимо установить зависимости в «минимальном» скрипте начального коммита.
– 9c049cb (Добавили GitHub Actions CI .yml)
и (73e6da5) Пофиксили CI build
— все исправления CI — это исправления исходного сценария GitHub Actions, необходимые для успешной сборки.
Я делаю это с другой интерактивной перебазировкой, на этот раз используя fixup
(или f
) или squash
(или s
) в зависимости от того, исправляю ли я непреднамеренные проблемы в коммите или комбинирую коммиты с совершенно новым сообщением, соответственно:
До
После
Когда я закрываю редактор, я создаю новое сообщение коммита для смешанных коммитов «WIP» и «Завершили скрипт» из-за использования squash
:
Я изменяю сообщение коммита на «Создание начального скрипта модификатора изображения», сохраняю свой редактор и продолжаю. Остальные ‘fixup’ применяются без каких-либо конфликтов, поэтому перебазирование завершается коммитами, объединенными как указано на картинке:
Объясняем контекст
Коммиты — это больше, чем просто код, который они содержат. Несмотря на то, что они являются темой для многих шуток, сообщения коммитов чрезвычайно ценны, но часто упускаются из виду компонентом коммита. Самое полезное – это возможность напрямую поговорить со своей аудиторией и объяснить изменения своими словами.
Проблема
Даже с четким повествованием и коммитами соответствующего размера изменение все равно может ввести читателей в замешательство. Это особенно верно в отношении крупных проектов или проектов с открытым исходным кодом, где рецензент или другой будущий читатель (даже вы сами!), скорее всего, не будут осведомлены о деталях реализации или нюансах кода, который вы изменили.
Код редко бывает настолько очевидным, как может показаться автору, и даже простые изменения могут быть неправильно истолкованы. Например, то, что может показаться ошибкой, на самом деле может быть функцией, реализованной для решения не связанной с ней проблемой. Не понимая цели первоначального изменения, разработчик может непреднамеренно изменить ожидаемое поведение пользователя. И наоборот, то, что кажется преднамеренным, могло быть изначально ошибкой. Неправильное толкование может привести к тому, что разработчик закрепит небольшую ошибку как «функцию», которая годами будет негативно влиять на работу пользователей.
Даже в лучшем случае плохо объясненные изменения будут замедлять рецензентов и участников, когда они пытаются интерпретировать код, напрасно тратя время и силы.
Решение
Опишите, что вы делаете и почему вы это делаете в сообщении коммита.
Поскольку вы пишете для аудитории, сообщение коммита должно четко сообщать то, что читатели должны понять. Как разработчик, вы должны уже достаточно хорошо знать предысторию и реализацию, чтобы объяснить их. Вместо того чтобы писать слишком длинные (и склонные к устареванию) комментарии к коду или помещать все в монолитное описание пул-реквеста, вы можете использовать сообщения коммитов, чтобы по частям предоставлять разъяснения по каждому изменению.
«Что» и «почему» далее разбиваются на детали высокого и низкого уровня, все из которых можно сформулировать в виде четырех вопросов, на которые нужно отвечать в каждом сообщении коммита:
Что вы делаете | Почему вы это делаете | |
Высокий уровень (стратегический) | Цель (что это дает?) | Контекст (почему код делает то, что он делает сейчас?) |
Низкоуровневый (тактический) | Реализация (что вы сделали для достижения своей цели?) | Обоснование (почему внесено это изменение?) |
Как это сделать?
Оглядываясь еще раз на ветку feature/image-modifier
:
Перед отправкой на рассмотрение каждый из этих коммитов должен быть переписан, чтобы включить в него цель, реализацию, контекст и обоснование их соответствующего изменения. Ради этого примера я переформулирую только коммит «Разрешить пользователям использовать вариант написания –gray».
Этот коммит не самый последний в ветке (в этом случае я могла бы использовать git commit --amend
для его изменения), поэтому мне снова нужно использовать интерактивную перебазировку, чтобы внести в него изменения. На этот раз единственное, что мне нужно сделать, это изменить коммит с pick
на reword
(или r
):
До
После
После закрытия git-rebase-todo
открывается новый экземпляр редактора с исходным сообщением коммита:
Это сообщение, как и многие однострочные сообщения фиксации, содержит лишь расплывчатое описание цели и полностью отсутствует объяснение того, как было реализовано изменение или почему это изменение необходимо.
Когда я пересматриваю свои коммиты, мне нравится упорядочивать разделы «что» и «почему» следующим образом:
- Цель (как название)
- Контекст
- Обоснование
- Реализация
Применяя этот план к фиксации, которую я здесь редактирую, я пишу примерно по одному (короткому) предложению для каждого элемента:
Вышеупомянутое сообщение охватывает весь контекст, который может понадобиться читателю, но является немного чрезмерным объяснением такого простого изменения. Хотя очень важно, чтобы сообщение включало некоторую форму цели/реализации/контекста/обоснования, вполне приемлемо адаптировать уровень детализации к сложности изменения. Например, более краткое, но все же полностью объясненное сообщение коммита может вместо этого выглядеть так:
Делаем проекты лучше
Используя рекомендации, изложенные выше, вы можете уменьшить влияние проблем, возникающих при разработке программного обеспечения, включая проверку кода, поиск ошибок и анализ первопричин.
Код-ревью
Рецензирование даже самых больших пул-реквестов может быть управляемым и простым процессом, если вы можете оценивать изменения на основе коммита за коммитом. Каждое из указаний, описанных ранее, направлено на то, чтобы сделать коммиты читабельными; чтобы коммиты были информативными, можно использовать руководящие принципы в качестве шаблона.
- Определите повествование, прочитав описание запроса на включение и список коммитов. Если кажется, что коммиты перескакивают между темами или адресами, оставьте комментарий с просьбой внести уточнения или внести изменения.
- Мельком просмотрите сообщение и содержимое каждого коммита, начиная с начала ветки. Проверьте малоразмерность и атомарность, убедитесь, что коммит выполняет одну функцию и не включает в себя какие-либо незавершенные реализации. Рекомендую разделять или объединять коммиты с неправильной областью действия.
- Внимательно читайте каждый коммит. Убедитесь, что сообщение коммита достаточно объясняет код, сначала проверив, что реализация соответствует цели, а затем, что код соответствует заявленной реализации. Используйте контекст и обоснование, чтобы понять код. Если какая-либо необходимая информация отсутствует, обратитесь за разъяснениями к автору.
- Наконец, закончив историю изменений коммитов всеобъемлющим описанием, убедитесь, что код эффективен и не содержит ошибок.
Поиск ошибок с помощью git bisect
Если вы когда-либо сталкивались с ошибками в работе программы и не знали, когда это произошло, инструмент git bisect
для вас. В частности, git bisect
– это инструмент, встроенный в Git, который при наличии заведомо исправных коммитов (например, вашего последнего стабильного развертывания) и заведомо плохих коммитов (сломанных) будет выполнять бинарный поиск коммитов, чтобы найти в каком из них ошибка.
Каким бы полезным он ни был, git bisect
требует, чтобы каждый коммит, через который он проходит, был одновременно атомарным и маленьким. Если не атомарный, вы не сможете проверить стабильность репозитория при каждом коммите; если не маленький, исходный коммит вашей ошибки может быть настолько большим, что вы в конечном итоге будете неэффективно читать код построчно, чтобы найти ошибку.
Анализ причин
Предположим, вы использовали что-то вроде git bisect
для изоляции исходного коммита с ошибкой. Если вам повезет, основная проблема будет лежать на поверхности, и вы сможете исправить ее немедленно. Чаще всего все не так просто; код, вызывающий ошибку, может быть необходим для другой функции или не имеет смысла в качестве источника ошибки, которую вы видите. Вам нужно понять, почему код был написан, и для этого вы можете снова использовать историю коммитов для поиска причин.
Есть два основных инструмента, которые помогут вам искать среди коммитов: git blame
и git log
.
git blame
аннотирует файл коммитом, который изменил его последним:
Это может быть особенно полезно для определения того, какие коммиты изменяют одну и ту же область кода, которую вы затем можете прочитать, чтобы определить, плохо ли они взаимодействуют.
Для более общего поиска фиксации вы можете использовать git log
. В простейшей форме git log
отобразит список коммитов в обратном хронологическом порядке, начиная с HEAD
:
Отображаемый список коммитов также можно отфильтровать по файлу (файлам), по имени функции, по диапазону строк в файле, по тексту сообщения коммита и т. д. Как и в случае с git blame, данные отфильтрованные списки коммитов могут помочь вам построить полную историю изменений, которые включают в себя конкретный файл или функцию, и в конечном итоге направить вас к основной причине вашей ошибки.
И напоследок
Хотя качество коммитов является субъективным и иногда трудно поддающимся количественной оценке, оно может иметь огромное значение для качества жизни разработчика в любом проекте: старом или новом, большом или маленьком, с открытым или закрытым исходным кодом. Чтобы сделать оптимизацию коммитов частью вашего процесса разработки, следуйте некоторым рекомендациям:
- Организуйте свои коммиты в повествование.
- Сделайте каждую фиксацию как маленькой, так и атомарной.
- Объясните «что» и «почему» в вашем изменении в сообщении коммита.
Эти рекомендации, а также их практическое применение демонстрируют, насколько эффективными могут быть коммиты, когда они используются для контекстуализации кода. Независимо от того, что вы с ними делаете, коммиты расскажут историю вашего проекта; данные советы помогут вам сделать процесс разработки лучше.
Комментарии