Широко известно, что стоимость программного обеспечения состоит по большей части из затрат не на изначальную разработку, а на текущее сопровождение — исправление ошибок, поддержание работоспособности его подсистем, расследование отказов, адаптацию к новым платформам, модификацию под новые сценарии использования, возврат технического долга и добавление новых возможностей.
Однако, к сожалению, многие работающие над программными системами люди ненавидят сопровождение так называемых унаследованных систем — вероятно, потому, что приходится исправлять чужие ошибки либо работать с устаревшими платформами или системами, которые заставили делать то, для чего они никогда не были предназначены. Каждая унаследованная система неприятна по-своему, поэтому столь сложно дать какие-либо общие рекомендации о том, что с ними делать.
Однако можно и нужно проектировать программное обеспечение так, чтобы максимально минимизировать «головную боль» при сопровождении, а следовательно, избегать создания унаследованных систем своими руками. Отталкиваясь от этого соображения, мы уделим особое внимание трем принципам проектирования программных систем.
- УДобство эксплуатации. Облегчает обслуживающему персоналу поддержание беспрепятственной работы системы.
- Простота. Облегчает понимание системы новыми инженерами путем максимально возможного ее упрощения. (Обратите внимание: это не то же самое, что простота пользовательского интерфейса.)
- Возможность развития. Упрощает разработчикам внесение в будущем изменений в систему, адаптацию ее для непредвиденных сценариев использования при смене требований. Известна под названиями «расширяемость» (extensibility), «модифицируемость» (
modifiability
) и «пластичность» (plasticity
).
Как и в случае надежности и масштабируемости, не существует простых решений для достижения этих целей. Следует просто иметь в виду удобство эксплуатации, простоту и возможность развития при проектировании систем.
Удобство эксплуатации
Считается, что «хороший обслуживающий персонал часто может обойти ограничения плохого (или несовершенного) программного обеспечения, но хорошее программное обеспечение не способно надежно работать под управлением плохих операторов». Хотя некоторые аспекты эксплуатации можно и нужно автоматизировать, выполнение этой автоматизации и проверка правильности ее работы все равно остается прежде всего задачей обслуживающего персонала.
Данный персонал жизненно важен для бесперебойной работы программной системы. Хорошая команда операторов обычно отвечает за пункты, перечисленные ниже, и не только:
- мониторинг состояния системы и восстановление сервиса в случае его ухудшения;
- выяснение причин проблем, например, отказов системы или снижения производительности;
- поддержание актуальности программного обеспечения и платформ, включая исправления безопасности;
- отслеживание влияния различных систем друг на друга во избежание проблемных изменений до того, как они нанесут ущерб;
- предупреждение и решение возможных проблем до их возникновения (например, планирование производительности);
- введение в эксплуатацию рекомендуемых практик и инструментов для развертывания, управления конфигурацией и т. п.;
- выполнение сложных работ по сопровождению, например переноса приложения с одной платформы на другую;
- поддержание безопасности системы при изменениях в конфигурации;
- формирование процессов, которые бы обеспечили прогнозируемость операций и стабильность операционной среды;
- сохранение знаний организации о системе, несмотря на уход старых сотрудников и приход новых.
Удобство эксплуатации означает облегчение выполнения стандартных задач, благодаря чему обслуживающий персонал может сосредоточить усилия на чем-то более важном. Информационные системы способны делать многое для облегчения выполнения стандартных задач, в том числе:
- обеспечивают хороший мониторинг и предоставляют информацию о поведении системы и происходящем внутри нее во время работы;
- обеспечивают хорошую поддержку автоматизации и интеграции со стандартными утилитами;
- позволяют не зависеть от отдельных машин (благодаря чему можно отключать некоторые из них для технического обслуживания при сохранении бесперебойной работы системы в целом);
- предоставляют качественную документацию и понятную операционную модель («если я выполню действие X, то в результате произойдет действие Y»); обеспечивают разумное поведение по умолчанию, но вместе с тем и возможности для администраторов при необходимости переопределять настройки по умолчанию;
- запускают самовосстановление по мере возможности, но вместе с тем и позволяют администраторам вручную управлять состоянием системы при необходимости;
- демонстрируют предсказуемое поведение, минимизируя неожиданности.
Простота: регулируем сложность
Код небольших программных проектов может быть восхитительно простым и выразительным, но по мере роста проекта способен стать очень сложным и трудным для понимания. Подобная сложность замедляет работу над системой, еще более увеличивая стоимость сопровождения. Увязнувший в сложности проект иногда описывают как большой ком грязи .
Существуют различные возможные симптомы излишней сложности: скачкообразный рост пространства состояний, тесное сцепление модулей, запутанные зависимости, несогласованные наименования и терминология, «костыли» для решения проблем с производительностью, выделение частных случаев для обхода проблем и многое другое. В литературе об этом было сказано немало.
Когда сложность достигает уровня, сильно затрудняющего сопровождение, зачастую происходит превышение бюджетов и срыв сроков. В сложном программном обеспечении увеличивается и шанс внесения ошибок при выполнении изменений: когда разработчикам сложнее понимать систему и обсуждать ее, гораздо проще упустить какие-либо скрытые допущения, непреднамеренные последствия и неожиданные взаимодействия. И напротив, снижение сложности резко повышает удобство сопровождения ПО, а следовательно, простота должна быть основной целью создаваемых систем.
Упрощение системы не обязательно означает сокращение ее функциональности. Оно может означать также исключение побочной сложности. Мозли и Маркс определяют сложность как побочную, если она возникает вследствие конкретной реализации, а не является неотъемлемой частью решаемой программным обеспечением задачи (с точки зрения пользователей).
Один из лучших инструментов для исключения побочной сложности — абстракция. Хорошая абстракция позволяет скрыть бо'льшую часть подробностей реализации за аккуратным и понятным фасадом. Хорошую абстракцию можно также задействовать для широкого диапазона различных приложений. Такое многократное применение не только эффективнее реализации заново одного и тоже же несколько раз, но и приводит к более качественному ПО, по мере усовершенствования используемого всеми приложениями абстрактного компонента.
Например, высокоуровневые языки программирования — абстракции, скрывающие машинный код, регистры CPU и системные вызовы. SQL — тоже абстракция, скрывающая сложные структуры данных, находящиеся на диске и в оперативной памяти, конкурентные запросы от других клиентов и возникающие после фатальных сбоев несогласованности. Конечно, при создании продукта с помощью языка программирования высокого уровня мы по-прежнему используем машинный код, просто не задействуем его напрямую, поскольку абстракция языка программирования позволяет не задумываться об этом.
Однако найти хорошую абстракцию очень непросто. В сфере распределенных систем, несмотря на наличие множества хороших алгоритмов, далеко не так ясно, как объединить их в абстракции, которые бы дали возможность сохранить приемлемый уровень сложности системы.
В моем блоге мы будем отслеживать хорошие абстракции, позволяющие выделить части большой системы в четко очерченные компоненты, допускающие повторное использование.
Возможность развития: облегчаем внесение изменений
Вероятность того, что ваши системные требования навсегда останутся неизменными, стремится к нулю. Гораздо вероятнее их постоянное преобразование: будут открываться новые факты, возникать непредвиденные сценарии применения, меняться коммерческие приоритеты, пользователи будут требовать новые возможности, новые платформы — заменять старые, станут меняться правовые и нормативные требования, рост системы потребует архитектурных изменений и т. д.
В терминах организационных процессов рабочие паттерны Agile обеспечивают инфраструктуру адаптации к изменениям. Сообщество создателей Agile разработало также технические инструменты и паттерны, полезные при проектировании программного обеспечения в часто меняющейся среде, например при разработке через тестирование (test-driven development
, TDD) и рефакторинге.
Большинство обсуждений этих методов Agile ограничивается довольно небольшими, локальными масштабами (пара файлов исходного кода в одном приложении). В блоге мы займемся поиском способов ускорения адаптации на уровне больших информационных систем, вероятно состоящих из нескольких различных приложений или сервисов с различными характеристиками. Например, как бы вы провели рефакторинг архитектуры сети Twitter для компоновки домашних лент (см. статью «Описание нагрузки») с подхода 1 на подход 2?
Степень удобства модификации информационной системы и адаптации ее к меняющимся требованиям тесно связана с ее простотой и абстракциями: простые и понятные системы обычно легче менять, чем сложные. Но в силу исключительной важности этого понятия мы будем использовать другой термин для быстроты адаптации на уровне информационных систем — возможность развития (evolvability
).