Разработка
Григорий Петров: Предварительная оптимизация: инкарнация зла
Что же не так с предварительной оптимизацией? Разве не логично сразу озаботиться тем, чтобы программа не тормозила, занимала мало памяти и была готова к портированию на Windows Phone?
[button color=4d61d7 icon=arrow-left-2 url=https://apptractor.ru/develop/grigoriy-petrov-zabudte-slovo-oshibka.html] Забудьте слово «ошибка» [/button]
В последних нескольких статьях я много рассказывал про социальные аспекты управления разработкой. Пора вернуться к коду! В этой статье речь пойдет о предварительной оптимизации: попытке угадать, где тормозит код.
Что такое «предварительная оптимизация»? Это такая игра, в которую очень любят играть разработчики. Заключается она в следующем. В процессе написания программы разработчик «выполняет» ее в своем воображении и пытается понять есть ли в ее архитектуре фундаментальные недостатки. К таким недостаткам часто относят скорость выполнения кода: неудачно написанная игра способна показать полтора кадра в секунду на крутейшем компьютере, а веб страница — дергаться и «лагать» просто при скролле. Но скорость выполнения — далеко не единственный недостаток, который программисты любят искать. Популярностью пользуются такие недостатки как большой объем занимаемой памяти, длительное время запуска или завершения работы, возможность быстро адаптировать программу для другой операционной системы — и множество других критериев.
Осмотрев создаваемую программу мысленным взором и найдя в ней один или несколько недостатков, программист самоотверженно бросается их исправлять. Но как можно исправить несуществующий недостаток в еще не написанной программе? Правильно — написав программу таким образом, чтобы этого недостатка в ней не было. Предварительная оптимизация — это исправление ошибок до того, как они появились на свет.
На первый взгляд, такой подход довольно логичен. Ведь когда программист только создает программу, ее сложность минимальна. Программа целиком умещается «в голове» разработчика, она еще не вырвалась за пределы Кошелька Миллера и менять ее так же просто, как фигурку из пластилина.
Корень зла
Как это обычно бывает, здравый смысл и житейская мудрость не очень хорошо работают со сложными вещами. Предварительная оптимизация не работает потому, что программисты очень плохо предсказывают будущее. Мы сильно переоцениваем свою возможность выполнить минимально сложную программу в воображении. Я сам много раз попадал в эту ловушку, когда при взгляде на код мне было абсолютно ясно что «вот здесь оно будет тормозить — надо бы исправить». А через пару месяцев и десяток переписываний запущенная программа тормозила совсем в другом месте, о котором на момент ее написания я и подумать не мог. И так почти каждый раз. Из года в год. Мы просто не угадываем.
Если бы предварительная оптимизация только отнимала время на разработку, я бы не стал называть ее «злом». «Зло» — это сильное слово. Обычно такой ярлык вешают на страшные и неприятные вещи, способные сгубить перспективные проекты, разрушить мечты и оставить семьи без средств к существованию. Разве может простое желание разработчика «сделать как лучше» привести к серьезным бедам?
Поверьте, может. Беда предварительной оптимизации не в том, что она чаще всего бесполезна. Беда в том, что за попытку предсказать будущее программист платит огромную цену: он увеличивает сложность программы.
Оптимизация — она не бесплатна. Когда более-менее квалифицированный программист решает задачу, то в большинстве случаев первое решение — оно самое простое. Любая попытка «сэкономить память», «сделать побыстрее» и «добавить гибкости в архитектуру» — это увеличение сложности создаваемой программы. Которое, скорее всего, никогда не пригодится. Причем часто «оптимизированная» версия не просто сложнее — она намного сложнее. Я видел множество ситуаций, когда в погоне за «оптимизацией» разработчики превращали несколько строк кода в сотни и тысячи, доводя сложность до такого состояния, когда через месяц сами на могли понять свой код.
А про сложность я уже писал. Страшная штука.
Преждевременная пессимизация
Многие разработчики считают, что отказ от оптимизации — это значит писать заведомо медленные и «плохие» программы. Но ключевая проблема преждевременной оптимизации описывается словом преждевременная. После того как написана минимально работающая версии программы — ничто не мешает провести ее профилирование. Которое покажет где программа тормозит на самом деле. Если тормозит.
Также я часто сталкиваюсь с тем, что отсутствие преждевременной оптимизации путают с преждевременной пессимизацией. Пессимизацией называют написание программы заведомо неправильным способом, «благодаря» чему она начинает потреблять неадекватное количество памяти или тормозить в простейших ситуациях. На практике все подобные случаи, которые я видел, происходят из-за недостаточной квалификации разработчика. Выбор неправильных структур данных, незнание каких-то базовых свойство используемой операционной системы или фрймворка, непонимание принципов работы с сетью и файловой системой — все это может привести к написанию крайне неэффективно работающего кода.
Одна из задач многострадального team lead — проверять код разработчиков на «общую адекватность» и не допускать таких ситуаций. Разработка программ — очень молодая область, и программисты часто могут не знать каких-то базовых вещей. Это нормально, с этим можно и нужно работать. Главное, на мой взгляд — знать обо всех закономерностях разработки и умение применять эти знания в работе.
И, конечно же, помнить, что разработка — она очень разная. Иногда оптимизация программы является одной из важных составляющих работы, к примеру, при создании игр или драйверов. В таких случаях задача team lead — организовать рабочий процесс таким образом, чтобы разработчики оптимизировали код не по результатам его выполнения в своем воображении, а старались делать как можно больше тестов и прототипов. Мы не можем устранить ограничения Кошелька Миллера, но можем успешно его обходить, если разбивать работу на достаточно мелкие части и не переоценивать собственные когнитивные возможности.