пятница, 25 сентября 2015 г.

Про хороший код или антихрупкая разработка

Находясь под сильным впечатлением от "Антихрупкости" Талеба, задумался о том, что такое хороший код с практической точки зрения.

Есть разница между академическими рассуждениями о хорошем коде в вакууме и практической пользой от его "хорошести". Качество кода учебной задачи и качество программы в промышленной эксплуатации - понятия связанные, но не тождественные. Если программа выполняется ровно один раз, делает то, что от неё требовалось, и после этого её никто больше никогда не увидит - то кого волнует качество её кода? Если программа работает из раза в раз, делает свою работу и к результату не возникает нареканий, то важно ли насколько хорошо или плохо она написана? В реальности, все эпитеты по отношению к коду возникают только при необходимости его изменить, когда программа делает что-то не то (или не так). Вот тогда и слышатся многочисленные WTF, яркие, красочные метафоры об авторе и т.п.

Похоже, что говорить о качестве кода имеет смысл только в контексте потребности его изменения. Качество кода остаётся неизвестным до того момента, пока кто-то не попробует его изменить. К аналогичному понятию качества подталкивает Стив Макконнел: "Писать программу нужно так, словно её будет поддерживать маньяк, который знает ваш адрес".
Приходим к тому, что в хороший код легко вносить изменения, в нём легко находить и исправлять ошибки. Такая формулировка очень субъективна и явно зависит от того кто и что будет изменять. Поэтому дальше я пишу о том, как должен выглядеть хороший код с моей, безусловно субъективной, точки зрения. (С удовольствием прочитаю в комментариях альтернативные мнения)

На мой взгляд, многое портит наивная попытка сразу делать всё правильно, подпитываемая ложной уверенностью, что мы точно знаем что и как надо делать. В итоге (почему-то) всегда оказывается, что требования были неполны, а те что были - неверно толковались. И "правильно" сделанное решение обрастает костылями и начинает рассыпаться на каждом шагу... Для борьбы с этим недугом я предлагаю признать, что наши сегодняшние знания - это всего лишь наши сегодняшние иллюзии. Завтра им на смену придут новые, и мы сами станем другими. Хороший код - это код который изменяется вместе с нами. Готовность к изменениям - это то, что позволяет коду становится лучше. Если не задумываться о возможных изменениях, то даже построенная с применением правильных техник и паттернов программа быстро превращается в неповортливую и неустойчивую конструкцию, в которой страшно изменить даже букву.

Чтобы сохранять эту возможность, я проповедую применение двух стратегий:
1. Хорошая программа состоит из разделяемых частей, каждая часть реализует определённые аспекты поведения системы. 
Разделяемость следует трактовать как возможность переработки или замены отдельных частей без существенной переработки всей программы.
У каждой выделенной части программы (класса, функции, модуля и т.п.) должна быть ясная и понятная зона ответственности в рамках общей задачи. Определяя границы ответственности, мы тем самым оставляем себе гипотетическую возможность изменить реализацию соответствующей части, не затрагивая лишнего. Чем меньше компонент, чем меньше у него обязанностей - тем, с одной стороны, меньше шансов, что его потребуется переделывать, а с другой стороны - и заменить его гораздо проще, чем большой. Компонент с большой зоной ответственности - признак потенциальной раковой опухоли в программе, которую будет очень трудно изменить. Таких надо делить в младенчестве или явно признавать их необходимость. (В конце концов множество систем довольно сильно привязаны к определённой СУБД.)

Возможность локальной переработки и замены даёт программе возможность развиваться по законам эволюции - неудачные решения отмирают, хорошие остаются и постепенно становятся лучше. Степень детализации при выделении частей в каждом случае определяется практической целесообразностью. Опыт показывает, что искусственное разделение на раннем этапе, не подтвержденное практической полезностью, часто приводит к неудачным архитектурным решениям. Я стараюсь начинать с простого решения, из относительно небольшого количества компонентов. По мере реализации, эти компоненты естественным образом делятся и связываются так, как этого требует логика системы.

2. Компоненты системы контролируют и гарантируют корректность своего состояния.
Технически это достигается жёстким контролем корректности состояния объектов на протяжении всего цикла существования - от конструирования до разрушения. Каждый объект берет на себя контрактные обязательства по поддержанию своего состояния, а остальные объекты могут на него положиться. Автоматизированные тесты контролируют качество выполнения объектами своих обязательств.

Я всегда следовал этим правилам интуитивно. В книге Талеба я нашел объяснение, почему сочетание этих подходов действительно является выигршной стратегией. Вместе они образуют стратегию штанги: исключить большой вред + добавить вариабельность для накопления выгоды. Первый принцип дает возможность эволюционного развития, удаления неудачных решений и замены их на более подходящие. Второй - это основа надежости системы, он не дает программе развалиться по мере роста сложности. Программа, разрабатываемая по таким принципам, становится антихрупкой - она улучшается по мере внесения в неё изменений.

А что такое хороший код с вашей точки зрения?

Комментариев нет:

Отправить комментарий