13* Заповедей хорошего кода

* — На самом деле больше

Моисей хочет взять и уебать

О документе

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

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

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

Оглавление

I. Коллективное авторство и персональная ответственность

II. Каноничность оформления и единство стиля

III. Обратная совместимость

IV. Предсказуемость и обратимость

V. Дублирование недопустимо

VI. Безблагодатность напилинга

VII. Зона ответственности

IIX. Клиент, прежде всего, — урод

IX. Магия чисел

X. Запрет без причины — признак дурачины

XI. Меньше трёпа — чище карма

XII. Эффективное использование ресурсов

XIII. Сломал — чини немедленно

XIV. Нормально делай — нормально будет

EPILOGOS

I. Коллективное авторство и персональная ответственность

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

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

Таким образом, внося изменение в какой-то объект или метод (далее — объект), программист обязан:

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

II. Каноничность оформления и единство стиля

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

Стандарт — это официальный для всей команды документ, регламентирующий правила и требования к оформлению исходных текстов программного кода и какие-то минимально необходимые требования к интерфейсам разрабатываемого ПО.

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

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

III. Обратная совместимость

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

Например, мы добавили полей в некий объект и навесили бизнес-логику, запрещающую запись этих объектов с незаполненными новыми полями. В результате мы обязаны позаботиться о том, как пользователи будут записывать те объекты, которые были введены в БД ранее — день, месяц, год, столетие назад.

IV. Предсказуемость и обратимость

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


Предсказуемость

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

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

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

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

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

Самодокументируемость кода очень сильно упрощает его сопровождение и, что еще важнее, она обеспечивает повторное использование программного кода.

В отсутствии самодокументируемости:

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


Обратимость

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

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

Необратимые действия в принципе — это зло. Необратимыми могут быть только какие-то операции, связанные с администрированием и настройкой системы в целом, применением обновлений системы.

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

V. Дублирование кода недопустимо

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

Дублированием кода называется способ разработки, при котором при создании нового функционала копируются уже существующие фрагменты кода или целиком процедуры и функции.

Такой способ разработки создает многочисленные проблемы для сопровождения программного продукта, среди которых:

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

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

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

VI. Безблагодатность напилинга

Делай или не делай, не надо бытаться.
© зелёная Ёда

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

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

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

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

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

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

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

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

VII. Зона ответственности

Зона ответственности ИТ — это инструменты. Программисты отвечают за создание инструментов, которыми будут пользоваться пользователи. Программисты отвечают за соответствие фактического функционирования этих инструментов, требованиям, явно перечисленным в техническом задании (форма ТЗ не имеет здесь значения).

А за данные в системе отвечают пользователи. За своевременное и корректное (с точки зрения предоставленных программистами инструкций) использование инструментов отвечают также пользователи.

За постановку задачи и формулирование требований отвечают бизнес-заказчики (в вырожденных случаях — пользователи).

Горе тому программисту, который забыл свою зону ответственности, который позволил пользователю или бизнес-заказчику протоптать свои (программиста) границы и возложить на себя (программиста) ответственность за данные, требования, использование.

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

IIX. Клиент прежде всего — урод

Основы клиент-серверной архитектуры.

Визуальная (экранная) форма объекта должна быть только интерфейсом пользователя к объекту и не содержать функций манипуляции данными объекта в БД, а также не должна содержать никакой бизнес-логики.

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

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

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

Помимо этого, следует всегда обращать внимание на защиту всех, получаемых с клиента, данных от sql-инъекций и подобных грязных трюков. На каждого беспечного программиста всегда найдется свой little Bobby Tables.

little Bobby Tables

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

IX. Магия чисел

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

Все хранимые в БД значения, влияющие на выполнение кода, должны получаться одним из двух способов:

  • значения извлекаются из метаданных системы по понятному и предсказуемому идентификатору;
  • значения запрашиваются у пользователя посредством клиентского приложения.

Извлекать такие значения в продуктивном коде непосредственно из БД по конкретным значениям ключей записей не следует.

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

BEGIN
 
delete AddAttr a
         
where a.ClassISN = 222068
           
and a.ObjISN   in (select ISN from AgrObject ao where AgrISN = :pNewAgrISN)
           
and a.AttrISN in (222917, 222970, 222969, 222967, 224127);
END

В результате:

  1. мы не знаем, что делает этот код и никогда, видимо, не узнаем;
  2. поведение кода непредсказуемо при переносе из продуктива в копию, т.к в копии под этими isn-ами могут храниться другие значения.
  3. если перечень объектов, с которыми надо делать ЭТО, расширится, то надо будет переписывать код. А перед этим — еще догадаться, что то, что нам надо переписать, — это вот эта адова каша и есть.

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

В данном случае в системе следовало создать две настройки — в одной хранить значение 222068, а во второй — список 222917, 222970, 222969, 222967, 224127 так, чтобы администратор системы без переписывания кода мог в случае необходимости поменять значение и дополнить список.

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

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

X. Запрет без причины — признак дурачины

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

  • Так не бывает — выполняемое действие в сложившихся условиях невозможно конструктивно. Например, нельзя сделать запись в БД ри условии отсутствия связи с этой БД. Так же, как нельзя купить минус одну кружку пива.
  • Будет больно — выполняемое действие в сложившихся условиях приведет к крайне негативным последствиям. Нельзя изменять задним числом данные за границей запрета редактирования потому, что следующая регламентированная отчетность не сойдется с предыдущей и это будет квалифицировано как “искажение бухгалтерской отчетности”, что является правонарушением. Пальцы в розетку засовывать нельзя по той же причине — поплохеет резко и драматично.

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

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

XI. Меньше трёпа — чище карма

Если программе нечем удивить пользователя, она должна работать молча.

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

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

Кроме того, все серверные методы и методы объекта просто не имеют права ничего сообщать или — тем более — спрашивать. Сервер должен писать в лог, а общением с пользователем может и должен заниматься ТОЛЬКО клиент. В крайнем случае сервер может вернуть что-то клиенту, на основании чего клиент сможет сгенерировать пользовательское сообщение или вопрос.

Также никогда не следует использовать пользовательский вывод для логирования действий. Любое логирование должно выполняться на сервере и логи должны храниться на сервере, а не у Васи на экране.

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

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

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

XII. Эффективное использование ресурсов

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

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

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

XIII. Сломал — чини немедленно

Если изменения, внесенные кем-то, сделали код неработоспособным в продуктиве, именно автор изменений ответственен за приведение кода в рабочее состояние.

Причем — не когда-нибудь потом, а ПРЯМО СЕЙЧАС!

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

XIV. Нормально делай — нормально будет

Именно программист и никто другой несет всю полноту ответственности за корректность того кода, который он производит:

  • за синтаксическую корректность кода с точки зрения грамматики языка;
  • за соблюдение всех требований к оформлению кода;
  • за логическую целостность кода и пригодность к повторному его использованию;
  • за функциональное соответствие кода требованиям задачи.

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

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

Синтаксический контроль

У всех программистов обязательном порядке должны быть включены все имеющиеся в IDE средства автоматической проверки синтаксиса.

Кроме этого, перед помещением кода в хранилище, в обязательном порядке следует запускать полную проверку синтаксиса (или компиляцию) всего проекта.

Автотесты и постоянная интеграция

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

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

Обязательное альфа-тестирование

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

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

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

Code review

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

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

ОЙ, ВСЁ!

Если ты читаешь это, значит ты дочитал 13 заповедей до конца.
Молодец!

За дизайн спасибо HTML5 UP.