За пределами кода
Быть хорошим инженером-программистом – значит не просто писать работающий код. Это значит писать код, который другие (включая вас из будущего) смогут понять, сопровождать и развивать. Это умение ясно излагать мысли, вдумчиво вносить вклад и быть достойным гражданином экосистем, в которых вы участвуете, – будь то open source или проприетарная разработка.
Односторонняя коммуникация
Немалая часть программной инженерии – это тексты для людей, у которых нет вашего текущего контекста: для коллег, которые присоединятся позже, мейнтейнеров, которым достанется ваш код, или вас самих через полгода, когда вы уже забудете, почему приняли то или иное решение. Ключевой совет для всех текстов такого рода: ваша цель – зафиксировать и передать почему, а не только что. «Что» обычно объясняет само себя, а вот почему – это добытое тяжким трудом знание, которое легко теряется со временем.
Пожалуй, самая распространённая форма общения инженера с инженером (не считая самого кода) – комментарии в коде. По моему личному опыту, немалая доля комментариев бесполезна. Но так быть не должно! Хорошие комментарии объясняют то, чего сам код объяснить не может: почему что-то сделано именно так, а не как оно работает (это как раз показывает код). Они могут сэкономить часы недоумения, тогда как плохие комментарии добавляют шум или, хуже того, вводят в заблуждение.
Виды комментариев, которые почти всегда себя оправдывают:
- TODO: Помечайте незавершённый или неотшлифованный код, но
оставляйте достаточно контекста, чтобы кто-то другой понял, что
осталось сделать и почему это отложили. «TODO: оптимизировать» –
бесполезно; «TODO: этот цикл за O(n²) годится при
n<100, но при масштабировании понадобится индексация» – уже руководство к действию. - Ссылки на источники: Давайте ссылки на внешние источники, когда код реализует алгоритм из научной статьи, адаптирует чужой код или кодирует поведение, заданное в документации. Используйте постоянные ссылки (permalinks). Отмечайте любые расхождения с источником.
- Обоснования корректности: Объясняйте, почему нетривиальный код даёт правильный результат. Код показывает шаги; комментарий объясняет, почему эти шаги работают.
- Выстраданные уроки: Если вы потратили 30+ минут на отладку, а исправлением оказалось неочевидное заклинание, задокументируйте его. Ваше прошлое «я» не догадывалось, что это понадобится; будущие читатели тоже не догадаются.
- Обоснование констант: Магические числа заслуживают объяснения. Почему 1492? Почему 16 бит? Значение выбрано случайно, подобрано по результатам тестов или необходимо для корректности? Даже «выбрано произвольно» – полезная информация.
- Несущие решения: Если корректность зависит от невинной на вид детали реализации (например, «здесь обязан быть BTreeSet, потому что ниже важен порядок итерации»), явно скажите об этом.
- «Почему не»: Когда вы сознательно отказываетесь от очевидного подхода, объясните почему. Иначе кто-нибудь позже это «исправит» и всё сломает.
README (у вас же он есть, правда?) – тоже нередко первая точка контакта с другими разработчиками. Хороший README сразу отвечает на четыре вопроса: Что это делает? Почему мне это должно быть интересно? Как этим пользоваться? Как это установить? Именно в таком порядке. Стройте его как воронку: однострочное описание и, возможно, наглядное демо в самом верху, чтобы человек за считанные секунды понял, решает ли это его проблему, а дальше постепенно наращивайте глубину. Показывайте использование до установки – люди хотят увидеть, что они получат, прежде чем ввязываться в шаги настройки.
Сообщения коммитов – ещё один вид «письма для других», которым часто
пренебрегают. Их нередко пишут в духе «fixed blah» или «added foo», и
хотя иногда этого достаточно, легко забыть, что именно они образуют
историческую летопись того, почему кодовая база развивалась так, а
не иначе. Когда кто-то (включая вас!) запускает git blame, пытаясь понять
озадачивающее изменение, хорошие сообщения коммитов должны давать ему
ответы.
В общем случае тело сообщения должно отвечать на вопросы:
- Какая проблема вынудила внести это изменение?
- Какие альтернативы вы рассматривали?
- Каковы компромиссы и последствия?
- Что в этом подходе может оказаться неожиданным?
Разумеется, уровень детализации должен соответствовать сложности. Исправлению опечатки в одну строку хватит одного заголовка. Исправление коварного состояния гонки, на отладку которого ушли часы, заслуживает нескольких абзацев с описанием проблемы и решения.
Для сложных изменений бывает полезно следовать структуре «Проблема → Решение → Последствия»: начните с вынуждающего фактора или ограничения, затем объясните, что изменилось и какие ключевые проектные решения были приняты, и затем перечислите заслуживающие внимания последствия (положительные и отрицательные). Последняя часть особенно важна: настоящая инженерия – это баланс интересов, и если зафиксировать, что компромисс был намеренным, будущие разработчики не подумают, что вы просто не заметили проблему.
LLM могут помогать с написанием сообщений коммитов. Однако если просто указать модели на ваше изменение и попросить написать к нему сообщение коммита, LLM будет видеть только что, но не почему. И получившееся сообщение окажется по большей части описательным (то есть противоположностью тому, что нам нужно!). Если вы изначально делали изменение с помощью LLM, попросить её написать коммит в той же сессии может быть куда лучшим вариантом: ваш разговор с LLM сам по себе – богатый источник контекста об изменении! Иначе (или вдобавок) есть полезный трюк: явно скажите LLM, что вам нужно сообщение коммита, сфокусированное на «почему» (и прочих нюансах из заметок выше), а затем велите ей спрашивать вас о недостающем контексте. По сути, вы играете роль своего рода MCP-«инструмента», с помощью которого агент для программирования может «прочитать» контекст.
По мере того как ваши изменения усложняются, не забывайте также
логично разбивать их на коммиты (git add -p вам в помощь). Каждый
коммит должен представлять одно цельное изменение, которое можно
понять и отревьюить независимо. Не смешивайте рефакторинг с новыми
фичами и не объединяйте несвязанные исправления багов: это запутывает
историю того, какое изменение какую проблему исправило, и почти
наверняка замедлит последующее ревью ваших изменений. А ещё такая
дисциплина даёт вам суперспособности через git bisect, но это
история для другого раза.
Одно замечание: начав внимательнее относиться к техническим текстам и писать их больше, не забывайте уважать читателя. Легко скатиться в избыточные объяснения, но этому порыву нужно сопротивляться – иначе читатель не прочтёт ничего из написанного вами. Объясните «почему» и доверьте ему самому разобраться с «как» применительно к его ситуации.
Совместная работа
Как инженеры, мы, возможно, проводим большую часть рабочего дня за написанием кода на собственной клавиатуре, но заметный кусок нашего времени уходит и на общение с другими людьми. Обычно это время делится на совместную работу и обучение, и вложения в то, чтобы стать лучше и в том, и в другом, окупаются сполна.
Вклад в проекты
Отправляете ли вы отчёт об ошибке (bug report), вносите простое исправление бага или реализуете огромную фичу – стоит помнить, что пользователей обычно на порядки больше, чем контрибьюторов, а контрибьюторов – на порядок больше, чем мейнтейнеров. В результате время мейнтейнеров – крайне дефицитный ресурс. Если вы хотите повысить шансы на то, что ваш вклад приведёт к чему-то полезному, позаботьтесь о том, чтобы он имел высокое отношение сигнала к шуму и стоил времени мейнтейнеров.
Например, хороший отчёт об ошибке уважает время мейнтейнера – в нём есть всё, что нужно, чтобы понять и воспроизвести проблему:
- Окружение: ОС, номера версий, релевантная конфигурация
- Что вы ожидали и что произошло на самом деле
- Шаги воспроизведения: будьте конкретны. «Нажмите кнопку» куда менее полезно, чем «Нажмите кнопку Submit на странице /settings, будучи залогиненным как администратор».
- Что вы уже попробовали: это избавляет от повторных советов и показывает, что вы уже провели какое-то расследование
Если вы нашли уязвимость, не публикуйте её открыто. Сначала свяжитесь с мейнтейнерами приватно и дайте им разумное время на исправление до раскрытия информации. Во многих проектах для этого есть файл SECURITY.md или что-то подобное.
Обязательно поищите существующие issue. О вашем баге или запросе фичи, возможно, уже сообщили, и куда лучше добавить информацию в существующее обсуждение, чем плодить дубликаты. Не говоря уже о том, что так меньше шума для мейнтейнеров.
Минимальные воспроизводимые примеры – на вес золота, если вам удаётся такой составить. Они экономят мейнтейнеру огромное количество времени и сил, а надёжно воспроизвести баг – часто самая сложная часть его исправления. Не говоря уже о том, что усилия, вложенные в изоляцию проблемы, часто помогают и вам лучше её понять, а иногда приводят к тому, что вы сами находите исправление.
Если вам не ответили сразу, помните, что мейнтейнеры – часто волонтёры с ограниченным временем. Если вы ждёте от них ответа, вежливое напоминание через пару недель – это нормально; ежедневные пинги – нет. Точно так же комментарии «у меня тоже» или отчёты об ошибках, которые представляют собой просто скопированный вывод терминала, обычно лишь вредят продвижению вашего issue.
Если вы собираетесь внести вклад в виде кода, вам также стоит
ознакомиться с правилами участия. Во многих проектах есть
CONTRIBUTING.md – следуйте ему. Кроме того, обычно стоит начинать с
малого: исправление опечатки или улучшение документации – отличный
первый вклад, ведь так вы знакомитесь с процессами проекта, не увязая
заодно в долгих обсуждениях содержания.
Проверьте, какую лицензию использует проект: любой код, который вы внесёте, попадёт под ту же лицензию. Особенно остерегайтесь copyleft-лицензий (вроде GPL): они требуют, чтобы производные работы тоже были open source, и могут иметь последствия для вашего работодателя, если вы с таким кодом соприкоснётесь! Больше полезной информации – на choosealicense.com.
Когда вы решили открыть пул-реквест («PR»), сначала убедитесь, что изолировали именно то изменение, которое хотите видеть принятым. Если ваш PR заодно меняет кучу других, не связанных с ним вещей, велика вероятность, что ревьюер вернёт его вам с просьбой навести порядок. Тут работает тот же принцип, что и при разбиении git-коммитов на семантически связанные части.
В некоторых случаях, когда у вас много на первый взгляд разрозненных изменений, но все они нужны для одной фичи, допустимо открыть PR покрупнее, охватывающий все эти изменения. Однако тогда особенно важна гигиена коммитов, чтобы у мейнтейнеров была возможность ревьюить изменение «коммит за коммитом».
Дальше позаботьтесь о том, чтобы хорошо объяснить «почему» вашего
изменения. Не просто описывайте, что поменялось – объясните, почему
изменение нужно и почему это хороший способ решить проблему. Также
стоит заранее указать на те части изменения, которые заслуживают
особого внимания на ревью, если такие есть. В зависимости от
CONTRIBUTING.md и характера вашего изменения ревьюеры могут также
ожидать дополнительной информации – например, на какие компромиссы вы
пошли или как протестировать изменение.
Мы рекомендуем вносить изменения в upstream-проект, а не «форкать» его – по крайней мере в качестве первого подхода. Форк (если лицензия позволяет) стоит приберечь для случаев, когда изменения, которые вы хотите внести, выходят за рамки исходного проекта. А если вы всё же делаете форк, обязательно укажите исходный проект!
ИИ позволяет невероятно быстро генерировать правдоподобно выглядящий код и PR, но это не освобождает вас от обязанности понимать, что вы вносите. Отправляя сгенерированный ИИ код, который вы не можете объяснить, вы взваливаете на мейнтейнеров ревью – а потенциально и поддержку – кода, который не понимает даже его автор. Использовать ИИ, чтобы находить проблемы и готовить исправления и фичи, – нормально, при условии, что вы всё же проделываете должную работу и доводите результат до вклада, который чего-то стоит, а не перекладываете этот труд на (и без того перегруженных) мейнтейнеров.
Помните, что для мейнтейнеров принять PR – значит принять долгосрочную ответственность. Они будут поддерживать этот код ещё долго после того, как контрибьютор двинется дальше, и потому могут отклонять изменения, которые сделаны из лучших побуждений, но не вписываются в направление проекта, добавляют сложность, которую они не хотят поддерживать, или попросту недостаточно хорошо обоснованы. Именно вам как контрибьютору предстоит убедительно объяснить, почему этот вклад стоит бремени его поддержки.
Получая отзывы на PR, помните: ваш код – это не вы! Ревьюеры стараются сделать код лучше, а не критикуют вас лично. Если вы не согласны – задавайте уточняющие вопросы: возможно, вы узнаете что-то новое, а может, что-то узнают они.
Ревью
Вам может казаться, что код-ревью – занятие для старших разработчиков, но, скорее всего, ревьюить код вас попросят куда раньше, чем вы ожидаете, и ваш взгляд ценен. Свежий взгляд замечает то, что опытные разработчики упускают, а вопросы человека, менее знакомого с кодом, часто вскрывают допущения, которые стоило бы задокументировать или упростить.
Ревью – это ещё и один из самых быстрых способов учиться. Вы увидите, как другие подходят к задачам, переймёте паттерны и идиомы и разовьёте интуицию в том, что делает код читаемым. Помимо личного роста, ревью ловит баги до того, как они доберутся до продакшена, распространяет знания по команде и повышает качество кода за счёт совместной работы. Это не просто бюрократические накладные расходы.
Хорошее код-ревью – навык, который оттачивается со временем, но есть несколько советов, которые быстро сделают ваши ревью заметно лучше:
- Ревьюйте код, а не человека: «Эта функция сбивает с толку» вместо «Вы написали запутанный код».
- Предпочитайте комментарии, по которым понятно, что делать: на «Можете заменить эти глобальные переменные на датакласс с конфигурацией» отреагировать проще, чем на «Не используйте здесь глобальные переменные»
- Задавайте вопросы, а не выдвигайте требования: «Что случится, если X здесь окажется null?» располагает к обсуждению лучше, чем «Обработайте случай с null».
- Объясняйте «почему»: «Подумайте о том, чтобы использовать здесь константу» менее полезно, чем «Подумайте о том, чтобы использовать здесь константу – так мы сможем легко подстраивать таймаут под окружение».
- Отделяйте блокирующие проблемы от предложений: ясно обозначайте, что изменить необходимо, а что – дело вкуса.
- Отмечайте то, что сделано хорошо: указать на остроумное решение или чистую реализацию – значит подбодрить автора и помочь ему понять, что стоит делать и дальше.
- Знайте, когда остановиться: время и терпение контрибьюторов не безграничны, и тратить их на разбор всех мелких придирок – не всегда лучший вариант. Сосредоточьтесь на главном, а мелочи, возможно, стоит потом подчистить самостоятельно.
ИИ-инструменты способны отлавливать определённые проблемы, но они не заменяют ревью человеком. Они упускают контекст, не понимают продуктовых требований и могут уверенно предлагать неверные решения. Их стоит использовать как первый проход, но не как замену вдумчивому человеческому ревью.
Обучение
Изрядная часть нашего инженерного времени, не связанного с написанием кода, уходит на вопросы: мы либо задаём их, либо отвечаем на них, а порой и то и другое сразу – при совместной работе, в диалоге с коллегами или когда пытаемся чему-то научиться. Умение задавать хорошие вопросы – это навык, который позволяет учиться у кого угодно, а не только у тех, кто идеально объясняет. У Джулии Эванс есть отличные посты «Как задавать хорошие вопросы» и «Как получать полезные ответы на свои вопросы», которые стоит прочитать.
Вот несколько особенно ценных советов:
- Сначала изложите своё понимание: скажите, что, как вам кажется, вы знаете, и спросите: «Всё верно?» Так отвечающему проще выявить ваши реальные пробелы в знаниях.
- Задавайте вопросы «да/нет»: вопрос «Верно ли X?» не даёт объяснению уйти в сторону и обычно всё равно вызывает полезные уточнения.
- Будьте конкретны: «Как работают JOIN в SQL?» – слишком расплывчато. «Включает ли LEFT JOIN строки, для которых в правой таблице нет совпадений?» – на такой вопрос можно ответить.
- Признавайтесь, когда чего-то не понимаете: перебивайте, чтобы спросить о незнакомых терминах. Это признак уверенности, а не слабости. Точно так же, если вам задают вопрос, ответа на который вы не знаете, лучше всего сказать «я не знаю» – и, возможно, добавить «но я думаю…» или даже «но я могу выяснить».
- Не соглашайтесь на неполные ответы: продолжайте задавать уточняющие вопросы, пока действительно не поймёте.
- Сначала немного разберитесь сами: базовое предварительное изучение помогает задавать более прицельные вопросы (хотя в непринуждённых вопросах между коллегами нет ничего плохого).
Помните: хорошо сформулированные вопросы приносят пользу целым сообществам. Они вскрывают скрытые допущения, которые нужно понять и другим.
Заметьте, что все эти советы в той же мере применимы и к общению с LLM!
ИИ-этикет
LLM и ИИ используются в разработке ПО всё шире, а социальные и профессиональные нормы вокруг них всё ещё не устоялись. Многие тактические аспекты мы уже разобрали в лекции об агентном программировании, но есть и более «мягкие» стороны их использования, которые стоит обсудить.
Первая из них: если ИИ внёс существенный вклад в вашу работу, сообщайте об этом. Дело не в стыде – дело в честности, в правильно выставленных ожиданиях и в том, чтобы результат прошёл ревью соответствующего уровня. Стоит также раскрывать, для каких именно частей работы вы использовали ИИ – есть существенная разница между «я это всё навайбкодил» и «этот инструмент для бэкапов я написал сам, а LLM использовал, чтобы оформить веб-интерфейс». Например, мы использовали LLM при подготовке некоторых из этих конспектов лекций – в том числе для вычитки, брейнсторминга и генерации первых черновиков фрагментов кода и упражнений.
Кроме того, здесь стоит следовать нормам команд и проектов, в которые вы вносите вклад. В одних командах политика использования ИИ строже, чем в других – например, из соображений комплаенса или резидентности данных (data residency), – и случайно нарушить её вам совсем ни к чему. Открытость в этом вопросе помогает предотвратить потенциально дорогостоящие ошибки.
Если работа, которую вы делаете, для вас ещё и способ чему-то научиться, имейте в виду: если всю работу или большую её часть за вас делает ИИ, это может свести цель на нет – скорее всего, вы узнаете больше о промптинге (и, может быть, о ревью вывода ИИ), чем о самой задаче. Особенно во время обучения смысл может быть в пути, а не в пункте назначения, так что использовать ИИ, чтобы «быстро получить решение», – это антицель.
Похожий вопрос встаёт на собеседованиях и в других ситуациях, где вас оценивают. Часто их цель – проверить именно ваши навыки и способности, а не навыки LLM. Всё больше компаний теперь разрешают пользоваться LLM и другими ИИ-инструментами на собеседованиях – при условии, что вы позволите наблюдать за этими взаимодействиями как за частью собеседования (то есть они заодно оценивают и ваше умение пользоваться такими инструментами!), – но такие компании пока в меньшинстве. Если вы не уверены, допустима ли помощь ИИ в конкретной задаче, – спросите!
Само собой разумеется: если условия оценивания явно запрещают внешние инструменты, LLM и тому подобное, пользоваться ими не следует. Попытка втихую сделать это и не попасться обязательно выйдет вам боком.
Упражнения
-
Полистайте исходный код какого-нибудь известного проекта (например, Redis или curl). Найдите примеры некоторых типов комментариев, упомянутых в лекции: полезный TODO, ссылку на внешнюю документацию, комментарий «почему не», объясняющий подход, от которого отказались, или выстраданный урок. Что было бы потеряно, не будь этого комментария?
-
Выберите интересный вам open-source-проект и посмотрите его недавнюю историю коммитов (
git log). Найдите один коммит с хорошим сообщением, объясняющим, почему было сделано изменение, и один со слабым сообщением, которое лишь описывает, что изменилось. Для слабого посмотрите на diff (git show <hash>) и попробуйте написать сообщение коммита получше, следуя структуре «Проблема → Решение → Последствия». Обратите внимание, сколько труда уходит на то, чтобы задним числом восстановить необходимый контекст! -
Сравните README трёх проектов на GitHub с 1000+ звёзд. Все ли они одинаково полезны? Поищите то, что кажется вам по большей части шумом, – это урок для будущих README, которые вы напишете сами.
-
Найдите открытое issue в проекте, которым пользуетесь (загляните в метки «good first issue» или «help wanted», если они там есть). Оцените это issue по критериям из лекции: похоже ли, что оно ценит время мейнтейнера и содержит всю информацию, необходимую для отладки, или же вы ожидаете, что мейнтейнеру придётся пройти с автором несколько раундов вопросов, чтобы добраться до корня проблемы?
-
Вспомните баг, на который вы натыкались в софте, которым пользуетесь (или найдите такой в каком-нибудь issue-трекере). Потренируйтесь составлять минимальный воспроизводимый пример: убирайте всё, что не относится к багу, пока не останется самый маленький случай, который всё ещё демонстрирует проблему. Опишите, что вы убрали и почему.
-
Найдите в знакомом вам проекте влитый (merged) пул-реквест с содержательными комментариями ревью (не просто «LGTM»). Прочитайте ревью целиком. Все ли комментарии были одинаково продуктивны? Будь вы автором PR, каково было бы вам получить все эти комментарии?
-
Зайдите на Stack Overflow и найдите вопрос по знакомой вам технологии с высоко оценённым ответом. Затем найдите вопрос, который закрыли или сильно заминусовали. Сравните их с советами из лекции; было ли предсказуемо, какой из вопросов получит ответы получше?
Лицензия CC BY-NC-SA.