Выпуск 7 · подкаст «Тысяча фичей»

#7: Трассирующий код: прототипы, оценки

37:38
↓ скачать mp3

Продолжение второй главы «Программиста-прагматика»: чем трассирующий код отличается от прототипа и почему первый — это продакшн-скелет с урезанной функциональностью, а второй всегда выкидывается. Затем — предметно-ориентированные языки (DSL): внутренние и внешние, и почему тесты — идеальное место для первого собственного DSL. И большая тема оценивания: как точность оценки задаёт ожидания, техника PERT с тремя сценариями, модель с критическими параметрами и лучший ответ на просьбу об оценке — «я к тебе вернусь». Полезняшка — игра Vim Adventures.

Главное

  • Трассирующий код — не прототип: это полноценный продакшн-скелет (база, CI/CD, интеграции) с урезанной функциональностью, который быстро доставляет пользователю работающий end-to-end сценарий.
  • Преимущества трассирующего кода: ранняя обратная связь, продуктивные разработчики (цикл доставки уже настроен) и всегда есть что показать заказчику.
  • Промах трассирующего выстрела — нормально: скелет и пайплайн остаются, следующая итерация корректирует направление.
  • Прототип, наоборот, всегда выкидывается: прототипируйте, чтобы учиться, а не чтобы кого-то убедить; валидацию, полноту и устойчивость к ошибкам можно смело игнорировать.
  • Главная ошибка прототипирования — позволить воспринять прототип как почти готовый продукт; если донести это не получается, пишите трассирующий код.
  • DSL приближает код к предметной области: внутренние DSL (Spock, RSpec) ограничены языком-хостом, внешние (Cucumber) — только своим парсером; самое безопасное место для первого DSL — тестовая инфраструктура.
  • Точность оценки задаёт ожидания: «130 дней» провоцирует планирование день в день — скажите «около полугода»; до 15 дней оценивайте в днях, до 6 недель — в неделях, до 20 недель — в месяцах, дальше — подумайте дважды.
  • Оценивайте через модель (компоненты → критические параметры → сценарии) и PERT (оптимистичная/вероятная/пессимистичная оценки со сценариями); итерируйте оценки вместе с кодом, а лучший ответ на просьбу об оценке — «я к тебе вернусь».
Расшифровка

[00:20] Здарова! Меня зовут Саша Пахомов, и я инженер, который любит своё дело. Это седьмой выпуск подкаста «Тысяча фичей». В этом выпуске мы поговорим про прототипы, трассирующий код, предметно-ориентированные языки и оценку проектов. Все темы взяты из второй главы книги «Программист-прагматик», которую я сейчас читаю. В конце — полезняшка для прокачки определённого скилла. Поехали!

[01:02] Напомню: в основной части подкаста я читаю второе издание «Программиста-прагматика» и рефлексирую над темами. Сегодня начну с темы «Стрельба трассирующими» — одной из моих любимых идей книги, которую я полюбил ещё с первого издания. Смысл такой: мы, разработчики, действуем в условиях огромной неопределённости, и её можно представить аналогией — движущаяся цель в темноте. Цель — наши пользователи и клиенты, которым нужен продукт. Она движется: их понимание, требования и сама ситуация меняются, и мы не знаем, попадём ли. Продолжая аналогию со стрельбой: цель движется, темно, а настоящие патроны дорогие — нет смысла стрелять ими первые выстрелы вслепую. Стрелок стреляет трассирующими — пулями, которые оставляют светящийся след. Он стреляет и видит, где цель и куда она движется; со второго выстрела делает поправку, с третьего — попадает. И только потом, зная траекторию, стреляет настоящими.

[02:44] В разработке можно применить ту же технологию: не выпускать сразу «готовую» программу, которая может не попасть в ожидания пользователя — он скажет «это не то, что я хотел», а мы потратили месяц. Вместо этого пишем трассирующий код, который максимально быстро что-то поставляет пользователю — и мы максимально быстро получаем обратную связь: то или не то, куда двигаться. Очень важное замечание: трассирующий код не должен быть некачественным. Это не прототип. Всё, что мы урезаем, — функциональность. Трассирующий код — это нормальная база данных, нормальный бэкенд, CI/CD, интеграции, сайт: всё, что мы разрабатываем, пойдёт в продакшн. Жертвуем только функциональностью: сайт разработан, но, например, ещё не работает авторизация — а пользователь уже может зайти и выполнить базовый бизнес-кейс. Трассирующим этот код называется потому, что потом используется как основа, как скелет для наращивания функциональности. И интересный момент: если архитектурный майндсет у нас нормальный, код будет написан так, что добавлять фичи будет легко и даже приятно, — а трассирующий выстрел, связавший все системы между собой, останется скелетоном.

[04:25] К преимуществам трассирующего кода авторы относят, во-первых, быструю обратную связь: чем быстрее доставим рабочую версию до пользователя — пусть без части фич, — тем раньше скорректируем требования, не реализовав не те. Во-вторых, разработчики работают продуктивнее: когда есть скелетон с настроенными CI/CD, ты поменял, скажем, алерт-месседж, замёржил в основную ветку — и через полчаса оно на тестовом стенде. Цикл разработки настроен, и люди не тратят время и ментальные усилия на борьбу с багами в CI/CD — они просто деливерят фичи. В-третьих, всегда есть что показать: приходит заказчик, спрашивает «какой прогресс?» — и вы всегда показываете пусть урезанную, но работающую функциональность, а не говорите «у нас сейчас оптимизация, код не компилируется». Такой ситуации почти не бывает — и это очень полезное преимущество. И последнее — у вас всегда есть прогресс. При этом написание трассирующего кода — это не техника типа TDD с определённым пайплайном действий: это скорее философский взгляд на разработку.

[06:19] Тем не менее трассирующий выстрел не всегда попадает в цель. Развернули базу, бэкенд, фронтенд с формочкой, отправили пользователю — а он говорит: «Почему вы спрашиваете паспортные данные? Я не хочу ими делиться, приложение должно быть анонимным». Очень большое изменение требований, не так ли? Пользователь понял это, начав пользоваться, — или знал заранее, а мы недоработали требования. Поставив быстрый результат, мы сразу понимаем: система должна быть максимально анонимной — и двигаемся в другом направлении. Но часть скелета — траектория трассирующей пули — остаётся: пайплайн, база данных; может, чуть поменяются форма и модель данных. Мы просто делаем следующую итерацию — следующий трассирующий выстрел. Такими итерациями, говорят авторы, разрабатывать эффективнее всего. Если отойти от книги: порефлексировав, я понял, что, в принципе, всегда так и разрабатываю — может, даже не для пользователя, а для себя. Мне важно убедиться, что минимальный end-to-end сценарий работает. И только когда эта сквозная интеграция всего процесса налажена, я обретаю спокойствие и начинаю накидывать фичи, тесты, рефакторить — процесс реальной разработки. До этого могу и забить на тесты: да, я люблю TDD, но, как уже говорил, оно применимо не всегда — если я делаю новую систему с нуля и пользователь сам толком не знает, чего хочет, писать в TDD я навряд ли буду, это бред. Трассирующий код в таких случаях подходит.

[08:34] И важный хайлайт, который авторы делают несколько раз: трассирующий код — ни в коем случае не прототип. Чтобы понять, что такое прототип с их точки зрения, они разбирают тему глубже. Прототип — это часто вообще не код: документ, ноукод-решение, рисунок на салфетке, проект в Figma, скрипт на питоне или на баше — что угодно. Что можно прототипировать? Тут открытий нет: архитектуру, новую функциональность, внутренние структуры данных, возможность использования сторонних библиотек, производительность, пользовательский интерфейс. В чём цель прототипирования? Главная цель — научиться на ошибках малой кровью, не производя продакшн-код. Авторы прямо делают акцент: прототип — ни в коем случае не продакшн-код, это его основной принцип. Прототип обязательно выкидывается — всегда. Два исхода: либо берём и переписываем нормально, либо не переписываем — и учимся на ошибках, которые сделали в прототипе. Совет части: прототипируйте, чтобы учиться. Подчеркну слово «учиться»: мы прототипируем, чтобы что-то узнать, продвинуться в понимании, — а не чтобы кому-то что-то доказать или кого-то убедить. Прототип должен давать обратную связь — от заказчика, пользователей, реальности, команды, — и над этим фидбэком мы рефлексируем и учимся.

[11:01] Дальше практичные советы: что в прототипе можно со спокойной душой игнорировать. Корректность вводимых пользователем данных — валидация не нужна. Функциональную завершённость: рассматриваем хэппи-пасс, который работает только при полной луне, — это окей; не нужно писать код, который работает во всех условиях, включая Марс. И устойчивость к ошибкам: пользователь ввёл абракадабру, и мы упали сегфолтом — нормально, это прототип. Какие языки подходят? Высокоуровневые, с большой стандартной библиотекой, на которых легко писать; авторы делают акцент на Python и Ruby — динамические языки, которые позволяют очень много.

[12:32] Отдельно авторы рассматривают прототипирование архитектуры — диаграммой или рисунком на бумажке — и предлагают задавать себе вопросы во время и после. Достаточно ли чётко определены обязанности основных компонентов? Хорошо ли описано их взаимодействие? Минимизировано ли количество связей? Есть ли потенциальное дублирование? И — есть ли у всех компонентов доступ к необходимым данным в тот момент, когда они нужны? На последнем остановлюсь: по-моему, это одна из самых полезных вещей в прототипе архитектуры. Компонентам нужны данные в определённый момент — а их может просто не быть: не потому, что лок на таблице, а потому, что система их ещё не произвела. У нас нет логов транзакций пользователя, потому что он ещё не сделал ни одной транзакции. Это краевой случай, который архитектура должна обработать, — и все такие случаи доступности данных мы видим на прототипе и прорабатываем. Тем самым генерируем более точные и адекватные требования к будущей системе. Если так получается — это прямо очень круто.

[14:13] Дальше авторы говорят про большую ошибку: прототип путают с рабочим кодом. Написали на питоне скрипт процессинга данных, на двух пользователях работает. Самое главное — чтобы этот прототип никем не воспринимался как то, чем можно пользоваться. У аналитиков, продуктов, пользователей не должно возникнуть ни малейшей мысли «сейчас немножко подтюним — и вот результат: за две недели сделали, а думали два месяца». Нет. Наша задача — донести максимально ясно: это прототип, и ни при каких условиях он не пойдёт в продакшн ни в каком виде; это вещь, на которую мы посмотрим, поймём направление — будем учиться. И если донести эту идею не получается — возможно, вам стоит посмотреть в сторону трассирующего кода: написать действительно рабочий код с урезанной функциональностью. В этом и принципиальное отличие: прототип мы выкинем — он даёт только знания; трассирующий код даёт направление и скелетон для дальнейшей разработки. Выбирайте по ситуации — а если у вас свой подход к таким разработкам, поделитесь, буду признателен.

[16:13] Следующая тема второй главы — предметно-ориентированные языки, DSL, domain-specific languages. Прежде чем начать — личная история: на четвёртом курсе бакалавриата я писал диплом как раз про написание DSL на Groovy и его подсветку в IDE. Про DSL я могу разговаривать долго — я тогда увлёкся темой глубоко, и как раз читал первое издание «Программиста-прагматика», где она тоже была. Повторение — мать учения. Своими словами, чтобы синхронизироваться: DSL — это библиотека, набор функций, специальные языковые конструкции, макросы — любые инструменты языка, которые используются, чтобы код выражался в терминах предметной области. Если наша область — обработка транзакций пользователя, код будет выглядеть скорее как спецификация, которую может прочитать даже человек из бизнеса: не вайлы, ифы и свитчи, а выразительные конструкции на предметном языке. Из популярного у нас: Gradle в каком-то смысле написан на DSL — он оперирует понятиями, которые сам ввёл: таски, dependency, артефакты, конфигурации.

[18:07] Что пишут авторы? Совет: программируйте ближе к предметной области. Неявно они говорят: ребят, если есть возможность — пишите DSL. Примеры приводят канонические: RSpec — фреймворк тестирования на Ruby (выглядит очень похоже на тесты на Kotlin или Scala), Cucumber — язык для end-to-end-тестирования со своими спецификациями, Phoenix из мира Elixir и Ansible — его плейбуки написаны на DSL, реализация которого — YAML. Все DSL делятся на две группы. Первая — внутренние, встроенные: по сути, набор выразительных библиотек внутри языка. RSpec — или, из моего опыта, Spock, фреймворк тестирования на Groovy: встроенный DSL, который использует все возможности Groovy и надстраивает над ними плюшки, удобные при тестировании. Вторая — внешние DSL, написанные со своими парсерами: Cucumber — отдельный обособленный язык со своей спецификацией и парсером (Ansible использует готовый YAML-парсер). Внешний язык может быть не Тьюринг-полным, с ограничениями — но это отдельный язык. Основное отличие: внутренние DSL могут использовать плюшки языка-хоста — но им же и ограничены; даже макросы — это всё-таки синтаксис, всё что угодно не напишешь, а если пытаться — потратишь усилия, сравнимые с написанием собственного парсера. Внешние же ничем не ограничены: вы полностью творец — но не получаете из коробки языковых конструкций хоста.

[21:06] Завершается часть так: внутренние DSL — это, как правило, просто набор функций, методов, объектов, каких-то экстеншенов — достаточно простые вещи, которые мы и так используем в ежедневной разработке. Но можно посмотреть на них с другой стороны: вот наш класс helper или utils — можно ли его переписать или обернуть так, чтобы из наших функций образовался DSL, которым легко пользоваться и который элегантнее описывает происходящее? Код станет читаемее и понятнее для разработчиков. Конечно, с этим можно перегнуть — везде упарываться и вводить DSL не надо, это моё личное мнение. Но идеальное место, чтобы безопасно попробовать написать свой первый DSL, — тесты. В тестах мы можем определить сетап и tear-down инфраструктуры, ассерты, тест-степы — как набор говорящих, в терминах предметной области, функций и объектов. Для тестов DSL — то, что доктор прописал: даже если немножко упоретесь, максимум навредите тестовой инфраструктуре, но не основному коду. DSL в тестах — лайк.

[22:51] И последняя тема на сегодня и во второй главе — оценивание. Любое: проектов, времени работы, времени отклика, чего угодно. Раздел почти сразу начинается с совета: оценивайте, чтобы избежать сюрпризов. Если деятельность оценена, вероятность сюрпризов ниже — хотя, как ни крути, не нулевая. При любой оценке авторы предлагают прежде всего спросить себя: а зачем нас спрашивают об оценке, с какой целью интересуются? Успокоить внутреннее желание иллюзорной определённости — одна мотивация; согласование сроков с маркетинговым отделом — совершенно другая, и подходы к оценке нужны другие. Отсюда следующая мысль: точность оценки задаёт ожидания у того, кто спрашивает. Авторы приводят интересный пример: один и тот же срок, произнесённый с разной точностью, даёт разные ожидания. Нужно сделать сайт продажи авиабилетов с базовым поиском — как оцените? Инженер уходит, возвращается: «за 130 дней сделаем». Чем плоха оценка «130 дней»? Она использует несоразмерно точную меру — дни, в количестве 130. Это сигнализирует: оценка точна, можно закладываться ровно на 130 дней — и тот, кто спрашивал, договаривается с маркетингом запускать кампанию через 130 дней. А через 120 выясняется, что нужно ещё 30, — а кампания уже запущена. Чтобы такого не было, не используйте столь точные меры: скажите «около шести месяцев» — тогда никто не запланирует запуск ровно через шесть месяцев день в день, будут думать «пять-семь месяцев, где-то так» и планировать свои сроки относительно того, как вы предоставили информацию. И это не спекуляция — авторы дают табличку, какие цифры как произносить: от 1 до 15 дней — в днях, от 2 до 6 недель — в неделях, от 8 до 20 недель — в месяцах, а больше 20 недель — подумайте дважды, прежде чем вообще оценивать такие сроки. Неплохой лайфхак.

[26:25] А как прийти к тем самым «полугода»? Во-первых, учитывайте опыт других: если кто-то в отделе, в компании, из бывших коллег — или просто из интернета известно, что люди разрабатывали похожую систему, — пообщайтесь с ними, посмотрите на релиз-ноуты их продукта. Во-вторых, учитывайте условия, при которых ожидаются результаты. Просят оценить время отклика поиска на сайте: пользователь ввёл запрос, нажал search — сколько? Факторов очень много, и в разных условиях время разное. В идеальных — пользователь в том же регионе, что и сервера, или результат закэширован — вернём за 5 миллисекунд. А если пользователь из Индонезии (где я сейчас нахожусь) делает запрос на сервера в Европе — только сетевой путь даст 200–300 миллисекунд. Это не те 5 миллисекунд. Обе цифры — оценки, но смысл в том, что в разных ситуациях она меняется, и важно называть оба случая, а не «в среднем за 100 миллисекунд».

[28:59] Дальше — небольшой фреймворк оценки сложных вещей. Постройте модель: что угодно — квадратики на листочке, более сложная декомпозиция, как вам удобно думать. Разбейте её на компоненты — обособленные блоки системы. Опишите параметры, влияющие на каждый компонент: для хранилища это, как минимум, SSD или крутящийся жёсткий диск. И найдите критические параметры — те, что влияют на оценку сильнее всего. Если оцениваем скорость записи в базу, тип хранилища (SSD против HDD) влияет кардинально, а размер записи — количество байтов — тоже влияет, но не так сильно: значит, в важное выносим тип хранилища. Затем сложите варианты при разных параметрах и предоставьте несколько результатов. И — записывайте свои оценки и сравнивайте их потом с реальными цифрами. Не чтобы расстроиться или доказать себе, какой вы профессионал, а чтобы учиться: совпало — закрепили верную технику; разошлось — поняли почему, отрефлексировали и не повторяем ошибку.

[31:36] Отдельный акцент — на оценивании проектов, самой оцениваемой вещи в разработке. И тут тоже: оценивайте не одним числом, а несколькими. Банальный, но наглядный пример из книги — вольно перескажу. Сколько займёт нарисовать дом? Если всё идёт хорошо — несколько часов, до десяти; но это маловероятно. Реалистичнее — 18 часов. А если погода испортится — 30 и больше. Три оценки. Те, кто имеет опыт оценивания проектов, уже поняли, к чему я: аббревиатура PERT — Program Evaluation Review Technique (для меня, кстати, она была новой). Даём три оценки: оптимистичную, наиболее вероятную и пессимистичную. И не просто называем три числа — описываем сценарии, в которых каждое применимо: как с домом — «плохая погода» (нельзя рисовать в дождь). Так мы учитываем риски и даём стороне, ожидающей оценку, три реальных числа.

[33:26] Почему так, а не «самая вероятная оценка плюс разброс сверху»? У Бобука в «Радио-Т» была байка про оценку проектов: берёте количество разработчиков в команде, умножаете на число π и прибавляете две недели. Откуда две недели? «Две недели — это срок, за который я, Бобук, напишу вам любой проект». Шутка, но смысл в том, что все эти «умножить на что-то и прибавить две недели» — по мнению авторов, огромное заблуждение: оно происходит от незнания и непонимания того, что мы делаем. Если мы знаем и понимаем — мы описываем сценарии: есть модель, есть параметры, и мы описываем их вариации — вот оптимистичный набор, вот самый вероятный, вот пессимистичный. Мы понимаем, что делаем, а не берём число из головы и умножаем на π.

[34:32] Завершается глава альтернативным подходом — даже не к оцениванию, а к разработке. «Слона нужно есть по частям»: слон — это проект, и съесть его сразу невозможно. Части — это итерации. Мы оцениваем, как и пишем код, итеративно: первая версия выйдет через месяц с такой-то урезанной функциональностью; а когда выйдет вторая — скажем, когда выпустим первую, потому что знания, полученные на первой версии, безусловно повлияют на оценку второй. Не оцениваем всё сразу — оцениваем по частям. Совет: итерируйте временные оценки вместе с кодом. И заканчивается глава так: лучший ответ на просьбу об оценке чего-либо — «я к тебе вернусь». Подумать над оценкой всегда лучше, чем не подумать, и оценки, данные не сразу, как правило, точнее и доставляют меньше проблем.

[36:11] На этом основная часть подошла к концу. Сегодня я решил отказаться от традиционной второй части — кстати, напишите в комментариях Telegram-канала «Тысячи фичей», как вам такой формат: оставить мои размышления в стороне или, наоборот, уделить им больше внимания. А теперь обещанная полезняшка — онлайн-игра Vim Adventures. Смысл в том, чтобы проходить уровень за уровнем с помощью навигации Vim. Она платная, но первые три уровня бесплатные: я попробовал, и мне понравилось — обязательно куплю лицензию и пройду на новогодних праздниках. Сейчас подумал, что это звучит как реклама, — но это не она. Не забывайте оставлять отзывы и делиться подкастом с другими — давайте прокачивать себя и людей вокруг. Ну а на этом всё. Услышимся!