Что можно приобрести, отказавшись от документирования кода функций

Disclaimer: за провокационным названием поста не скрывается призыв оторвать функциональным программистам руки и никогда не писать doc-блоки в коде. Нет. Это сказ о том, что бессознательное следование правилу “описывать поведение функций” не принесёт вам того счастья, которого вы ожидаете.

Все имена вымышлены, все совпадения случайны.

Представьте себе типичный PHP-проект. Jenkins, PHPDocumentor, док-блоки перед каждой функцией с детальным описанием чего она делает, которые потом собираются в единую документацию. Типа круто. Приведу пример знакомого вам с детства оформления кода:

/**
* Функция активации юзера на конкретном сайте.
* Принимает на вход два параметра – идентификатор сайта и емайл юзера.
*
* Помечает сайт как активированный пользователем. Активирует юзера в базе данных. Активирует юзера в биллинге. Отправляет юзеру письмо с логином и паролем. Снимает с сайта гостевой рекламный блок. Подготавливает следующий демонстрационный сайт для прогрева кэша. Записывает в базу отложенных уведомлений пометку, чтобы через неделю отправить юзеру письмо-напоминание.
*
* param $siteId string
* param $email string
*/
function userActivate($siteId, $email) {

}

Знакомо? Так вот, это п***дец.

Вы напихали в одну функцию раз-два-три… семь разнотипных действий, относящихся к одному событию. Мартин Фаулер отрезал бы вам уши за такое решение. Чудно, вы решили бизнес-задачу, но вы заложили в продукт такую бомбу замедленного действия, что через год-два вам будет легче отдать разработку новой версии на аутсорс, чем вносить исправления самому.

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

И ладно бы, если вы работаете над проектом один. А если вас 5-10 человек, то в будущем каждый будет вносить свой кусочек изменений в исходный код, задействуя одну-две из ваших подфункций для каких-то своих задач. Ну, вы поняли. Через год у вас вырастет Адский Конгломерат v2.0, и вы снова окажетесь в жопе.

Внимательный читатель заметит, что нужно было описать класс User и метод activate, например так:

class User {
public function activate($siteId, $email) { … }
}

Однако, как проницательно замечает один мой коллега, это всё равно что когда вам нужно выкопать яму в земле, вы говорите “земля->копайся()”. Это столь же бессмысленно и вредно, как и “юзер->активируйся()”. Очевидно, что земля не копается сама по себе – её копают. Не объект совершает действие, а над объектом совершается действие.

Как надо делать:

Во-первых, забыть ООП как страшный сон. Осознать, что реальный мир состоит не из объектов и функций, а из событий и реакций на них. В реальном мире события происходят спонтанно, параллельно, и на каждое из них может быть реакция, а может и не быть. Может быть несколько реакций на одно и то же событие, а может быть одна реакция на комбинацию нескольких событий. Несколько реакций, как правило, происходят параллельно (одновременно друг с другом).

Принципы ООП хороши для описания статичных моделей. Вот объект, вот его свойства. Но тот злой гений, который придумал что функции объекта могут описывать его поведение, выкопал яму перед миллионами программистов. Начиная с того, что поведение может (должно) меняться в зависимости от ситуации (контекста)… Но об этом в другой раз.

Не объекты выполняют действия, а над объектами выполняются действия – как реакции на возникающие события. Отсюда возникла и концепция SOA (Сервисно-ориентированная архитектура, продвигаемая IBM ещё с 19xx годов), в которой операции над каждым классом объектов выполняются отдельными изолированными друг от друга сервисами.

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

Во-первых, определитесь с событием. Оно в нашем примере очевидно:

name: user.activate
params:
  siteId: 1234567890
  email: vasya@pupkin.ru

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

Вот его надо документировать, а не ваш код.

Отсюда, кстати, проистекает интересный эффект – документация может появиться раньше кода (и сегодня это нормально), а в старых системах попытка вызывать несуществующие методы может привести к Fatal error (и конечно же, с точки зрения разработчика старой системы – виноват не он).

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

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

Итак, реагируем на событие:

1) Сервис, отвечающий за пользователей, ловит это событие и отмечает пользователя как активированного.

2) Биллинг ловит это событие и начинает считать по пользователю списание средств.

3) Сервис, отвечающий за сайты, активирует сайт и снимает с него рекламный блок.

4) Сервис нотификаций отправляет пользователю приветственное письмо.

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

Преимущества очевидны, даже если вы не читали литературу по event-driven development. Вместо адского конгломерата вы получаете простую систему с низкой связанностью, а значит с низкой стоимостью владения. Легко внести изменения в поведение любого обработчика прямо “на ходу”, не влезая в остальные. Легко добавить новый обработчик, легко убрать, временно отключить, или заменить любой из существующих.

– Ну и что? – скажете вы. – Нужно просто документировать обработчики в каждом сервисе, и всё. Никто не отменяет документирования кода!

А вот и нет.

Возвращаясь к концепции SOA, легко понять, что если сервисы изолированы друг от друга, то они инкапсулируют логику действий над объектами внутри себя. Нельзя из биллинга напрямую лезть в сервис, обслуживающий сайты. Нельзя из сервиса нотификаций лезть в БД пользователей. Можно только общаться между сервисами через межсервисные каналы коммуникаций (как правило вне кода, через брокеры сообщений). А стало быть, внутренняя механика сервисов имеет гораздо меньшее значение, чем их внешние интерфейсы.

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

Глядя на функцию даже с хорошим doc-блоком, вы не сможете быстро составить представление о том, каковы цели бизнес-процесса, в котором она участвует.

Поэтому вместо док-блоков, собираемых PHPDocumentor-ом, гораздо важнее иметь описание межсервисного API – описание того, каков перечень и смысл реакций на те или иные события в системе. Поэтому крайне важно, после того как вы потратили несколько месяцев доводя продукт до следующего major release (когда “устаканивается” внутреннее API) – актуализировать его в wiki проекта, чтобы пользоваться в дальнейшей работе.

Когда записывать, и когда не записывать задачи в таск-менеджер

Тут Мегаплан постепенно приобретает человеческое лицо, и начинает понимать что нельзя “с наскока” внедрить процесс постановки задач, как лампочку Ильича, в каждую избу на деревне. Выскажу и я своё скромное мнение.

Существует две крайности относительно постановки задач: одна из них – полное отсутствие таск-менеджера вообще, передача задач устно, по аське, по е-майлу, на стикерах к монитору. Вторая – запись всего-всего-всего в таск-менеджер, и обсуждение всех подробностей и шагов решения задачи там же в комментариях, даже между людьми сидящими за соседними столами.

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

Даже тщательное ведение таск-менеджера не приближает вас к успеху проекта.

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

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

Вернёмся к теме:

Записывать в таск-менеджер (трекер) нужно:

Когда команда распределена. Если у вас команда и/или заказчики значительно разделены территориально и по рабочему времени, то очевидно рекомендуется записывать задачи и результаты, чтобы передавать их друг другу.

Когда задача – это на самом деле баг. Баг, как сущность, – это формализованно описанная проблема. Есть даже мантра тестировщиков, помощающая составить хорошее описание: что я делал, что я получил, что я ожидал получить. Говоря умными словами – шаги по воспроизведению, полученный результат, ожидаемый [по спецификации] результат. Однозначно записывать, иначе потеряется.

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

Когда задача содержит много формализованных значений. Например, увеличить максимальную длину логина с 8 до 16 символов, и допускать логины начинающиеся только с маленькой буквы из диапазона [a-z].

Когда исполнитель пьян. Ну, или не пьян, а с похмелья. Или не с похмелья, а всю ночь херачил над другими проектами, а утром вспомнил что нужно идти на работу. Или всю ночь укачивал капризничающего ребёнка. В общем, в тех случаях, когда над ним нужно сидеть и помогать нажимать пальцами на кнопки.

Записывать в таск-менеджер (трекер) НЕ нужно:

Когда задачу лучше сделать прямо сейчас. Как в GTD есть правило – если входящее письмо требует меньше двух минут на решение, то нужно решать сразу, не перекладывая в todo или later. Так и в разработке – если проще обсудить и тут же решить, то надо обсудить и тут же решить. Вспоминаем начало этого поста, если забыли.

Важно, чтобы это реально была задача класса “быстро решить”, а не бравада вашего разработчика в стиле “плавали-знаем”, которая выльется в полдня работы.

Когда задача требует исследования и/или проектирования. Педанты скажут, что надо делать две задачи – на исследование и на реализацию. Если вам так удобно – делайте. Но если вы сами работали программистом, то вы знаете, что в фазе исследования вы можете уже накодить так много пробных вариантов, что фаза реализации превратится по сути в рефакторинг уже сделанной работы. Как теперь отчитываться по двум задачам?

Можно не оформлять исследовательскую работу, но оформить в задачу когда исследование проведено и стало понятно что делать. Но опять же, в большинстве случаев исполнитель уже настолько погрузился в проблему, уже исписал так много листиков с алгоритмами и схемами, что переоформлять это в трекере нет никакого смысла: и так всем понятно (а главное – исполнителю понятно), что надо сделать и как оно будет работать.

Автор этих строк пользуется простым методом, и вам рекомендует: берите свой айфон и фоткайте все эти листики. Или маркерные доски, если там нарисовано. Это и будет документацией, если она вдруг понадобится кому-то из коллег.

Когда задача очевидна. Говоря другими словами, когда мимо неё не пройти, не споткнувшись. Очевидно, что в проекте должен быть процесс деплоя (скрипты, выполняющие выгрузку новой версии в продакшен). Очевидно, что у проекта должны быть группы dev-, stage-, prod-серверов. Очевидно, что когда у интернет-магазина есть корзина, то должен быть и способ фиксации заказов.

Замечание: речь про собственные (внутренние) проекты. Это не подходит, если вы работаете по fix-price и фиксированному ТЗ с внешним заказчиком.

Когда конечный результат не определён. Мой любимый пример – система аутентификации, авторизации, и распределения прав. Не смотря на (казалось бы) универсальный стандарт ACL, я не встречал ни одного проекта, в котором система прав доступа была бы сделана так, чтобы удовлетворять всех от разработчиков до клиентов. Если у вас нет возможности воткнуть какой-то готовый модуль и успокоиться, то единственный вариант – принудить команду запилить конкретное относительно нормальное и легко расширяемое (в будущем) решение. Иначе холивар вырастет на месяц.

Возвращаясь к теме поста, это пример задачи, не имеющей формализованного результата. Понятно, что система авторизации в проекте (в общем случае) должна быть. Понятно также, что на начальных этапах всем по%уй, как она устроена. Это потом, в результате тестовой эксплуатации первых альфа-версий, к ней появятся конкретные требования. Туда ходи, сюда не ходи. Это мочь, это не мочь. Но это только потом, а сейчас, на берегу, у вас есть несколько вариантов:

– привлечь профессиональное проектирование и запроектировать от начала до конца. В большинстве случаев означает бездарно потерять время [/деньги], потому что всё равно половина будет переделана, а вторая половина сразу не подойдёт.
– надавить авторитетом и сформулировать как что должно работать от начала до конца. Говно-вариант, потому что если ваша команда – не роботы, то они и сами смогут выдвинуть встречный десяток альтернативных идей, и будут демотивированы вашим давлением.
– выбрать исполнителя, соответствующего по квалификации, который сам (с небольшими подсказками) выберёт нормальный алгоритм и запилит хорошее расширяемое решение.

По какому варианту пойти – вам должно быть очевидно. И очевидно, что по такой задаче бесполезно записывать в трекер что-то большее чем заголовок “Внедрить подсистему разграничения прав доступа”.

Резюме:

Автор этих строк использует простое правило: если результат формализован и нельзя сделать прямо сейчас – записывать. В иных случаях – на%уй. Как действовать вам – зависит от вашего проекта и команды. Выберите способ, с которым команда будет наиболее продуктивна, и действуйте по нему.

Проектирование VS Прототипирование: холивар или реальное решение?

Холивар проектирования против прототипирования в больших проектах – это палка о двух концах. На одном из которых – консервативные приверженцы махрового энтерпрайза с каскадным (waterfall) подходом “Спроектируй-запланируй-отрисуй-закодируй-протестируй”, на другом – ярые фанаты Agile и всякого там “XP” с идеей “лучший прототип – это быстро сделанный и запущенный проект”. Continue reading “Проектирование VS Прототипирование: холивар или реальное решение?”

Сделать большой проект – это как проехать через весь город в незнакомой стране

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

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

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

Ну, или взять такси.

50 оттенков геморроя: константы и переменные в PHP

Я ненавижу константы в PHP. Когда мне случайно попадается файлик конфигурации проекта, в котором на три страницы перечислены константы с параметрами, мне хочется оторвать руки автору и пришить их в правильное место. Но давайте разберёмся, почему я их так не люблю. Continue reading “50 оттенков геморроя: константы и переменные в PHP”