Делай самое простое, что может сработать
При проектировании программных систем делай самое простое, что может сработать.
Удивительно, как далеко можно зайти с этим советом. Я искренне думаю, что можно делать это постоянно. Можно следовать этому подходу при исправлении багов, при поддержке существующих систем и при архитектуре новых.
Многие инженеры проектируют, пытаясь придумать “идеальную” систему: что-то хорошо факторизованное, почти бесконечно масштабируемое, элегантно распределенное и так далее. Я думаю, что это совершенно неправильный подход к проектированию ПО. Вместо этого потратьте время на глубокое понимание текущей системы, а затем делайте самое простое, что может сработать.
Простое может разочаровывать
Системное проектирование требует компетенции со множеством различных инструментов: серверы приложений, прокси, базы данных, кеши, очереди и так далее. По мере освоения этих инструментов младшие инженеры естественно хотят их использовать. Весело создавать системы из множества различных компонентов! И очень приятно рисовать коробки и стрелки на доске - как будто занимаешься настоящей инженерией.
Однако, как и во многих навыках, истинное мастерство часто включает в себя изучение того, когда делать меньше, а не больше. Бой между амбициозным новичком и старым мастером - это избитое клише в фильмах боевых искусств: новичок - это размытое движение, сальто и вращения. Мастер в основном неподвижен. Но почему-то атаки новичка никогда не достигают цели, а итоговая атака мастера решающая.
В программном обеспечении это означает, что великое проектирование ПО выглядит неубедительно. Кажется, что вообще ничего особенного не происходит. Можно понять, что находишься в присутствии великого проектирования ПО, потому что начинаешь думать что-то вроде “о, я не понимал, что проблема была такой простой” или “о, мило, тебе действительно не нужно делать ничего сложного”.
Unicorn - это великое проектирование ПО, потому что он обеспечивает все самые важные гарантии в веб-сервере (изоляция запросов, горизонтальное масштабирование, восстановление после сбоев), опираясь на примитивы Unix1. Стандартный для индустрии Rails REST API - это великое проектирование ПО, потому что он дает вам именно то, что нужно для создания веб-API с минимальной конфигурацией.
Что не так с тем, чтобы делать самое простое?
Существует несколько возражений против принципа “делай самое простое, что может сработать”, которые я регулярно слышу.
“Но это просто создает большие комки грязи”
Первое возражение заключается в том, что следование этому совету приведет к созданию кодовых баз, состоящих из хаков и быстрых исправлений. “Конечно”, - говорят критики, - “твой код будет работать, но он будет беспорядочным и его будет трудно поддерживать”.
Я думаю, что это возражение основано на неправильном понимании того, что означает “простое”. Хаки и быстрые исправления на самом деле не просты. Они добавляют сложность, вводя дополнительные вещи, о которых разработчики должны помнить (“о, да, это не работает, если параметр X установлен в Y” или “убедитесь, что вы никогда не вызываете эту функцию из этого контекста”).
Истинная простота требует тщательного рассмотрения множественных подходов и глубокого понимания всей кодовой базы. Простое решение - это то, которое элегантно вписывается в существующую архитектуру, не требуя специальных случаев или обходных путей.
“Но что такое простота?”
Это справедливый вопрос. Простота может быть субъективной, и то, что кажется простым одному разработчику, может показаться сложным другому.
Вот мое приблизительное определение простоты в контексте программных систем:
Меньше движущихся частей для размышления: Простая система имеет меньше компонентов, меньше абстракций и меньше слоев косвенности.
Меньше внутренней связанности: В простой системе изменения в одной части не требуют каскадных изменений в других частях.
Когда я не уверен, какое из двух решений проще, я использую этот тайбрейкер: простые системы стабильны. Если одно решение требует постоянного обслуживания, исправлений и настроек, а другое просто работает месяцами без внимания, второе проще.
“Но разве ты не хочешь быть масштабируемым?”
Это возражение обычно принимает форму: “Конечно, твое простое решение работает сейчас, но что произойдет, когда у тебя будет 10 миллионов пользователей?”
У меня есть несколько ответов на это:
Во-первых, ты не можешь точно предсказать узкие места в массивном масштабе. Я видел бесчисленное количество систем, которые были чрезмерно спроектированы для масштаба, который они никогда не достигли, или которые масштабировались неправильным образом для реальной нагрузки, с которой они столкнулись.
Во-вторых, чрезмерная инженерия для масштаба делает кодовые базы негибкими. Когда у тебя есть сложная распределенная система с множеством движущихся частей, становится намного труднее вносить изменения. Ты торгуешь скоростью разработки на теоретическую масштабируемость.
В-третьих, большинство систем на самом деле не нуждаются в сложных распределенных архитектурах. Современное оборудование невероятно мощное. Одна хорошо настроенная машина может обрабатывать огромные объемы трафика. Даже если тебе в конечном итоге понадобится несколько машин, простая архитектура с балансировщиком нагрузки часто достаточна.
Практический пример: ограничение скорости
Давайте рассмотрим конкретный пример. Скажем, тебе нужно добавить ограничение скорости в твой API. Вот несколько подходов, которые ты мог бы рассмотреть:
- Самое простое: Отслеживание в памяти с простым словарем/хэш-картой
- Более сложное: Redis с алгоритмом скользящего окна
- Еще более сложное: Распределенное ограничение скорости с согласованностью между узлами
Для большинства приложений вариант 1 совершенно адекватен. Он прост в реализации, прост в понимании и прост в отладке. Да, он не будет работать, если у тебя несколько серверов, но если у тебя несколько серверов, ты, вероятно, уже используешь балансировщик нагрузки или прокси, который может выполнять ограничение скорости за тебя.
Только если у тебя есть конкретные требования, которые делают простое решение неработоспособным, тебе следует переходить к более сложным вариантам.
Заключительные мысли
Существует два способа подхода к разработке программного обеспечения:
- Попытаться предсказать будущие требования и проектировать для них
- Проектировать для текущих требований и доверять своей способности адаптироваться
Я твердо верю во второй подход. Он приводит к более простым системам, более быстрой разработке и, как это ни парадоксально, часто к лучшей долгосрочной масштабируемости (потому что простые системы легче понять и изменить).
В следующий раз, когда ты столкнешься с проблемой проектирования, сопротивляйся желанию показать все свои навыки инженерии. Вместо этого спроси себя: что самое простое, что может сработать?
Эта фраза была впервые сформулирована Уордом Каннингемом, но популяризирована в более широком контексте экстремального программирования (XP) Кентом Беком. Спасибо им обоим за эту вечную мудрость.
Оригинал: Do the simplest thing that could possibly work by Sean Goedecke
использует модель fork Unix для достижения изоляции процессов. Главный процесс порождает рабочих, каждый из которых обрабатывает один запрос за раз. Если рабочий процесс падает, главный процесс просто порождает нового. Это невероятно просто и невероятно надежно.
Для тех, кто не знаком с Unicorn: это веб-сервер Ruby, который ↩