Принцип изолированной оптимизации
Я начал программировать, когда мне было лет 12, и моим первым языком был Паскаль. Компьютеры тогда были медленными и глупыми. Часто приходилось писать фрагменты программ на Ассемблере, чтобы заставить их работать с хоть сколько-нибудь приемлемой скоростью.
Это время приучило меня думать о производительности. Писать код, который делает лишние или бессмысленные вещи, было очень плохо, за это даже на уроках программирования оценки снижали. Так что к каждому куску программы подсознательно задавался вопрос: не зря ли я загружаю компьютер этой работой? Можно ли её избежать?
Сегодня постоянные мысли об оптимальности сильно мешают жить, увеличивают время на разработку и нередко даже снижают качество кода. Я завидую людям, которые начали программировать сравнительно недавно и сразу получили в своё распоряжение мощные компьютеры и языки высокого уровня. Когда они используют регулярные выражения, у них не возникает мысли, что это катастрофически сложная работа, и что надо по возможности её избегать. Благодаря этому у них быстрее выходят работающие продукты.
Да, фанатичная оптимизация всего и вся по-прежнему нужна в некоторых областях (иначе фиг сделаешь Айфон), просто важно понимать, что не во всех.
Сразу оговорюсь, что я не профессиональный программист, и, возможно, для многих буду звучать изобретателем велосипеда и Капитаном Очевидность. Хоть я и получил хорошее техническое образование, профессионально я занимаюсь дизайном, а программирую я для души (и не представляю, как можно это делать за деньги).
Постепенно я пришёл к подходу, который мне очень помогает в разработке моих продуктов. Наконец я его сформулировал.
Принцип изолированной оптимизации состоит в том, что любой код, отвечающий за оптимизацию, должен быть написан отдельно от кода, отвечающего за смысл. «Отдельно» означает: в другое время, в другом месте.
Допустим, я делаю сайт, и для вывода главной страницы «в лоб» нужно 18 запросов к базе. Поскольку в голове постоянные мысли о производительности, то я начинаю думать, как бы подсократить число запросов. Естественно, сразу же обнаруживаются места для оптимизации. Оказывается, что если запрос №1 вернул меньше 10 строк, то запросы 2—5 можно вообще не делать, а результаты запросов 10—12 будут отличаться от результатов запросов 7—9 на константу. Если же строк в первом запросе получилось 10, то нужно проверить, нет ли среди них строки X, потому что если она есть, то не нужны уже запросы 6 и 13 (а они как раз самые тяжёлые).
Так постепенно мой красивый и понятный код из 18 последовательных фрагментов превращается в отвратительную лапшеобразную кашу с кучей условий и проверок, цель которых — сократить число запросов всегда, когда это возможно.
Естественно, как это ни комментируй, через год или два в этом будет очень сложно разобраться. А изменить будет ещё сложнее, потому что всегда будет опасность того, что с изменившимися условиями какие-то из моих оптимизаций уже нельзя будет применять, так как они будут приводить к ошибках в данных.
Давайте применим принцип изолированной оптимизации. Напишем наш код из 18 запросов «в лоб» и наплюём на его неоптимальность. Теперь посмотрим на него со стороны. Как можно сделать, чтобы он работал быстрее, не меняя его?
Самое первое, что приходит в голову — это всё закешировать. При всей очевидности этого решения, далеко не всегда выбираешь именно его, ведь оптимизации «прямо в коде» кажутся такими нужными и важными (а с ними и кеширование, глядишь, не понадобится). Тем не менее, результат кеширования — это изоляция оптимизационной части кода от смысловой.
Кеширование, конечно, не всегда лечит. Иногда данные устаревают слишком быстро. Тогда делаем более сложную манипуляцию. Делаем все запросы к базе не напрямую, а через специальную умную функцию, которая знает обо всех особенностях данных, за счёт которых число запросов можно снизить. А наша умная функция уже решит, делать ли настоящий запрос или вернуть данные исходя из своих «знаний» и результатов других запросов. Результат, снова в том, оптимизация изолирована от смысла.
Такой код легче и писать, и поддерживать. Если нужно изменить что-то в смысловой части, то никакой мусор не мешает. Если хочется что-то оптимизировать, то, опять же, мы видим картину в целом в одном месте, а не разбросанной непонятно как по всему проекту.
Обратите ещё внимание, что я тут не борюсь с преждевременной оптимизацией, я говорю именно о том, чтобы просто отделить одно от другого. Если хочется, оптимизацию можно сделать даже до смысловой части (например, заранее спроектировать какую-то подсистему кеширования) — это не противоречит моему принципу. Главное — отдельно.