Navigation

Искусство программирования под Unix (и не только). Часть шестая, «правило кодоэкономии»

Я продолжаю цикл статей, посвященных некоторым простым правилам разработки под Unix «по версии Эрика Реймонда», которые, по моему глубочайшему убеждению, могут быть распространены на любые другие операционные системы. Я уже рассказывал в первых трех частях о правилах модульности, ясности, композиции, разделения и простоты. Сегодня дело дошло до шестого правила —




Правило кодоэкономии: разрабатывайте большие программы только при наличии объективных причин это делать



Есть такое поколение программистов, которых я называю write-only-программистами. Это такие люди, которые любят создавать, но не успевают осмыслить. Они могут за очень короткое время написать вполне работающего монстра, но через неделю уже затрудняются что-то в нем изменить. Особенно, если изменение не мелочное. Правило кодоэкономии перекликается с другими правилами Реймонда, даже более того, оно неразрывно связано с ними. Модуль, решающий одну, хорошо сформулированную задачу, должен быть небольшим по объему. Если это не получается, значит нужно подумать, а нельзя ли разбить одну задачу на несколько более мелких? В оригинальной трактовке данного правила есть слова ‘by demonstration’, что означает, что вы должны быть способны доказать своим коллегам по цеху, что разбивать дальше нельзя или нецелесообразно.



Разработка сразу большого объема кода в короткое время вызвана зачастую не только желанием инвестора получить продукт скорее (хотя это самая распространенная причина). Большей бедой является излишний перфекционизм, приложенный к неправильному месту. Программистам, завершившим первую версию продукта, становится «скучно» заниматься рутинной поддержкой и вялым развитием и исправлением собственных ошибок, потому что они уже прекрасно понимают, что они сделали «не так» с самого начала. И они начинают все «с нуля». Обычно это преподносится инвесторам как благо. На самом деле, это часто путь, приводящий к еще большим проблемам. Не всегда приводящий, но отрицательных примеров много даже среди именитых брендов.
Известно, что «читать» код значительно сложнее, чем его писать. Чем меньше человек в команде, чем лучше и согласованнее выходит продукт, потому что коммуникаций меньше, а следовательно, мнений и «разного почерка». Но добиться того, чтобы этот продукт был поддерживаем другими, при таком подходе непросто. Потому что «не барское это дело» — писать технические документации и интерфейсы администрирования (табличка «SARCASM»).



Из собственного опыта я могу вспомнить разработку объектного языка программирования ArtPublishing. Мы создавали его на C/С++, взамен существующей реализации языка шаблонов на Perl. Последний не устраивал нас в первую очередь по производительности. В итоге из языка шаблонов получился язык, на котором строились довольно сложные сайты — интернет-магазины, сообщества, сложные корпоративные сайты, интранет-системы и, самое интересное, полноценная система управления контентом. С самого начала мы понимали, что получится монстр и делать его нужно правильно. В разработке интерпретатора языка самое главное — заложить запас на развитие, ведь оно обещает быть очень интенсивным. Где-то нам это удалось, где-то — нет. Например, неверный выбор хранения строк («сишный», с 00 в конце) сделал очень дорогой доработку под использование UTF-8. С другой стороны, использование внешних подключаемых модулей и API сделало возможным развитие системы, не затрагивая т.н. «ядро», так как на нем работало параллельно сразу несколько сайтов для разных клиентов.



Чтобы быстрее отдать язык в тестовую эксплуатацию, мы сначала полностью повторили синтаксис библиотеки Template для Perl, и переключили обработку шаблонов на C++. Убедились, что все заработало на примере проекта chernobil.ru, а потом стали наращивать язык всякими полезными конструкциями, которых не было в Perl, оставляя обратную совместимость. Фактически, первый прогон системы был на одних «заглушках», которые, одна за одной заменялись на работоспособные модули. Если кому интересно, довольно убогая документация на язык есть здесь. Кстати, немного отвлекаясь от темы, были совсем близки к выпуску транслятора в C++. Представьте себе сайт, скомпилированный в бинарный код в форме одного запускаемого exe-шника и набора dll-модулей (в юниксе, соответственно, исполняемого файла и so-модулей)? То есть, интерпретатор для отладки и транслятор с компилятором для публикации. Для 2001–2002 годов было бы довольно интересное решение, но, увы, время уже убежало.
Язык, к сожалению, был чудовищный по синтаксису — но этого было не избежать, надо было обратную совместимость с Perl-версией выдерживать. Вот на что был похож, например, функционал одного из модулей форума:


&{../templates/header()};
&{init()};
&{#message_id=param('id')};
&{#message_parent="&&{../papa(#message_id)};"};
&{#tree="&&{../tree_message(#message_parent)};"};
<!--%if{:'&{#message_id};' ne '':}-->
<!--%repeat source="sql_uprate(#message_id)"--><!--%/repeat-->
<!--%repeat source="sql_message(#message_id,#approved_check)"-->
        &{#message_answer=../templates/message_answer()};
        &{#author="&f{#author};"};
        &{#title="&f{#title};"};
        <!--%if{:'&{#user_mode};' eq 'admin':}-->&{#admin_message=../templates/admin_message()};<!--%/if-->
        <!--%if{:'&{#email};' ne '':}-->&{#author="<a href='mailto:&{#email};'>&{#author};"};<!--%/if-->
        <!--%repeat source="sql_forum(#forum_id)"-->
                &{#path="&{../templates/path()};"};
        <!--%/repeat-->
        &{../templates/message()};
<!--%/repeat-->
<!--%/if-->
&{../templates/footer()};

Но при всем, порой, ужасном стиле программирования на этом языке, соблюдалась одна важная особенность, без которой на нем программировать просто невозможно. Это необходимость мыслить модульно. Редкий файл с логикой внутри, типа изображенного выше, не превышал по размерам экранную страничку. У него всегда был интерфейс, позволяющий использовать его где-то еще. Если к поддержке продукта подключался новый человек, он всегда мог запустить на отладку маленький кусочек модуля и посмотреть, как он работает.



В итоге, возвращаясь к теме, вот мои рекомендации по разработке больших систем:
  • Система должна собираться из «кирпичиков», каждый из которых должен быть максимально «обособленным».«Кирпичики» должны образовывать иерархию. В идеале, должна быть возможность заменить «кирпичики» «заглушками», иммитирующими их работу для как можно более раннего тестового прогона.
  • Для работы над «кирпичиками» можно нанимать довольно большую группу программистов, а над архитектурой проекта — максимально компактную команду профессионалов. Техническое управление разработкой «кирпичиков» и ответственная приемка должны быть на этой команде. На разработку интерфейсов между «кирпичиками» и на всю архитектуру в целом определенно уйдет время, которое при других подходах часто экономят. Это самая большая ошибка при разработке больших систем.
  • Документирование работы «кирпичиков» должно быть максимально компактным: что на входе, что на выходе и как работает в двух словах, плюс несколько важных моментов, которые могут быть непонятны из кода. Из документов гораздо более важной является схема объединения «кирпичиков» в систему. Она должна быть построена так, чтобы можно было за пять минут объяснить, как работает программа.
  • Каждый модуль должен поставляться с набором тестов, демонстрирующих его работу в соответствии с требованиями. Критерий приемки модуля в работу — тесты проходят, документация короткая, понятная и соответствует тестам, удовлетворительный и «читабельный» код.
« Ранее: правило простоты Правило прозрачности »