Что общего у гардероба Барака Обамы с хорошей историей

Dec 02 2022
Одним летним днем ​​в конце августовской жары 2014 года тогдашний президент США Барак Обама принял решение, которое шокировало всю страну: он надел другой костюм. Возникшая в результате полемика о коричневом костюме доминировала в цикле новостей и распространялась по разным причинам, но в конечном итоге она была вызвана новизной самого костюма.

Одним летним днем ​​в конце августовской жары 2014 года тогдашний президент США Барак Обама принял решение, которое шокировало всю страну: он надел другой костюм. Возникшая в результате полемика о коричневом костюме доминировала в цикле новостей и распространялась по разным причинам, но в конечном итоге она была вызвана новизной самого костюма. Подобно черным водолазкам Стива Джобса и серым рубашкам Марка Цукерберга, Обама каждый день носил одни и те же синие или серые костюмы .

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

Итак, какое отношение все это имеет к любимой всеми системе контроля версий, git? Как и во многих случаях в программировании, не существует «правильного» способа структурировать коммит git или управлять историей git проекта; вы должны просто выбрать руководящий принцип и организовать вокруг него свои паттерны. Я лично верю в выбор стратегии, которая снижает усталость от принятия решений (и «умственную усталость» в более широком смысле) для всех различных «потребителей» коммита. Я более подробно расскажу о том, как это сделать ниже (и не стесняйтесь просто перейти к этому пронумерованному списку, если хотите), но я думаю, что очень важно сначала объяснить, почему я так считаю. И нам нужно начать с того, для кого на самом деле предназначен коммит.

Чья это история?

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

Имея это в виду, стоит отметить, что есть две широкие категории людей, которые в какой-то момент просматривают конкретный коммит:

  1. Рецензенты кода
    Те, кто просматривает фиксацию до того, как она будет добавлена ​​в историю в процессе проверки кода. Этих людей обычно называют «ревьюверами кода». В хорошей команде каждый в какой-то момент будет рецензентом кода, и для каждого набора новых изменений кода может быть несколько изменений.
  2. Детективы кода
    Те, кто просматривает коммит после его слияния. Как правило, это люди, которые просматривают историю, чтобы попытаться понять, почему что-то было добавлено или когда появилась ошибка. За неимением лучшего названия я буду называть этих людей «детективами по коду», чтобы отличить их от людей, описанных выше.

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

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

Решения, Решения

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

Без какого-либо дополнительного контекста при просмотре одной строки «добавленного» кода необходимо принять следующие решения:

  1. Это совершенно новая строка кода?
  2. Если это не новая строка кода, может быть, это существующая строка кода, которая просто была перенесена откуда-то еще?
  3. Если это не новая строка кода и она не была перемещена, является ли это тривиальной модификацией существующей строки (например, изменением форматирования) или законным логическим изменением?
  4. Если это либо совершенно новая строка кода, либо модификация, которая приводит к логическому изменению, то зачем это делается? Правильно ли это сделано? Можно ли его упростить или улучшить?

Мы можем видеть аналогичный процесс для каждой «удаленной» строки кода:

  1. Эта строка полностью удаляется?
  2. Если он не удаляется полностью, он перемещается или модифицируется?
  3. Если он не удаляется полностью и не просто перемещается, является ли это результатом тривиальной модификации (например, форматирования) или результатом логического изменения?
  4. Если это действительно логическая модификация, то почему она модифицируется? Правильно ли это делается?
  5. Если линия удаляется полностью, почему она больше не нужна?

Итак, это, наконец, возвращает нас к усталости от принятия решений:

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

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

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

1. Размещайте тривиальные модификации в собственных коммитах

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

  • Изменения форматирования кода
  • Переименование функции/переменной/класса
  • Переупорядочивание функций/переменных/импортов внутри класса
  • Удаление неиспользуемого кода
  • Перемещение файлов

Рассмотрим следующий коммит, смешивающий тривиальные изменения с нетривиальными:

Сообщение коммита: «Обновить список допустимых фруктов»

Сколько времени вам понадобилось, чтобы заметить нетривиальные изменения? Теперь посмотрите, что произойдет, если эти два изменения разделить на два отдельных коммита:

Сообщение фиксации: «Обновить допустимое форматирование списка фруктов»

Коммит-сообщение: «Добавить даты в допустимый список фруктов»

Фиксацию «только форматирование» можно по существу игнорировать, а дополнения кода можно обнаружить сразу же с первого взгляда.

2. Поместите рефакторинг кода в собственные коммиты

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

Например, как быстро вы сможете обнаружить здесь ошибку?

Сообщение фиксации: «Обновить логику подсказки»

Как насчет выделения рефакторинга?

Сообщение фиксации: «Извлечь ставку чаевых по умолчанию»

Сообщение фиксации: «Разрешить индивидуальную ставку чаевых»

3. Разместите исправления ошибок в собственных коммитах

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

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

4. Размещайте отдельные логические изменения в собственных коммитах

После разделения вышеперечисленных типов изменений у вас должен остаться один коммит с законными, логическими изменениями для добавления/обновления/удаления функциональности. Для небольшого краткого изменения этого часто бывает достаточно. Однако иногда этот коммит добавляет совершенно новую функцию (с тестами) и достигает 1000+ строк (или больше). Git не будет представлять эти изменения связным образом, и для успешного понимания этого кода проверяющему потребуется пропустить и сохранить большую часть этих строк в памяти сразу, чтобы следовать дальше. Наряду с усталостью от принятия решений, связанной с обработкой каждой строки, растягивание рабочей памяти таким образом утомительно для ума и, в конечном счете, неэффективно.

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

5. Объедините любые изменения обзора с коммитами, которым они принадлежат.

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

- <Initial commits>
- Respond to review feedback
- Work
- More work
- Addressing more review feedback

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

Рассмотрим это начальное изменение, за которым последовало несколько «рабочих» коммитов:

А теперь представьте, что вы видите эти изменения уже в самом процессе (или даже годы спустя). Разве вы не хотели бы просто увидеть следующее?

6. Перебазируйте, перебазируйте, перебазируйте!

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

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

Если вы заботитесь о создании чистой истории (а вы должны!), то лучшим вариантом здесь будет перебазирование: все изменения строятся упорядоченно и линейно. Вам не нужны причудливые инструменты для просмотра истории, чтобы понять взаимосвязь между ветвями.

Рассмотрим следующую историю проекта, в которой используется слияние ветвей:

Использование стратегии слияния, а не перебазирования, может привести к запутанной истории.

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

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

В первом случае отношения между изменениями должны быть нанесены на карту, обдуманы и расшифрованы; во втором вы просто течете вперед и назад во времени.

Кто-то может возразить, что на самом деле перебазирование разрушает историю; что вы теряете историю изменений, сделанных, чтобы получить некоторый код в его окончательной форме перед слиянием. Но такая история редко бывает полезной и очень зависит от разработчика: путь одного человека может отличаться от пути другого, но важно видеть серию коммитов в истории, отражающих окончательные изменения, которые они представляют… какой бы процесс ни потребовался, чтобы получить там. Да, здесь есть особые случаи, когда коммиты слияния неизбежны, но они должны быть исключением. И часто сценариев, которые вызывают это (например, долгоживущие ветки функций, совместно используемые несколькими членами команды), можно избежать, используя более эффективные рабочие процессы (например, используя флаги функций вместо общих ветвей функций ).

Контраргументы

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

«То, что вы так сильно беспокоитесь о структуре коммитов, замедляет разработку».

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

«В моем проекте используются инструменты, которые не позволяют вносить даже тривиальные изменения форматирования».

Я согласен с тем, что это отличный способ свести к минимуму вред, который в противном случае причиняется оттоком кода, связанным с форматированием. Как разработчик Android, я твердо верю в использование автоформатеров всей командой и клянусь такими инструментами, как ktlint . Тем не менее, я также знаю из первых рук, настроив все эти инструменты, что они не идеальны, и существует множество возможных изменений форматирования, о которых они совершенно не знают. И, как обсуждалось выше, некоторые тривиальные изменения — это не просто изменения форматирования, например изменение порядка кода. Всегда будут тривиальные изменения кода, которые можно внести, и поэтому должен быть план, как лучше с ними справиться.

«Не все сайты хостинга git разрешают запросы на вытягивание с несколькими коммитами».

Это очень верно! Мои рекомендации в первую очередь основаны на использовании таких инструментов, как GitHub и GitLab , которые позволяют PR иметь столько коммитов, сколько вам нужно, но есть такие инструменты, как Gerrit , которые этого не делают. В этом случае просто рассматривайте каждый коммит как отдельный PR. Это создает дополнительные накладные расходы для автора (а иногда и для рецензентов), но я считаю, что в долгосрочной перспективе это стоит затраченных усилий. Могут быть даже способы упростить этот процесс и связать эти отдельные PR друг с другом, например, используя «зависимые изменения» в Gerrit.

«Один коммит гарантирует, что все изменения компилируются и проходят тесты».

Это тоже очень хороший момент. Автоматические проверки, которые выполняются на сайтах хостинга git, обычно выполняются только для всего набора изменений, а не для каждого отдельного коммита. Если на этом пути есть сломанная фиксация, которая исправлена ​​более поздними изменениями, нет никакого способа автоматически обнаружить это. Вы хотите, чтобы каждый коммит мог работать сам по себе на тот случай, если вам когда-нибудь понадобится вернуться и протестировать состояние кода на тот момент, чтобы отследить ошибки и т. д. Как понятное правило, это должно требоваться для каждого коммита в multi-commit PR как для компиляции, так и для прохождения любых соответствующих тестов, но нет способа строго обеспечить это (кроме того, чтобы сделать каждый коммит своим собственным PR). Это требует бдительности, но это просто то, что нужно сопоставить с преимуществами, которые дает разделение кода.

«Один коммит обеспечивает наибольший контекст для всех изменений».

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

#74 здесь — это ссылка на запрос на слияние, который создал этот коммит, что позволяет просматривать связанные коммиты из того же PR.

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

Последние мысли

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

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

Брайан работает в Livefront , где он всегда пытается сделать немного больше (git) истории.