Книга: Site Reliability Engineering. Надежность и безотказность как в Google
Назад: 8. Технологии выпуска ПО
Дальше: Часть III. Практики

9. Простота

Автор — Макс Люббе

Под редакцией Тима Харви

Цена надежности — гонка за предельной простотой.

Ч-А. Хоар, лекция на премии Тьюринга

Программное обеспечение по своей природе динамично и нестабильно. Оно может быть полностью стабильно только в том случае, если находится в вакууме. Если мы перестанем изменять исходный код, мы перестанем создавать ошибки. Если аппаратная часть или библиотеки никогда не изменятся, ни один компонент не будет вызывать ошибки. Если мы заморозим текущую базу пользователей, нам никогда не придется масштабировать систему. Фактически описать подход SR-инженеров к управлению системами можно такой фразой: «В конечном счете наша задача заключается в том, чтобы поддерживать баланс между гибкостью и стабильностью».

Стабильность или гибкость?

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

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

«Скучность» как добродетель

Когда речь идет о программном обеспечении, его «скучность» является достоинством. Нам не столько важно, чтобы наши программы были спонтанными и интересными, сколько важно, чтобы они работали по сценарию и предсказуемо решали свои бизнес-задачи. Как сказал инженер компании Google Роберт Мут: «В отличие от детективной истории, желательно, чтобы исходный код не давал повода для волнения, беспокойства и загадок». Сюрпризы на производстве — злейшие враги SR-инженеров.

Как предполагает в своем эссе No Silver Bullet Фред Брукс [Brooks, 1995], очень важно понимать разницу между сложностью органичной (естественной) и неорганичной (случайной). Органичная сложность — это сложность, свойственная заданной ситуации, ее нельзя избежать по определению, а неорганичная сложность более гибкая, от нее можно избавиться, приложив некоторые усилия. Например, при создании веб-сервера необходимо учитывать органичную сложность задачи быстрого формирования веб-страниц. Однако если мы пишем код веб-сервера на языке Java, то можем создать неорганичную сложность, попытавшись минимизировать влияние сборки мусора на производительность.

Нацелившись на минимизацию неорганичной сложности, команды SR-инженеров должны делать следующее:

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

• постоянно стремиться избавляться от сложности в системах, с которыми они работают.

Не отдам свой код!

Поскольку инженеры — обычные люди, они могут эмоционально привязываться к своим творениям, и поэтому нередки конфликты из-за того, что были удалены крупные фрагменты кода. Некоторые могут протестовать: «Что, если этот код понадобится нам в будущем?», «Почему бы нам просто не закомментировать код, чтобы мы могли вновь добавить его позднее?» или «Почему бы нам не пропустить код, пометив его флагом, вместо того чтобы удалять?». Все эти возражения безосновательны. Системы контроля версий позволяют легко откатить изменения, а сотни строк закомментированного кода лишь отвлекают и сбивают с толку разработчиков (особенно по мере увеличения файлов исходного текстов). Код, который никогда не выполняется, помечен флагом и всегда отключен, похож скорее на бомбу замедленного действия, что на своем печальном опыте прочувствовала, например, компания Knight Capital (см. Order In the Matter of Knight Capital Americas LLC [Securities…, 2013]).

Не хочу утрировать, но, когда вы задумываетесь о создании веб-сервиса, который должен быть доступен в режиме 24/7, в какой-то мере вы несете ответственность за каждую новую строку кода. SR-инженеры должны использовать приемы, гарантирующие, что весь код следует исходной цели. К числу таких приемов можно отнести, например, следующее:

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

• регулярное удаление «мертвого» кода;

• внедрение методов, позволяющих обнаружить чрезмерное увеличение объема кода («разбухание») на всех уровнях тестирования.

Показатель «Отрицательные строки кода»

Термин «разбухание ПО» был введен для описания тенденции, в рамках которой с течением времени программное обеспечение увеличивается в объеме и заме­дляется из-за постоянного добавления дополнительной функциональности. Хотя «разбухшее» ПО кажется нежелательным даже интуитивно, его недостатки становятся особенно заметны, если посмотреть на них с точки зрения SR-инженера. Каждая новая или измененная строка кода проекта создает угрозу появления новых дефектов и ошибок. Небольшой проект проще понять, протестировать, и в нем зачастую меньше дефектов. Учитывая такую точку зрения, мы должны дополнительно проверять код, когда нам нужно срочно добавить в проект новую функциональность. Я был свидетелем того, как удалялись тысячи строк кода, утратившего актуальность.

Минимальные API

Французский писатель Антуан де Сент-Экзюпери написал: «…совершенство достигается не тогда, когда уже нечего прибавить, но когда уже ничего нельзя отнять» [Saint Exupery, 1939]. Этот же принцип применим и к разработке ПО. API — особенно яркий пример того, почему нужно следовать этому правилу.

Написание понятных, четких API — это существенный аспект в достижении простоты программного обеспечения. Чем меньше методов и аргументов мы предоставляем пользователям API, тем проще будет понять этот API и тем больше усилий мы сможем приложить для совершенствования данных методов. Опять же сознательный отказ устранять некоторые проблемы позволит нам сосредоточиться на основной задаче и улучшить решения, выполнение которых мы явно откладывали. В области ПО «меньше» значит «больше»! Небольшой простой API также является признаком хорошо проработанного проекта.

Модульность

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

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

По мере увеличения сложности системы разделение обязанностей между API и исполняемыми файлами становится все более важным. Это прямая аналогия с проектированием классов в объектно-ориентированном программировании. Вы прекрасно понимаете, что создание класса-«солянки», который содержит несвязанные функции, — плохой прием. Так вот, плохим приемом считается также и создание и передача в промышленную эксплуатацию исполняемого файла util или misc. Хорошо спроектированная распределенная система содержит взаимодействующие объекты, каждый из которых имеет четкую цель.

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

Простота релизов

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

Итоги главы

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

Зачастую это верно для сложных систем вообще; см. [Perrow, 1999] и [Cook, 2000].

Фраза придумана моим бывшим менеджером Йоханом Андерсоном примерно в то время, когда я стал SR-инженером.

Протокол сериализации, называемый также протокольными буферами (protobuffs), — это независимый от языка и платформы расширяемый механизм для сериализации структурированных данных. Для получения более подробной информации см. .

Назад: 8. Технологии выпуска ПО
Дальше: Часть III. Практики