Экстремальные практики программирования: что, как и зачем

8efa112cee4b0397d372222a94b18b60.png

Всем привет! Меня зовут Сергей Бережной, я работаю в Яндексе уже 18 лет: в 2005 году пришёл разработчиком интерфейсов, потом руководил подразделениями разного размера, а сейчас я директор по взаимодействию с разработчиками. Ещё я много занимаюсь обучением: в рамках наших сезонных школ (особенно Школы разработки интерфейсов) и в Практикуме (где я работал над курсом «Управление командой разработки»).

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

Сразу оговорюсь, что со времён появления этих практик степень их «экстремальности» и инноваторства сильно уменьшилась. Некоторые из них стали стандартом де-факто в индустрии — их используют широко и не выделяют в отдельную категорию. Например, рефакторинг и непрерывная интеграция когда-то считались экстремальными, а сейчас ими никого не удивишь.

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

Об экстремальном программировании можно почитать в оригинальной книге «Extreme Programming Explained». Важно понимать, что книга объясняет именно общую идею, а не предлагает универсальные решения. Практики модифицируются для каждого конкретного случая. Нет ни одного человека, который бы использовал их как есть, без модификаций. Дело в том, что нечего использовать «как есть». 

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

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

Давайте перейдём к сути. В рамках статьи я рассмотрю три группы практик:

  1. Короткий цикл обратной связи
    — Разработка через тестирование
    — Короткие интервалы планирования
    — «Заказчик всегда рядом»
    — Парное программирование

  2. Непрерывный, а не пакетный процесс
    — Непрерывная интеграция
    — Рефакторинг
    — Небольшие частые релизы

  3. Понимание, разделяемое всеми
    — Простота проектирования
    — Метафора системы
    — Коллективное владение кодом
    — Стандарт оформления кода

1. Короткий цикл обратной связи

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

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

Разработка через тестирование

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

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

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

Встраивание процесса тестирования в процесс разработки помогает быстро итерироваться и сохранять качество. 

«Игра в планирование»

Моё вольное прочтение: циклы планирования должны быть легковесными и короткими в противовес ватерфольным, долгосрочным циклам. 

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

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

«Заказчик всегда рядом»

Классическая модель работы выглядит так: заказчик ставит задачу, бизнес-аналитики собирают требования, исполнители реализуют проект и приносят результат на утверждение. Часто это не самая удачная стратегия.

Принцип «заказчик всегда рядом» подразумевает, что команда контактирует с заказчиком супермаленькими интервалами, показывая ему процесс разработки в мелочах. Это помогает вовремя всё подправить и избежать ситуации, когда при сдаче проекта оказывается, что требования поняли некорректно и принесли не тот результат.

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

Парное программирование

Парное программирование — это моментальная обратная связь в процессе разработки. Я активно использую эту практику, про неё у меня есть целая лекция. 

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

Качество кода, как правило, выше за счёт двух моментов:

  • Люди — «социальные животные»: когда за ними наблюдают, они стараются сильнее. Конечно, при условии, что наблюдение экологичное и не создаёт стресса.

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

Есть контраргумент: зачем платить двоим за одну работу, если можно их распараллелить? На самом деле, когда человек работает один, взгляд замыливается. Он неизбежно совершает ошибки, а потом тратит долгое время на их поиск. При работе вдвоём легче находить ошибки и приходить к качественному результату за более короткий срок. 

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

Использование этой практики — хороший способ быть руководителем группы разработчиков. Программировать в одиночку, если ты лидер команды, — это сродни забиванию гвоздей микроскопом. Лучше практиковать как раз таки парное программирование и код-ревью. 

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

2. Непрерывный, а не пакетный процесс

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

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

Непрерывная интеграция

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

Опять же, все участники процесса быстро получают обратную связь. Например, они могут выяснить, что новая функциональность ломает хребет системе не в конце разработки, а моментально. 

Рефакторинг

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

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

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

Небольшие частые релизы

Это другая грань непрерывных интеграций. Я часто провожу аналогию с неким «релизным поездом» — это как поезд в метро, который ходит по расписанию. В него мы постоянно подсаживаем пассажиров в виде новых фичей.

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

     3. Понимание, разделяемое всеми

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

Как и в парном программировании, здесь подходит выражение «одна голова хорошо, а две — лучше». Один человек, каким бы умным он ни был, может допускать ошибки. Поэтому здорово, когда вся команда понимает, над чем она работает, и все участники способны принимать эффективные решения.

Простота проектирования

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

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

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

Метафора системы

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

Моя любимая поговорка: «Плохая метафора подобна котёнку с дверцей».

Коллективное владение кодом и шаблонами проектирования  

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

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

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

Стандарт оформления кода

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

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

Кому нужны эти практики

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

В настольном теннисе есть два вида хватов, часто их называют «азиатский» и «европейский». Азиатский хват также называют «хват пером», так как ракетку держат подобно перьевой ручке. 

Чемпионы используют и тот, и другой хваты. Нельзя сказать, что какая-то методология держания ракетки объективно лучше, чем другая. Кому-то нравится так, кому-то — по-другому.

Полезно рассмотреть все варианты. Экспериментируйте с подходами и ищите те, которые оптимальны для конкретных команды или человека, проектов или задач.

© Habrahabr.ru