У меня, наконец, появилось ощущение, что я приличный программист, поэтому я подумал, что было бы интересно написать несколько советов на тему «Что помогло бы мне добиться этого быстрее?» Не утверждаю, что это отличные советы для всех, просто это были бы хорошие советы для меня.

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

Не могу сказать, сколько раз был в командах, где в системе есть что-то такое, что очень легко поломать, но никто не думает, как затруднить само повторение ошибки.

Я занимался iOS-разработкой, работал с CoreData и подписывался на изменения множества представлений. Обратный вызов подписки происходил в том же потоке, где изменение инициировалось. Иногда это был основной поток, а иногда фоновый. Важно то, что при разработке под iOS обновлять UI можно только в основном потоке, иначе приложение упадёт. Таким образом, новая подписка может работать нормально, но сломается, если кто-то начнёт изменение из фонового потока или если позже вы добавите обновление UI.

Люди смотрели на это сквозь пальцы и часто упоминали проблему в ревью для новых членов команды. Иногда кто-то ошибался, и мы добавляли DispatchQueue.main.async, когда видели отчёт о сбое.

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

Я не хочу сказать: «Посмотрите на этих неразумных, они не решают проблему с кодовой базой, хотя для меня она была очевидна». Проблема была очевидна любому, кто задумался о ней хотя бы на несколько минут. Подобные штуки сохраняются в удивительном количестве, потому что на них никогда нет времени. Попадая в команду, вы не пытаетесь изменить что-нибудь серьёзное. Можно подумать, что это странно, но вы не должны менять ту кучу вещей, которую вы ещё изучаете. А позже это как будто отходит на второй план, восприятие сдвигается.

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

Оцените компромисс между качеством и скоростью, убедитесь, что он подходит вашему контексту

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

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

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

Обычно вы не во второй компании. Но я видел, как многие разработчики ошибались в программировании такого рода. Когда ошибки не являются критически важными (например, в 99% веб-приложений), вы добьётесь большего, если допустите и быстро исправите ошибки, чем если потратите время, чтобы убедиться, что сразу пишете безупречные фичи.

Потратьте время на освоение инструмента, почти всегда оно того стоит

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

Уже могу сказать, что люди будут писать в комментариях: «Нельзя просто потратить весь день на настройку конфигурации neovim, иногда нужно просто работать». Не думаю, что когда-нибудь видел, чтобы кто-то действительно переусердствовал. Один из главных хороших признаков, что я видел у новых инженеров, — тщательный выбор инструментов и освоение навыков работы с ними.

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

Мой любимый за всю карьеру менеджер имел привычку давить на меня, когда я утверждал, что что-то сложно реализовать. Часто его ответ был примерно «Разве это не просто вопрос отправки X, когда мы Y» или «Разве это не то же самое, что Z, который мы сделали пару месяцев назад»? Я хочу сказать, что это возражения на высоком уровне абстракции, а не на уровне реальных функций и классов, с которыми мы имели дело, о которых я говорил.

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

Попробуйте разбираться с багами на слой глубже

Представьте, что у вас на информационной панели есть компонент React, работающий с объектом User. User взят из состояния текущего пользователя, который вошёл в систему. Вы видите отчёт об ошибке в Sentry, где во время рендеринга user устанавливается в null. Можно быстро добавить if (!user) return null. Или изучить проблему и обнаружить, что функция выхода из системы выполняет два отдельных обновления состояния: первое устанавливает для пользователя значение null, второе перенаправляет на домашнюю страницу. Поменяйте их местами, и теперь ни один компонент больше никогда не будет иметь эту ошибку, потому что пользовательский объект больше никогда не null, пока вы в панели.

Продолжайте исправлять ошибки первого типа — и в конечном счёте вы получите бардак. Продолжайте исправлять ошибки второго типа — и у вас будет чистая система и глубокое понимание инвариантов.

Чтобы разобраться с багами, иногда погружайтесь в иcторию коммитов

Мне всегда хорошо удавалось отлаживать странные ошибки с отладчиком и обычным набором инструментов println. Поэтому я никогда особо не изучал git, чтобы разобраться с историей какого-нибудь бага. Но для некоторых багов это критично.

Недавно у меня возникла проблема с сервером, из-за которой, видимо, постоянно происходила утечка памяти, а затем OOM убивал и перезапускал. Я не мог понять причину. Все вероятные виновники были исключены, я не мог воспроизвести баг локально, как будто метал дротики с завязанными глазами.

Я просмотрел историю коммитов и обнаружил, что это стало происходить после того, как я добавил поддержку платежей в Play Store. Никогда бы не посмотрел такое место и за миллион лет, ведь это всего лишь пара HTTP-запросов. Оказывается, запрос застрял в бесконечном цикле получения токенов доступа после истечения срока действия первого токена. Возможно, каждый запрос добавлял в память всего около одного килобайта, но повторы попытки каждые 10 мс в несколько потоков быстро умножали это число. Обычно такие штуки приводили к переполнению стека, но я использовал асинхронную рекурсию Rust, которая не переполняет стек. Это не пришло бы мне в голову никогда. Но при изучении конкретного фрагмента кода, о котором я знал, что он должен был вызвать переполнение, внезапно всплыла теория.

Не уверен, какое здесь работает правило, то есть когда следует это делать, а когда нет. Это чутьё, что-то вроде «Ага!», если сравнить с триггером отчёта об ошибке, который запускает расследование. Чутьё со временем обостряется, достаточно знать, что иногда, если вы застряли, оно бесценно.

Аналогично, если проблема поддаётся решению, попробуйте git bisect — это даст историю небольших коммитов, быструю автоматическую проверку на проблему, если у вас есть один плохой коммит и один хороший.

Плохой код даёт обратную связь, идеальный — нет

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

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

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

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

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

Наши модели серверов точно такие, как DTO, которые мы писали, поэтому я просто сериализовал их, а не прописывал весь шаблон; написать DTO можно позже, по необходимости

Я не писал тесты этих компонентов, потому что они тривиальны и ошибка в каком-то одном погоды не сделает.

Упростите отладку

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

Вот несколько примеров того, что я имею в виду.

  • На бекенде Chessbook у меня есть:
  • команда копирования всех данных пользователя на локальный ПК, поэтому я могу легко воспроизводить проблемы при помощи только имени пользователя;
  • я отслеживаю каждый локальный запрос через OpenTelemetry, это позволяет легко увидеть, на что запрос тратит своё время;
  • есть файл, работающий как псевдо-REPL. Он выполняется при каждом изменении кода и позволяет легко извлечь фрагменты, поиграть с ними, чтобы лучше понять, что происходит;
  • в промежуточной среде я ограничиваю параллелизм до одного потока, чтобы упростить визуальный анализ логов.
  • На фронтенде:
  • параметр debugRequests предотвращает оптимистичную загрузку данных, чтобы упростить отладку запросов;
  • параметр debugState выводит все состояние программы после каждого обновления вместе с изменившейся частью;
  • файл, полный небольших функций, которые переводят UI в определённые состояния. Поэтому, когда я пытаюсь исправить ошибки, мне не нужно постоянно кликать по UI, чтобы перейти в это состояние.

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

При работе в команде обычно следует спрашивать

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

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

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

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

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

  • Система, которая не подвержена ошибкам.
  • Быстрый обмен между командами.
  • Готовность вырезать 10% новой фичи, которые займут 50% времени разработки, и предусмотрительность, чтобы понимать, что есть что.
  • Согласованные шаблоны повторного использования, которые можно составлять для новых экранов/фич/конечных точек.
  • Быстрое и простое развёртывание.
  • Процесс, который не замедляет вас; нестабильные тесты, медленная CI, суетливые линтеры, медленные обзоры PR, JIRA как религия и т. д.
  • Ещё около миллиона других вещей.

Медленная доставка должна заслуживать вскрытия так же, как падение продакшна. В нашей отрасли дела обстоят иначе, но это не значит, что вы лично не можете следовать за северной звездой Shipping Fast. Спасибо, что прочитали!

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Marcus Buffett: A Bunch of Programming Advice I’d Give To Myself 15 Years Ago

Предыдущая статьяТекстовой эмбеддинг: классификация и семантический поиск
Следующая статьяОптимизация кода задачи на миллиард строк — ускоряем запуск в 87 раз