Борьба со сложностью является постоянной темой в области создания программного обеспечения, которое я встречаю снова и снова. Это нечто, что я постоянно вижу в обсуждениях на всех уровнях, ну например, как много комментариев должно сопровождать методы и функции? Каково идеальное "количество" абстракции? Когда фреймворк начинает содержать "слишком много магии"? Когда в компании используется слишком много языков программирования?
Мы пытаемся избавиться от сложности, обуздать её и ищем простоты. Я думаю, что представлять себе суть вещей таким образом, — это ложный путь. Сложность обязательно должна где-то обитать.
Есть одна вещь из дисциплины построения устойчивых систем (resilience engineering), которая научила меня концепции необходимой вариативности (requisite variety) из кибернетики: только сложность может держать под контролем другую сложность.
Когда вы имеете дело с инструментами сборки, проявляются и становятся очевидными несколько моментов:
- если вы делаете инструменты сборки простыми, они не могут покрыть все странности граничных случаев, возникающих на практике
- если вы хотите обработать эти странные случаи, вам приходится отходить от соглашений и норм, которые вы бы хотели установить
- если вы хотите облегчить использование типичных случаев, правила для них должны быть созданы как в самом инструменте, так и хорошо известны пользователям, которые подгоняют структуру своих систем в соответствии с ожиданиями системы сборки
- если вы позволяете расширенное конфигурирование или поддержку скриптов, вы даёте пользователям путь для определения новых правил, которые в свою очередь должны быть распространены на остальных пользователей и инструмент сохранит совместимость с их системами
- если вы хотите сохранить инструмент простым, вы вынуждены заставить пользователей играть только по определённым правилам в пределах установленных параметров так, чтобы инструмент остался простым
- если пользовательские варианты использования не отображаются на ваше представление о простоте, пользователи будут создавать обёртки вокруг вашего инструмента, чтобы добиться решения своих задач
И этого никак не избежать. Сложность должна обитать где-то. Она всегда сопровождает разработчиков в процессе решения проблем, независимо от того, осознаёте вы это или нет.
К сожалению, если мы боремся со сложностью подобно последнему пункту в списке (мы всегда боремся с ней тем или иным образом), обёртки становятся частью "ландшафта" нашей системы. Сложность не ложится в спячку. Она становится частью общего приобретаемого опыта, и разработчики с пользователями вынуждены приспосабливаться к ней.
Они создают временные решения, когда видят несоответствия между парой конфликтующих концепций. Эта необходимая сложность может быть перемещена куда-то — назад в инструмент (или новый инструмент) — или выдавлена путём реконструкции частей. Каждое такое структурное изменение требует определённых усилий и дополнительных подгонок для того, чтобы люди увидели сложность, поняли сложность и научились справляться со сложностью. И в некоторых случаях сделанные изменения не упрощают сущности, они усложняют их создавая новые несоответствия между представлениями в головах разных людей, и это несоответствие порождает новые обёртки. Непреднамеренная сложность (accidental complexity) является просто необходимой сложностью (essential complexity), в которой проявился её возраст. Сложность невозможно избежать, она всё время меняется. Сложность должна обитать где-то.
В книге Дизайн привычных вещей, Дон Норман упоминает о концепциях "Внутренних знаний" (Knowledge in the head) и "Внешней информации" (Knowledge in the world) (подобные концепции в академическом исследовании представлены в работе Roesler & Woods Designing for Expertise). Внутренние знания — это факты и связи между ними, которые вы узнали, изучили и которые находятся в вашей памяти. Внешняя информация — это всё остальное: информация в виде текста, знаки в дизайне (вы узнаёте кнопку включения питания просто смотря на её символ и знаете, что она может быть нажата, поскольку выглядит как кнопка) и прочее. Одна нетривиальная вещь состоит в том, что интерпретация внешней информации зависит от культуры контекста, и, вдобавок, она основана на внутренних знаниях (вы знаете, что кнопка включения питания может быть нажата потому, что вы вообще знаете о кнопке в принципе).
В некотором смысле, экспертиза это обладание такими внутренними знаниями, которые позволяют лучше понимать информацию из внешнего мира.
Типичная ловушка, с которой мы сталкиваемся в процессе проектирования ПО, проявляется в фокусировании на том, насколько "простым" является для нас чтение и понимание конкретного фрагмента кода. Однако фокусирование на простоте чревато множеством затруднений, поскольку сложность не может быть удалена: она может быть только перемещена. Если вы её перемещаете из своего кода, куда она тогда денется?
Когда мы проектировали Rebar3, мы ощущали что инструмент может быть сделан простым в использовании. Отправной точкой для этой простоты являлось общее соглашение о предполагаемой структуре проектов на Erlang/OTP. Пока вы следуете этим правилам, всё отлично работает. Мы вынесли некоторую часть сложности на внешний уровень. Правила всегда были бы изучены (как мы думали), но теперь инструмент зависел от понимания и принятия всеми этих правил. В погоне за упрощением использования инструмента для разработчиков уже освоивших эти правила, мы усложнили использование для новичков и тех, кто ещё находился в процессе изучения. Другие инструменты самого разного назначения в других экосистемах — подобно нам — также выбирают свои компромиссы.
Эта ловушка скрыта и в архитектуре ПО. Когда мы перенимаем какую-то технологию, скажем, микросервисы, мы пытаемся сделать их так, что каждый сервис в отдельности был бы достаточно прост. Но как только эта простота станет чересчур ограничивающей, а ваше приложение будет стремиться вырваться за принудительные рамки простоты, оно должно развиваться куда-то. Если оно не уложится в каждый из микросервисов, то куда оно денется?
Сложность должна обитать где-то. Если вам повезло, она живёт в хорошо очерченных местах. В код, куда вы решили поместить часть сложности, в документацию, которая описывает поведение кода, в обучающие материалы для ваших инженеров. Вы выделяете место без попыток скрыть всю её. Вы создаёте способы управления сложностью. Вы знаете куда нужно смотреть, чтобы разобраться с ней, когда вам это понадобится. Если вам не так повезло и вы просто попытались притвориться, что сложности можно полностью избежать, ей будет некуда деться в этом мире. Но это не значит, что перестанет существовать.
Не имея какого-то определённого места, она расползётся по всем уголкам внутри вашей системы, и в вашем коде и в головах ваших коллег. А поскольку люди меняют интересы, перемещаются и уходят из проекта, понимание сложности разъедается со временем.
Сложность должна обитать где-то. Если вы принимаете её и отводите для неё подобающее место, соответственно ей проектируете вашу систему и отношения внутри организации, сосредотачиваетесь на приспособлении к ней, зная, что она существует, сложность может однажды стать сильной чертой вашей системы.