Как устроен дифф
Дифф — это список изменений: какие строки исчезли и какие появились. Всё. Никакой магии в нём нет, и читать его можно, зная язык программирования весьма поверхностно.
Устроен он так. Перед каждым куском стоит имя файла. Строки со знаком минус — удалённые, со знаком плюс — добавленные, без знака — соседние, они нужны только чтобы понять, где это происходит.
Одна строка удалена, две добавлены. Соседние строки без знака показывают, что это происходит внутри функции скидки.
Важная деталь, которую упускают новички: правка строки в диффе выглядит как удаление плюс добавление. Никакого отдельного знака «изменено» не существует. Поэтому строка, которая исчезла, и строка, которая появилась, часто оказываются двумя версиями одного и того же — и сравнивать их надо между собой.
Второе, что стоит знать: дифф не показывает того, чего не изменилось. Это очевидно и тем не менее регулярно подводит. Если агент добавил новую функцию, а старую, которая делает то же самое, оставил на месте, — в диффе будет только добавление. Ничего подозрительного, пока не задашь вопрос «а старая-то куда делась».
Третье: дифф не показывает порядка. Он показывает итог. Агент мог написать одну версию, потом переписать, потом вернуться к первой — в диффе останется только разница между началом и концом. Путь виден в журнале работы, о котором был второй урок; в диффе его нет.
И четвёртое, самое практичное: у диффа есть размер, и этот размер сам по себе является информацией. К этому вернёмся в последней главе урока.
Стоит сразу снять и одно распространённое возражение. «Я не читаю чужой код, я читаю сводку и запускаю тесты» — так говорят те, у кого пока не случалось неприятностей. Проблема этого подхода в том, что он проверяет только то, о чём кто-то заранее подумал: тест ловит ошибку, для которой он написан. Дифф — единственное место, где видно то, о чём никто не думал вовсе, и именно там живут самые дорогие сюрпризы.
Прежде чем перейти к порядку чтения, одно слово о том, где дифф смотреть. Агент показывает его сам, и в облаке, и в терминале. Но есть и второй способ, который иногда удобнее: посмотреть его средствами системы контроля версий — там можно читать не спеша, листать, искать и сравнивать с любой точкой истории.
git diff
git diff --stat
Вторая команда показывает только сводку по файлам: сколько строк в каждом изменено. С неё и стоит начинать — об этом следующая глава.
Порядок чтения
Порядок чтения имеет значение. Тот, кто читает дифф сверху вниз, находит меньше, чем тот, кто читает его в правильном порядке, — и это не вопрос внимательности.
Шаг первый: список файлов. Не строки — имена. Прежде чем смотреть, что изменилось, посмотрите, где изменилось. Один этот шаг занимает пять секунд и находит больше половины неожиданностей.
git diff --stat
src/cart.js | 4 +++-
test/cart.test.js | 8 ++++++++
package.json | 2 +-
src/format.js | 12 +++++-------
Посмотрите на этот вывод глазами человека, который просил поправить итог корзины. Первые два файла ожидаемы. Третий — список зависимостей — уже вопрос: зачем правке расчёта менять версии? Четвёртый — форматирование — вопрос второй.
Шаг второй: объём. Сколько всего строк? Сравните с тем, сколько вы ожидали. Про это отдельная глава, здесь достаточно правила: оценку делают до открытия диффа, иначе полученный объём будет казаться правильным сам по себе.
Шаг третий: удалённые строки. Именно удалённые, и именно раньше добавленных. Причина простая: пропажу заметить труднее, чем появление. Добавленная строка сама просится, чтобы её прочитали. Исчезнувшая проверка на пустой список не просится никак — её надо искать.
Шаг четвёртый: добавленные строки. Теперь можно читать по существу: делает ли новый код то, что нужно, и делает ли он это так, как принято в проекте.
Отдельно про соседние строки — те, что без знака. Их роль не украшать: они показывают обстановку. Строка, добавленная внутри условия, и строка, добавленная снаружи, выглядят в диффе почти одинаково, а работают совершенно по-разному. Соседние строки — единственный способ это различить.
Есть и приём, который сильно упрощает третий и четвёртый шаги: читайте дифф с вопросом, а не с чистого листа. Вопрос формулируется до открытия: «где здесь учитывается количество?» Тогда глаз ищет ответ, а не скользит по строкам, и всё, что в ответ не входит, само выделяется как лишнее. Без вопроса чтение превращается в проверку синтаксиса — а синтаксис проверит машина, и проверит лучше.
И практический совет для длинного диффа: читайте его по файлам, а не подряд. Дифф на четыреста строк, прочитанный подряд, превращается в мелькание; тот же дифф, прочитанный по одному файлу за раз с вопросом «что здесь должно было измениться», читается вдвое быстрее и находит вдвое больше.
Красные флаги
Теперь список признаков, каждый из которых означает «остановись». Ни один не означает «неверно» — но каждый обязан быть объяснён, и объяснить его должны вы себе, а не сводка вам.
Изменён файл, о котором не просили. Самый частый и самый информативный флаг. Бывает законным: правка расчёта иногда требует правки того, кто его вызывает. Бывает и другим: «заодно привёл к общему стилю».
Правка в тесте вместо кода. Тест упал, и агент поправил тест. Законно ровно в одном случае: если тест действительно проверял неверное. Во всех остальных это подгонка, и она превращает вашу защиту в решето.
Тест теперь ожидает неверный ответ. Тесты зелёные, ошибка на месте — и сводка честно скажет «все тесты проходят».
Удалена проверка или условие. Минус на строке, которая что-то проверяла, — всегда вопрос. Возможно, проверка была лишней. Возможно, она мешала, и её убрали.
Обновлена версия зависимости. Строчка в списке пакетов выглядит безобидно и тянет за собой чужой код, который вы не читали. Правка расчёта скидки не требует новой версии библиотеки — а если требует, об этом стоит услышать словами.
Изменено публичное имя. Переименованная функция или поле — это изменение, которое затрагивает всех, кто ими пользуется, включая тех, о ком агент не знает: другой сервис, чужой скрипт, выгрузку в бухгалтерию.
Правка в файле настроек. Настройки часто выглядят как код и работают как рычаги. Изменённый параметр может тихо поменять поведение там, где никаких строк не менялось.
И один флаг, который не попал в список, но стоит всех прочих: строка, которую вы не понимаете. Не «сложная», а именно непонятная — вы не можете сказать, что она делает. Это причина спросить, а не причина принять. Агент объяснит за десять секунд, и в половине случаев объяснение окажется интереснее строки.
Про флаги стоит сказать и то, чего среди них нет. Среди них нет «длинной строки», «непривычного стиля» и «я бы написал иначе». Это вопросы вкуса, и разбирать их в диффе агента — тратить внимание там, где его не хватает на важное. Стиль либо задан правилами и проверяется линтером, либо не важен; спорить о нём построчно — худший из возможных способов.
Полезно завести и обратную привычку: замечать, когда флагов нет вовсе. Дифф, в котором изменено ровно то, о чём просили, ровно в том объёме, без единого «заодно», — это не повод для скуки, а признак, что поручение было хорошим. Такие диффы стоит запоминать вместе с поручениями, которые к ним привели.
Объём как сигнал
Последняя мерка, и самая недооценённая: объём.
Правило одно и простое: прикиньте объём до того, как открыли дифф. «Тут надо поменять условие в одном месте и добавить тест» — значит, ожидаем строк пять-десять в двух файлах. Теперь смотрим и сравниваем.
Порядок важен. Если сначала посмотреть, а потом подумать, полученный объём покажется правильным сам по себе: тридцать строк — ну, значит, так было нужно. Это работает и с людьми, и с собой, и называется это подгонкой ожиданий под факт.
Больше, чем ожидали. Самый частый случай, и у него три обычные причины. Агент сделал «заодно» что-то соседнее. Агент выбрал более общее решение, чем требовалось. Агент наткнулся на то, что вы не учли, и это надо услышать словами.
Меньше, чем ожидали. Реже, но опаснее. Возможно, задача была проще, чем казалось. Возможно, сделана только часть. Возможно, сделана не та часть. Маленький дифф почему-то проверяют менее внимательно, чем большой, — а зря: «починил» одной строкой там, где нужно было три, выглядит убедительнее любого многословия.
Отсюда правило для планирования работы: задача, дающая дифф больше сотни строк, должна быть разбита. Не потому что агент не справится — справится. А потому что вы не прочитаете, а значит, весь урок про проверку пропадает впустую.
Есть и полезный приём для тех случаев, когда крупный дифф всё-таки получился. Попросите агента разложить изменения по смыслу: «раздели то, что ты сделал, на отдельные коммиты по смыслу». Триста строк одним куском не читаются; те же триста, разложенные на пять коммитов с понятными названиями, читаются за десять минут.
Тот же объём. Но теперь понятно, что читать внимательно, а что пролистать: четвёртый коммит — механический перенос.
И последнее наблюдение по этому уроку, из тех, что стоят практики. Ошибка, которую находят в диффе, стоит минуту. Та же ошибка, найденная после слияния, стоит день. Та же ошибка, найденная в работе, стоит доверия — и это единственная из трёх цен, которую нельзя оплатить временем.