Главные характеристики качественного кода
Как часто вы поражаетесь, читая чужой код, и думаете «господи, ну и каша. ». Скорее всего, достаточно часто. И можете ли вы быть уверенным, что никто не думал также когда читал ваш код? Другими словами, насколько вы уверены в чистоте своего кода? Можно быть уверенным только если полностью понимаешь, что значит чистый код.
Сложно дать точное определение чистому коду, и, скорее всего, сколько программистов — столько определений. Однако, некоторые принципы достаточно универсальны. Я собрал девять самых релевантных и описал ниже.
1.Плохой код делает слишком много, чистый код сфокусирован
Каждый класс, метод и любая другая сущность должна оставаться неискаженной. Она должна следовать принципу единственной обязанности. Вкратце, можно сказать так: если подумать о причинах изменения класса, то нельзя придумать больше одной хорошей причины.
Но я бы не ограничивал определение классами. В свой последней статье Ральф Вестфал (Ralf Westphal) представил более широкое определение принципа единственной обязанности:
Функциональная единица на определенном уровне абстракции должна отвечать за один аспект требований системы. Аспект требований это признак или свойство требования, которое может изменяться независимо от других аспектов.
Если хотите узнать больше, то советую прочитать его статью.
2. Язык, на котором вы написали код, должен выглядеть как будто его создали для решения этой проблемы.
Не язык делает программу простой, а программист, который делает так, что язык выглядит просто.
(цитата Роберта C. Мартина)
Это означает, что не нужно использовать хаки, из-за которых код и язык обычно выглядят неуклюже. Если вы считаете, что что-то можно сделать только хаком или заплаткой, то обычно это значит, что вы недостаточно времени провели в попытках найти хорошее, чистое решение.
3. Не нужно избыточности
Код должен подчиняться правилу DRY (Don’t repeat yourself — не повторяйся). Если это так, то модификация любого элемента системы не требует изменения других, логически не связанных элементов.
4. Читать ваш код должно быть приятно
Когда читаешь ваш код, должно быть ощущение, что читаешь «Гарри Поттера» (да, знаю, я немного переборщил). Должно быть ощущение, что его написали, чтобы любой разработчик мог с легкостью прочитать, не проводя часы в попытках разобраться.
Для этого нужно стараться подчиняться принципам KISS (Keep It Simple, Stupid!) и YAGNI (You Ain’t Gonna Need It — Вам это не понадобится). Принцип KISS гласит, что большинство систем работают лучше всего если сохранять их простоту, а не развивать сложность.
То есть, простота должна быть целью в дизайне, и нужно избегать ненужных усложнений. YAGNI это практика, мотивирующая фокусироваться на простейших вещах, которые позволяют вашему софту работать.
5. Другой разработчик может легко расширить ваш код
Вы не пишите код для себя, или еще хуже — для компилятора. Вы пишите код для других разработчиков. Не будьте эгоистом — подумайте о людях. Не пытайте других разработчиков, выдавая плохо поддерживаемый и плохо расширяемый код. К тому же, через несколько месяцев этим другим разработчиком можете оказаться вы сами.
6. Нужно минимизировать зависимости
7. Меньше — лучше
Код должен быть минимальным. Классы и модули должны быть короткими, в идеале — всего несколько строк кода. Код должен быть хорошо разделен (в том числе внутри класса). Чем лучше вы делите код, тем легче его читать. Этот принцип хорошо влияет на пункт 4 — другим программистам будет проще понять его.
8. Необходимы юнит- и приемочные тесты
Как можно узнать, удовлетворяет ли наш код требованиям, если не писать тесты? Как можно поддерживать и расширять его, не боясь, что все сломается? Код без тестов — просто не чист. Если хотите узнать больше о принципах юнит-тестирования, то советую прочитать очень хорошую статью Three Pillars of Unit Tests, написанную одним из моих коллег.
9. Код должен быть выразительным
Выразительность кода означает, что в нем используются имена со смыслом. Эти имена должны выражать намерение. Они не должны запутывать. Они должны быть различимыми. Выразительность документирует код и делает отдельную документацию менее необходимой. Если хотите узнать больше о теме самодокументированного кода, то советую прочитать эту статью.
Так что же такое чистый код?
В целом, одно последнее качестве можно назвать итогом всего вышесказанного:
Чистый код написан тем, кому не плевать.
цитата Майкла Фетерса (Michael Feathers).
Он написан тем, кто относится к коду как к искусству, и кто обращает внимание на все детали.
Тема чистого кода — очень сложна, и выходит за рамки описанного в этой статье. Так что, если вы считаете, что существуют другие характеристики чистого кода, то, пожалуйста, поделитесь ими в комментариях.
Чистый код: причины и следствия
Автор: Виктор Свирский, Senior Python Developer / Team Lead, DataArt
Сколько программистов, столько и определений, что такое чистый код. Часто, проводя собеседование, я слышу, что хороший код — это такой, который легко читается. Согласен, но как подсказывает мой личный опыт, это только вершина айсберга.
Первый звоночек, который нам сообщает, что код перестает быть чистым — это рост времени разработки новой функциональности и увеличение регрессионного скоупа при малейшем изменении в системе. Это следствие того, что технический долг накапливается, компоненты в системе очень тесно связаны, автотесты отсутствуют. Причины этого могут быть:
Что такое чистый код?
Получается, чтобы сказать, что код чистый и система спроектирована грамотно, легкого чтения кода недостаточно. Он должен обладать и другими качествами:
Стоит ли писать чистый код?
Однозначно стоит! Но не всегда и не везде стоит уделять чистоте слишком много внимания.
Не стоит забывать о целесообразности и сроке жизни вашего кода. Например, если перед вами стоит задача разработки концепции — PoC (Proof of concept), и вы доказываете, что выбранный стек технологий выполняет поставленную задачу, ваш код станет неактуален уже через неделю или две. Не стоит тратить силы на совершенствование этого функционала.
Бытует мнение, что не нужно следить за качеством кода или части системы, которые в скором времени будут заменены. И это неверно по нескольким причинам. Высокое качество исполнения сделает переход или интеграцию с новыми частями более простыми, бесшовными и быстрыми. Оно наверняка упростит жизнь в тех случаях, когда несколько версий кода придется поддерживать одновременно. Количество регрессионных ошибок с чистым кодом будет в разы меньше. Также не стоит забывать, что нет ничего более постоянного, чем временное. Возможно, задачи по улучшению этой части кода еще несколько месяцев будут лежать в бэклоге.
Что поможет улучшить ваш код?
Большинство программистов мечтают писать код быстро и максимально красиво, причем так, чтобы все идеально работало с первого раза. Но далеко не каждому удается сделать код не просто работающим, но и понятным. Как же добиться успеха в написании чистого кода? Есть два пути — самоорганизация и командная работа.
Самоорганизация
Рассмотрим несколько возможных способов улучшить индивидуальное качество кода. Эти рекомендации подойдут разработчику любого уровня.
Не спешите решать задачи в лоб. Задавайте вопросы старшим разработчикам и самому себе. Всегда важно понимать причинно-следственную связь тех или иных решений. Хорошо понимая проблему, вы сможете эффективно ее решить.
Любой опыт лучше, чем его отсутствие.
Командная работа
Большинство задач решается в команде. Очень важно разделять ответственность за качество между ее участниками. Чем больше команда, тем сложнее поддерживать продукт в хорошем состоянии. Рассмотрим несколько подходов удержания кода в вышеуказанных условиях.
Во время проверки кода необходимо учитывать несколько вещей:
Суть непрерывной интеграции в том, что она позволяет быстро получить множество отзывов о текущем состоянии кода.
Непрерывная интеграция работает, когда вы следуете двум простым правилам:
Важно иметь список соглашений о кодировании. Но прежде чем вы начнете составлять список, все в команде должны понимать значимость этого соглашения. Не рассчитывайте, что такое соглашение будет принято с первого раза, вас ожидает множество дискуссий.
Составьте список соглашений о кодировании, в которых вы обозначаете то, как переменные должны объявляться, соглашения об именах и т. д. Количество правил, которые вы можете добавить в этот список, не ограничено и может варьироваться. Просто делайте то, что работает для вас и вашей команды. Не стесняйтесь добавлять новые правила в список соглашений, если команде это подходит. Это же касается и удаления соглашений из списка.
После того, как вы получили свой список соглашений о кодировании, крайне важно придерживаться их. Наиболее предпочтительный способ — проверить соглашения о кодировании с помощью статических анализаторов и непрерывной интеграции, поскольку он не требует каких-либо ручных действий.
Чем меньше ошибок в коде, тем выше его качество. Тщательное тестирование отфильтровывает критические ошибки и гарантирует, что код работает так, как задумано.
Наличие четкой стратегии тестирования важно, когда дело доходит до улучшения качества кода. Как минимум, ваш код должен быть модульным. Еще лучше, если вы хотите использовать и другие способы, например интеграционное или регрессионное тестирование.
Наличие ошибок в вашем коде, вероятно, неизбежно. Поэтому анализ и способ обработки этих ошибок очень важны. Если вы хотите улучшить свои навыки, важно учиться на собственных ошибках.
Когда возникает ошибка, проанализируйте ее с помощью нескольких вопросов:
Есть несколько метрик, которые вы можете использовать для количественной оценки качества вашего кода. С такой задачей легко справляется SonarQube. Он с легкостью поможет вам собрать все необходимо важные метрики:
Используется при тестировании программного обеспечения. Она показывает процент исходного кода программы, который был выполнен в процессе тестирования. Задайте планку, ниже которой процентное соотношение ваших тестов не опускается.
Ошибки в коде чем-то сродни углеродному следу. Избежать совсем невозможно, а лишний выхлоп сам по себе не убьет ни человечества, ни окружающей его природы. Тем не менее, снизить негативный эффект от своего пребывания на планете сегодня кажется естественной потребностью. Примерно так же и написание чистого кода оказывается ответственностью каждого разработчика. Независимо от того, какой именно путь вы выберете, необходимо стремиться писать работающий и понятный код.
Хорошо, если удастся не превращать чистоту в фетиш, учитывая срок жизни нашего кода и оценивая целесообразность дальнейших улучшений. Главное помнить о людях: пользователях, которых может подвести внезапный отказ даже небольшой части разработанной нами системы, и инженерах, которым предстоит эту систему поддерживать.
Пишите код, который легко удалять, а не дополнять

Каждая новая строка кода приносит с собой затраты в виде необходимости ее поддержки. Чтобы избежать подобных затрат на работу с большим количеством кода мы прибегаем к его повторному использованию. Недостаток применения этого метода заключается в том, что он начинает мешать нам, в случае если мы захотим что-либо поменять в будущем.
Чем больше у вашего API пользователей, тем больше кода приходится переписывать для введения новых изменений. Верно и обратное: чем больше вы полагаетесь на сторонний API, тем больше проблем испытываете когда он изменяется. Упорядочивание взаимодействия и взаимосвязей разных частей кода является серьезной проблемой в больших системах. И по мере развития проекта, растет и масштаб этой проблемы.
Перевод статьи на русский язык подготовлен компанией PayOnline, провайдером платежных решений для вашего онлайн-бизнеса.
Я говорю о том, что если уж мы так хотим считать количество строк кода, нам следует смотреть на них не как на «произведенные строки», но как на «потраченные строки», — Э. Дейкстра, рукопись 1036.
Если относиться к «строкам кода» как к «потраченным», тогда, удаляя их, мы снижаем стоимость поддержки. Вместо создания повторно используемых программ, нам следует стремиться к созданию программ одноразового употребления. Думаю, не нужно объяснять вам, что удалять код гораздо веселее, чем писать его.
Чтобы написать легко поддающий удалению код, старайтесь всячески избегать зависимостей в общем и как можно чаще отказываться от их упорядочивания. Разбивайте свой код на уровни: пишите простые в использовании API, создавая их на основе более простых в применении, но в целом менее удобных по отдельности решений. Разделяйте код, изолируя сложные в написании и наиболее изменчивые части от остального программного кода и друг от друга. Не делайте жестких определений для всех возможных случаев: в некоторых ситуациях лучше оставить возможность выбора во время работы программы. Не пытайтесь заниматься всем этим одновременно и подумайте, следует ли вам вообще писать так много кода.
Шаг 0: Не пишите код
Количество строк кода само по себе мало о чем говорит, а вот эффект от 50, 500, 5 000, 10 000, 25 000 строк и т. д. отличается существенно. Монолит размером в миллион строк попортит вам больше нервов, чем структура в 10 тыс. строк. Если же говорить о времени, деньгах и усилиях, которые вам придется потратить на его замену, то здесь разница будет ощущаться гораздо сильнее.
Чем больше у вас кода, тем сложнее от него избавиться. Тем не менее, сохранение одной строчки кода не дает никаких результатов само по себе. Как бы то ни было, удалять проще всего такой код, от которого вы успели отказаться еще до того, как приняться за его написание.
Шаг 1: Пользуйтесь копипастой
Писать «многоразовый» код гораздо легче задним числом, имея на руках несколько примеров использования, нежели пытаться оценить, какие из них могут потребоваться в будущем. Впрочем, положительные стороны этого метода вы ощущаете на себе, уже просто работая с файловой системой. Поэтому с повторным использованием кода вроде бы все в порядке: небольшая избыточность пойдет программе только на пользу.
Пользоваться копипастой время от времени лучше, чем создавать библиотечную функцию, только лишь для того, чтобы понять, как эта функция будет себя вести в вашем случае. То есть следует хорошенько подумать, надо ли вам в данный момент писать функцию вместо копипасты, потому что как только вы превращаете свои наработки в общедоступный API, процесс их последующего изменения становится сложнее.
Всегда помните, что написанная вами функция будет вызываться как по прямому назначению, так и для других вещей, о которых вы даже и не думали в момент ее создания. Использующие ее программисты будут полагаться на собственные наблюдения, а не на то, что вы написали в документации. Ну и, конечно, легче будет удалить содержимое функции, нежели саму функцию.
Шаг 2: Не пользуйтесь копипастой
Небольшое отступление: создайте для util специальную директорию и сохраняйте каждую утилиту в отдельный файл. Использование одного util-файла приведет к тому, что он в конечном счете вырастет до огромных размеров и тогда делить его на отдельные части будет очень сложно. Всегда помните, что ведение одного util-файла — плохая практика.
Чем менее специфичен тот или иной код для вашего приложения или проекта, тем легче вам будет использовать его повторно, и тем меньше вероятность его изменения или удаления. Это, как правило, библиотечный код, описывающий запись данных, работу со сторонними API, дескрипторы файлов или процессы. Еще примеры, которые вам не нужно будет удалять — списки, хеш-таблицы и другие наборы данных. Не из-за частой простоты их интерфейсов, но потому, что они не будут расти с точки зрения области применения с течением времени.
Не пытайтесь специально облегчить себе задачу удаления кода. Вместо этого на данном этапе мы должны стараться держать трудно поддающиеся удалению части программы как можно дальше от частей, удалить которые легко.
Шаг 3: Пишите больше boilerplate-кода
Библиотеки пишутся, чтобы избежать постоянной копипасты. И тем не менее, часто получается так, что в процессе их написания мы добавляем еще больше копипасты, но называем ее по-другому: boilerplate. Создание бойлерплейтов во многом похоже на копипастинг, с той разницей, что в нем вы каждый раз меняете разную часть кода, а не одну и ту же. Как и в случае с копипастой, вы дублируете части кода, чтобы избежать представления зависимостей и добиться гибкости, взамен получая еще большую избыточность.
Библиотеки, которым требуются бойлерплейты — это часто разработки вроде сетевых протоколов, форматов передачи данных, инструментов для парсинга и вообще все те кодовые базы, в которых сложно объединить политики (то, что программа должна делать) с протоколом (что она может делать) без накладывания каких-либо ограничений. Такой код сложно удалить: как правило, он требуется для общения с другими компьютерами или обработки различных файлов. При этом загрязнять его бизнес-логикой — это последнее, чего мы хотим. Нет, речь не идет о каком-то дополнительном упражнении в повторном использовании кода. Мы просто стараемся держать все подверженные частым изменениям блоки кода подальше от относительно статических. То есть мы занимаемся минимизацией зависимостей библиотечного кода, хоть пусть нам и приходится писать для него бойлерплейт. В итоге получается, что вы пишите больше строк кода, но все они приходятся на те части, которые легко поддаются удалению.
Шаг 4: Не пишите boilerplate-код
Бойлерплейты лучше всего работают, когда предполагается, что библиотеки будут соответствовать самым разным вкусам разработчиков, но иногда такой подход приводит к чрезмерной избыточности. Тогда наступает время поместить вашу гибкую библиотеку внутрь другой, которая обладает своими взглядами на правила, схемы и состояния. Создание простых в использовании API заключается в превращении вашего boilerplate в библиотеку. И это не такая уж и редкость, как вы могли бы подумать. В качестве примера можно привести один из самых популярных и любимых http-клиентов для Python, requests, который успешно справляется с предоставлением простого интерфейса, работая на основе более избыточной библиотеки urllib3. Requests позволяет пользоваться многими типовыми схемами работы с http, скрывая большинство подробностей от глаз пользователя. В то же время urllib3 выполняет конвейеризацию, управление соединением и ничего от пользователя не прячет.
Дело здесь не столько в том, чтобы скрывать какие-то подробности, помещая одну библиотеку внутрь другой, сколько в разделении ответственности: requests можно сравнить с турагентством, которое дает на выбор путевки для популярных в мире http путешествий, тогда как urllib3 нужна, чтобы удостовериться, что у вас есть все необходимое, чтобы это путешествие прошло как надо.
Нет, я не призываю вас немедленно пойти и создать директории /protocol/ и /policy/. Однако, возможно, это станет необходимостью, ведь вы наверняка захотите содержать вашу util-директорию свободной от всякой бизнес-логики и при этом продолжать работать над созданием своего тандема библиотек. Вы вполне можете работать над ними параллельно, не дожидаясь, пока работа над базовой библиотекой будет завершена.
Часто бывает полезным также делать «обертки» для сторонних библиотек, даже если они выполнены в виде протокола. Вы можете создать библиотеку, подходящую именно для вашего кода, вместо того, чтобы использовать общие для всего проекта решения. Часто бывает так, что создаваемый вами API не может быть одновременно приятным в использовании и хорошо расширяемым. Эти два понятия идут вразрез друг с другом.
Разграничение ответственности позволяет нам порадовать некоторых пользователей, не перекрывая кислород другим. Деление на уровни легче всего делать, когда у вас изначально есть хороший API, однако написание хорошего API поверх плохого едва ли придется вам по вкусу. Хорошие API проектируются с учетом того, как их будут видеть пользователи (т. е. программисты), и создание иерархии в этом смысле означает понимание того, что вы не можете угодить всем одновременно.
Смысл разделения кода на уровни состоит не столько в том, чтобы написать код, который вы впоследствии сможете удалить, сколько в том, чтобы сделать трудноудаляемый код приятным в использовании (не загрязнять его бизнес-логикой).
Шаг 5: Напишите большой блок кода
Сколько бы вы ни копипастили, ни рефакторили, ни делили на уровни, ни проектировали, все сводится к тому, что код должен выполнять какую-то работу. Иногда, если все идет не так, как задумывалось, лучше всего просто сдаться и написать доброе количество низкокачественного кода, просто чтобы все остальное заработало.
Бизнес-логика — это бесконечная серия пограничных случаев, а также быстрых и грязных трюков. И это нормально. Меня это устраивает. Другие стили вроде «игрового кода» и «кода основателя» представляют собой то же самое: попытка срезать на поворотах, чтобы сэкономить значительное количество времени.
Почему я предлагаю просто взять и написать много кода? Потому что избавляться от одной большой ошибки бывает гораздо легче, нежели пытаться удалить 18 маленьких, тесно переплетенных друг с другом. Программирование вообще во многом связано с исследованием. Сделать несколько ошибок и получить результат — способ более быстрый, чем пытаться продумать все с первого раза. Это особенно справедливо в случаях веселых или творческих начинаний. Если вы пишете свою первую игру, не начинайте этот процесс с движка. Аналогично этому, не пишите веб-фреймворк поперек приложения. Я говорю так, потому что знаю, что у вас все равно получится бардак, разобраться в котором не сможет ни один психически здоровый человек. Поэтому лучше сядьте и напишите сначала именно это бардак.
Монорепозитории представляют собой такой же компромисс: вы не будете заранее знать, как разделить код. Ну а разворачивать такую вот «одну большую ошибку» легче, чем 20 тесно связанных. Когда вы знаете, какую часть кода надо будет вскоре забросить, а какую удалить или с легкостью заменить, вы можете срезать гораздо больше углов. Так бывает, когда вы занимаетесь заказами по сайтам и веб-страницами, посвященными одноразовым событиям, ну или любой подобной работой, где у вас есть готовый шаблон и все что вам остается делать — это штамповать копии или просто заполнять пробелы, оставленные разработчиками фреймворка.
Нет, я не предлагаю вам писать одну и ту же ерунду по десять раз, пытаясь исправить все ее ошибки. Я говорю о другом. Как сказал когда-то Алан Перлис: «Все должно создаваться сверху вниз, за исключением первого раза». Не бойтесь совершать новые ошибки, брать на себя новые риски и пусть медленно, но верно, с помощью итерации продвигаться вперед.
Стать профессиональным разработчиком ПО означает собрать целый каталог сожалений и ошибок. Успех ничему не учит. Вы не можете заранее знать, как выглядит хороший код, но вот оставшиеся от плохого кода шрамы всегда свежи в вашем сознании. В любом случае проекты, в конце концов, либо терпят неудачу, либо становятся унаследованным кодом. Неудачи случаются чаще, чем успехи. Быстрее будет слепить десять разных комков грязи и посмотреть, что из этого получится, нежели пытаться довести до блеска одну кучу дерьма. Удалять код целиком проще, чем делать это по частям.
Шаг 6: Делите код на части
Большие комки грязи легко лепить, но вот поддерживать — сложнее всего. Попытка внести в них простое, на первый взгляд, изменение заканчивается внесением исправлений почти во все части кодовой базы.
Итак, мы создали в нашем коде иерархию для разделения ответственности как для платформенных, так и для доменных задач, и теперь нам надо найти способ разделить логику, которая находиться поверх всего этого.
«Начните со списка самых сложных проектных решений или тех из них, которые с наибольшей вероятностью изменятся. Далее проектируйте каждый модуль так, чтобы скрыть такое решение от других модулей». — Дэвид Парнас.
Вместо того чтобы разбивать код на части со схожей функциональностью, мы делим его на части, исходя из того, чем они отличаются друг от друга. Мы выделяем те из них, которые вызывают наибольшие трудности в написании, поддержке или удалении. Мы не создаем модули, исходя из того, сможем ли мы использовать их повторно, главное, чтобы их было удобно менять в будущем.
К сожалению, некоторые проблемы оказываются связанны друг с другом теснее, и их бывает сложнее отделить от других. Несмотря на принцип одной ответственности, который гласит, что «каждый модуль должен решать только одну сложную проблему», на деле гораздо важнее, чтобы «решением каждой сложной проблемы занимался только один модуль». В случаях, когда модуль занимается сразу двумя вещами, это происходит, потому что изменение одной части требует изменения другой. Работать с одним ужасным, но простым в плане интерфейса компонентом часто бывает проще, чем с двумя компонентами, требующими тщательной координации друг с другом.
«Я не стану пытаться сейчас точнее определить материал, подпадающий под это краткое описание [«слабая связанность»]; возможно, я никогда не сумею дать этому внятное определение. Однако я знаю, когда вижу, и кодовая база, рассматриваемая в этом деле, не такая». — Судья Верховного суда США Стьюарт.
Система, в которой вы можете удалять те или иные ее части без необходимости переписывания другие, часто называется слабосвязанной, однако объяснить как она выглядит на деле гораздо проще, чем заранее знать как ее создать. Слабая связанность допускает даже жесткое задание переменной или использование флага командной строки поверх нее. Смысл этой методики заключается в получении возможности менять основные решения без необходимости переделывать весь код.
В продуктах Microsoft Windows, к примеру, для этих целей используются внешние и внутренние API. Внешние API привязаны к жизненному циклу десктопных программ, а внутренний — к ядру, на основе которого они работают. Скрытие этих API дает Microsoft гибкие возможности по внесению в систему изменений без какой-либо опасности сломать кучу программ в результате этой деятельности.
HTTP также содержит примеры слабой связанности: добавление кэша перед HTTP сервером, перемещение изображений в CDN, в результате которого изменяются лишь ссылки на них. Ни тот, ни другой механизм не ломают ваш браузер. Другой пример слабой связанности — применяемые в HTTP коды ошибок. Общие для серверов по всему вебу проблемы имеют свой уникальный идентификатор. Когда вы получаете 400 ошибку, вы знаете, что выполнение тех же операции, которые к ней привели, никак не изменит ситуацию. А вот в случае с 500 ошибкой повторная перезагрузка страницы может все изменить. HTTP-клиенты могут обрабатывать множество ошибок, избавляя программистов от необходимости делать это самостоятельно.
Следует учитывать, как ваше ПО будет обрабатывать ошибки, когда вы будете раскладывать его на более малые части. И, конечно же, об этом тоже легче говорить, чем делать.
«Я решил, хоть и с большой неохотой, использовать LATEX».— Джоуи Армстронг. Создание распределенных систем, надежно работающих при наличии в них программных ошибок. 2003 г.
Erlang/OTP весьма уникален в плане способа обработки ошибок, который называется «деревья контроля». Говоря в общих чертах, каждых процесс в Эрланг-системах запускается и наблюдается супервизором. Когда процесс сталкивается с проблемой, он прекращает свою работу, после чего сразу же перезапускается супервизором. Что же касается самих супервизоров, то они запускаются начальным процессом, который также осуществляет их перезапуск, когда со сбоем сталкиваются уже они.
Ключевая идея состоит в том, что работа по схеме «ошибка-перезапуск» проходит быстрее по сравнению с попытками обработки ошибок. Подобное обращение со сбоями, когда надежность достигается путем отказа от решения возникшей проблемы, может показаться контринтуитивным, однако на практике метод выключения и перезапуска оказывается очень эффективным в деле подавления разовых и преходящих сбоев.
Обработка ошибок и восстановление лучше всего выполнять на внешних уровнях вашей кодовой базы. По-другому это называется принципом взаимодействий двух оконечностей. Он гласит, что обрабатывать ошибки легче на двух дальних концах связующей среды, нежели где-либо в ее середине. Это связано с тем, что даже если работа над ошибкой происходит где-то посередине, вам, так или иначе, придется делать проверку и на пограничных уровнях. Если каждому верхнему уровню все равно придется обрабатывать ошибки, то зачем же тогда делать это еще и где-то внутри программы?
Обработка ошибок — один из множества способов, с помощью которых система может оказаться тесно связанной внутри. Существует немало других примеров тесной связанности, и все же выделять какой-то один в качестве плохого было бы нечестно. Кроме IMAP.
В IMAP практически каждая операция, словно снежинка, обладает уникальными параметрами и алгоритмом обращением. Обработка ошибок становится очень неприятным процессом: ошибки могут появиться прямо посередине выполнения другой операции.
Вместо UUIDs, IMAP генерирует уникальный токен для идентификации каждого сообщения. Последний также может измениться прямо во время выполнения другой операции. Многие операции можно поделить на части. Потребовалось 25 лет для изобретения способа, который позволял бы надежно перемещать электронные письма из одной папки в другую. И, конечно, нельзя не отметить применения в нем весьма специфичных кодировок UTF-7 и base64.Нет, я ничего не выдумываю.
Для сравнения: как файловая система, так и база данных представляют собой гораздо лучшие примеры удаленного хранилища. Файловая система предлагает фиксированный набор операций, однако набор объектов, над которыми вы можете их производить, велик и весьма разнообразен. Может показаться, что интерфейс SQL обладает более широкими по сравнению с файловой системой возможностями. Тем не менее, он использует ту же схему работы: есть некоторое количество операций для работы с набором данных и огромное количество строк, над которыми эти операции проводятся. И хотя вы не всегда можете сделать замену одной базы на другую, найти решения, которые работали бы с SQL гораздо проще, чем аналогичные решения для любого кустарного языка запросов.
В качестве других примеров слабой связанности можно привести системы, использующие межплатформенное ПО или фильтры и конвейеры. Finagle твиттера использует обычный API для сервисов, и это позволяет без каких-либо лишних усилий добавить базовую обработку таймаутов, механизмов повторного соединения и проверки подлинности. И, конечно, я не могу не упомянуть в связи с этим конвейер UNIX. Это вызвало бы большое негодование.
Итак, сначала мы разделили свой код на уровни, однако теперь уже некоторые из этих уровней вместе используют один интерфейс: некий общий набор поведений и операций, пригодный для самых разных вариантов применения. Хорошими примерами слабой связанности часто оказываются однородные интерфейсы.
Правильная кодовая база совсем необязательно должна быть идеально поделена на модули. Просто модульность делает процесс написания кода гораздо более интересным. Это как детали Lego, играть с которыми интересно, потому что они подходят друг к другу. Здоровая кодовая база всегда обладает небольшим избытком функциональности, а также ровно таким расстоянием между движущимися частями, чтобы ваши руки в них не застряли.
Слабосвязанный код совсем необязательно легко удалить, однако заменить его или внести в него изменения всегда значительно легче.
Шаг 7: Продолжайте писать код
Умение писать код без оглядки на ранее написанные строки серьезно облегчает процесс экспериментирования с новыми идеями. Нет, я не говорю, что вам теперь всегда нужно стараться писать микросервисы вместо монолитов, но ваша система должна позволять вам провести один или два эксперимента поверх вашей основной работы.
Feature flags — один из способов изменить принятые ранее решения. Несмотря на то, что feature flags многими воспринимаются как способ экспериментирования с новыми возможностями, они также позволяют вам добавлять изменения без повторного развертывания новой версии.
Google Chrome — потрясающий пример положительных моментов, которые они в себе несут. Разработчики Chrome поняли, что самым сложным моментом поддержки регулярного цикла релизов была большая трата времени на объединение существующих давно feature-ветвей.
Возможность включить или выключить новый код в любой момент без его повторной компиляции позволяет разбивать крупные изменения на более мелкие слияния без нанесения ущерба существующему основному коду. Кроме того, благодаря заблаговременному появлению новых фич в той же самой кодовой базе, команда получила возможность предвидеть ситуации, когда разработка долгоживущей фичи повлияет на другие части кода.
Feature flag — непросто параметр командной строки. Это способ разделения feature-релизов от объединяемых ветвей или от основного кода. Возможность поменять свое решение прямо во время работы программы становиться все более важным в условиях, когда выпуск нового ПО может занимать часы, дни или недели. Спросите любого главного инженера по отказоустойчивости, и он скажет вам, что если система «будит» вас посреди ночи, значит она определенно должна предусматривать внесение изменений по ходу работы.
Речь в этом шаге идет не столько об итерациях как таковых, сколько о необходимости иметь петлю обратной связи. Не столько о написании многоразовых модулей, сколько о разделении компонентов для внесения в них изменений. Важно помнить, что введение изменений в основной код — это не только создание новых фич, но также и удаление старых. Писать расширяемый код — все равно что надеяться, что через три месяца с вашим проектом все будет хорошо. Написание кода, который вы сможете удалить — работа, основанная на противоположном предположении.
Стратегии, о которых я говорил выше по тексту — деление на уровни, изолирование, общие интерфейсы, композиция — призваны помочь вам не в том, чтобы написать хорошее ПО, но в том, чтобы создать такое ПО, которое способно меняться с течением времени.
«Вопрос управления, таким образом, заключается не в том, надо ли создавать пилотную систему и выбрасывать ее. Вы и так это сделаете.… Поэтому планируйте выбросить ее с самого начала; все равно так оно и получится». — Фред Брукс.
Конечно, это не значит, что вам надо выбрасывать абсолютно все, но некоторую часть удалить придется. Писать хороший код — не значит делать все правильно с первого раза. Хороший код — это просто унаследованный код, который не путается у вас под руками. И хороший код легко удалить.



