Мост, который нам не нужен
Представьте: вы сидите на встрече стартапа. Три инженера. Осталось две недели работы. Продукт ещё не проверен на востребованность. И кто-то — всегда найдётся кто-то — говорит: «Нам, наверное, стоит настроить архитектуру микросервисов с оркестрацией Kubernetes, реализовать очередь сообщений, добавить сервисную сетку и спроектировать её для 100 миллионов одновременных пользователей». Ваше чутье подсказывает, что что-то не так. Вы правы. Это эпидемия избыточной инженерии, и она убивает больше продуктов, чем спасает. Я наблюдал, как блестящие инженеры строили архитектурные памятники собственному техническому мастерству, пока бизнес медленно кровоточил. Я видел, как команды тратили месяцы на оптимизацию баз данных, обрабатывающих тысячу запросов в день. Я был свидетелем полной переработки рабочего кода, потому что кто-то решил, что первоначальный подход «недостаточно элегантен». Вот неудобная правда, которую никто не хочет слышать: ваш продукт не проваливается, потому что ваш код недостаточно изощрён. Он проваливается, потому что вы не выпустили его достаточно быстро, чтобы узнать, хочет ли кто-то его вообще.
Что такое избыточная инженерия?
Избыточная инженерия — это не про то, чтобы строить что-то хорошо. Это про то, чтобы строить что-то слишком хорошо для того, что нужно прямо сейчас. Это разница между необходимой сложностью и ненужной сложностью. Необходимая сложность? Это когда вы решаете действительно сложные задачи. Это ожидаемо и часто красиво. Избыточная инженерия — это решение воображаемых проблем. Построение масштабируемости, когда у вас нет пользователей. Оптимизация производительности для нагрузок, которые вы никогда не увидите. Проектирование систем, достаточно гибких для каждой возможной будущей функции, которую вы можете реализовать когда-нибудь. Это решение для киноверсии вашей проблемы, а не для реальной проблемы.
Почему умные люди создают глупые системы
Психология избыточной инженерии увлекательна, потому что она коренится не в злобе или глупости, а в страхе и амбициях. Страх перед беспорядком Инженеры ненавидят устаревший код. Мысль о том, чтобы застрять в запутанной кодовой базе, действительно пугает. Поэтому, начиная с чистого листа, возникает желание построить это «правильно на этот раз» — с идеальными абстракциями, разделением задач и элегантными шаблонами проектирования. Ирония? Эти идеальные системы становятся устаревшим кодом быстрее, чем кто-либо ожидает, за исключением того, что теперь это устаревший код, который никто не понимает, потому что он был чрезмерно абстрактным до смерти. Проблема уверенности Некоторые разработчики усвоили, что сложность равна изощрённости. Построение простых решений кажется им ниже их достоинства. Монолит, работающий на одном сервере? Это для любителей. Микросервисы, управляемые через пользовательскую инфраструктуру? Вот это инженерия. За исключением того, что когда изменение кнопки занимает шесть месяцев, это перестаёт казаться изощрённым. Организационный импульс В сфере технологий существует коварная культурная проблема: мы поощряем сложность. Сложные решения сигнализируют о технической зрелости и привлекают более умных людей. Более простые решения отвергаются как «немасштабируемые» или «не соответствующие требованиям предприятия». Организации, усвоившие эту ловушку, в конечном итоге создают решения, которые впечатляют других инженеров, а не обслуживают реальных клиентов. История о вложенных затратах Есть также проблема повествования. Мы говорим себе, что «строим фундамент правильно» или «думаем о долгосрочной перспективе», хотя на самом деле мы просто беспокоимся о техническом долге. Худший вариант этого — когда команды реализуют сложные системы, чтобы «избежать необходимости переписывать позже». Но вот в чём дело: вы всё равно перепишете это, потому что неправильно поняли требования. Единственная разница в том, что вы потратили месяцы вместо недель на построение лесов для здания, которое не будет построено.
Математика неудачи
Давайте поговорим о реальных затратах, потому что они не теоретические — они разрушительны.
Время разработки
Каждый день, который вы тратите на оптимизацию масштаба, которого у вас нет, — это день, когда вы не проверяете свою бизнес-модель. На ранних стадиях это катастрофично. Healthcare.gov потратил месяцы на разработку системы, которая вышла из строя при запуске, потому что сложность ввела больше режимов сбоя, чем предотвратила. Переписывание Netscape Mozilla было настолько избыточным, что отдало рынок браузеров Internet Explorer.
Бремя обслуживания
Простой код легко модифицировать. Сложный код требует специалистов. По мере роста сложности она растёт экспоненциально. Простой монолит может быть понятен новому разработчику за несколько дней. Система микросервисов с пользовательскими сервисными сетками и распределённой журналистикой? Это месяц обучения, прежде чем они смогут что-либо изменить. Когда вы тратите деньги на зарплаты разработчиков, ожидая, что люди поймут вашу систему достаточно хорошо, чтобы быть полезными, вы уже проиграли.
Скорость итераций
Здесь избыточная инженерия становится экзистенциальной. Соответствие продукта рынку требует быстрых итераций. Вам нужно постоянно что-то менять. Вам нужно убирать функции, которые не работают, и удваивать ставки на те, которые работают. Чем сложнее ваша система, тем медленнее вы можете итерировать. Монолит, развёрнутый за секунды, позволяет вам экспериментировать. Архитектура микросервисов со своим конвейером развёртывания? Это ограничение вашей способности к изменениям.
Как распознать это до того, как оно убьёт вас
Вот практический чек-лист. Используйте его, прежде чем добавлять новый уровень абстракции: Тест «Почему?» Прежде чем что-либо реализовывать, ответьте на вопрос: «Почему?». И задайте ещё четыре вопроса «Почему?». Если вы строите что-то для будущей гибкости, спросите, почему вы думаете, что вам понадобится эта гибкость. Если ответ «потому что это может пригодиться когда-нибудь», вы нашли избыточную инженерию. Тест пользователя Решает ли эта функция или архитектурное решение непосредственную проблему ваших текущих пользователей? Если ответ требует более одного предложения для объяснения, вероятно, нет. Тест простоты Есть ли более простой способ решить эту задачу? Не худший способ. Не более дешёвый способ. Более простой способ. Если есть, и вы выбираете сложный подход, вы занимаетесь избыточной инженерией. Тест переписывания Представьте, что вам нужно объяснить эту систему кому-то новому. Сколько времени это займёт? Если больше 30 минут, возможно, вы перешли грань сложности, которая пока не нужна.
Реальные примеры из практики
Позвольте мне показать вам, как это выглядит на практике.
Стартап, который погиб из-за избыточной архитектуры
Я знал команду, которая создавала инструмент для управления проектами. Три основателя, 500 тысяч долларов начального финансирования. Они потратили два месяца на создание архитектуры микросервисов с отдельными сервисами для пользователей, проектов, задач, уведомлений, отчётности и поиска. У каждого свой собственный базы данных. Шесть месяцев спустя у них была система, которая теоретически могла масштабироваться до миллионов пользователей. На самом деле у них было 200 пользователей, и их самая большая проблема заключалась в том, что служба уведомлений постоянно выходила из строя, потому что они перемудрили с системой публикации событий. Им нужен был монолит. Всего один. Работающий на одном сервере. Всё, что они создали, могло бы быть одной кодовой базой, развёрнутой как единое целое. Вместо этого они отлаживали сбои распределённой системы, которых не должно было быть. Продукт погиб не от технического долга, а от накладных расходов на обслуживание «решения» технического долга, которого у них никогда не было.
Переписывание «Давайте сделаем это правильно на этот раз»
Другая команда, за которой я наблюдал, переписала весь свой бэкенд с Node.js на Go, потому что «Node недостаточно производителен». Их реальная проблема была не в производительности — у них было около 2000 активных пользователей в день, и запросы к базе данных были медленными. Переписывание заняло восемь месяцев. К тому времени, когда они выпустили продукт, они упустили окно рынка. Надлежащий индекс базы данных решил бы их проблему за день.
Когда сложность действительно необходима
Я должен быть честен. Бывают моменты, когда сложность оправдана, и я не хочу поощрять наивное упрощение. Реальные проблемы масштаба требуют реальных решений. Если у вас 100 миллионов пользователей и ваша база данных тает, вы не можете больше работать с монолитом. Twitter выбрал микросервисы не потому, что они казались модными — они выбрали их, потому что одна база данных буквально не может обработать такой объём. Некоторые области действительно сложны. Финансовые системы, обработка платежей, приложения с критическими требованиями безопасности — у них есть врождённая сложность, которую трудно избежать. Вы не занимаетесь избыточной инженерией, когда проявляете должную осторожность. Технический долг реален. Иногда нужно провести рефакторинг. Иногда нужно изменить архитектуру. Просто убедитесь, что вы делаете это, потому что столкнулись с реальной стеной, а не потому, что беспокоитесь о будущем. Ключ в том, чтобы решать проблемы, с которыми вы сталкиваетесь сейчас, и проектировать с возможностью рефакторинга в будущем.
Как сохранить здравомыслие: практические стратегии
Стратегия 1: Начните с скучного
Это лучший совет, который я могу дать. Сначала создайте максимально скучное возможное решение. Вот как это выглядит:
# Скучный подход к сервису пользователей
from flask import Flask, request, jsonify
import sqlite3
app = Flask(__name__)
@app.route('/users', methods=['POST'])
def create_user():
data = request.json
conn = sqlite3.
