для того, чтобы программист работал лучше и достигал более высоких результатов. Пожалуйста, не забывайте об этом. Какие принципы мы можем сформулировать, чтобы помочь программисту создавать наилучшее программное обеспечение с наименьшими затратами энергии? Мы уже выражали наше мнение по всей книге, поэтому этот раздел по существу представляет собой резюме.
Основная причина, побуждающая нас создавать хорошую структуру кода, — стремление вносить в него изменения без излишних усилий. Чем лучше структура, тем легче изменить код, найти и исправить ошибку, добавить новое свойство, настроиться на новую архитектуру, повысить быстродействие программы и т.д. Именно это мы имеем в виду, говоря “хорошо”.
В оставшейся части раздела мы рассмотрим следующие вопросы.
• Что мы хотим от кода?
• Два общих подхода к разработке программного обеспечения, сочетание которых обеспечивает лучший результат, чем использование по отдельности.
• Ключевые аспекты структуры программ, выраженные в коде.
• Непосредственное выражение идей.
• Уровень абстракции.
• Модульность.
• Логичность и минимализм.
Идеалы должны воплощаться в жизнь. Они являются основой для размышлений, а не просто забавными фразами, которыми перекидываются менеджеры и эксперты. Наши программы должны приближаться к идеалу. Когда мы заходим в тупик, то возвращаемся назад, чтобы увидеть, не является ли наша проблема следствием отступления от принципов (иногда это помогает). Когда мы оцениваем программу (желательно еще до ее поставки пользователям), мы ищем нарушение принципов, которые в будущем могут вызвать проблемы. Применяйте идеалы как можно чаще, но помните, что практичные концепции (например, производительность и простота), а также слабости языка (ни один язык не является совершенным) часто позволяют лишь достаточно близко приблизиться к идеалу, но не достичь его.
Идеалы могут помочь нам принять конкретные технические решения. Например, мы не можем принять решение о выборе интерфейса для библиотеки самолично и в полной изоляции (см. раздел 14.1). В результате может возникнуть путаница. Вместо этого мы должны вспомнить о нашем первом принципе и решить, что именно является важным для данной конкретной библиотеки, а затем создать логичный набор интерфейсов. А главное — следовало бы сформулировать принципы проектирования и принятия компромиссных решений для каждого проекта в его документации и прокомментировать их в коде.
Начиная проект, обдумайте принципы и посмотрите, как они связаны с задачами и ранее существующими решениями вашей задачи. Это хороший способ выявления и уточнения идей. Когда позднее, на этапе проектирования и программирования, вы зайдете в тупик, вернитесь назад и найдите место, где ваш код отклонился от идеалов, — именно там, вероятнее всего, кроются ошибки и возникают проблемы, связанные с проектированием. Этот подход является альтернативой методу отладки, принятому по умолчанию, когда программист постоянно проверяет одно и то же место с помощью одного и того же метода поиска ошибок. “Ошибка всегда кроется там, где вы ее не ожидаете, — или вы ее уже нашли”.
22.1.2.1. Чего мы хотим?
Как правило, мы хотим следующего.
• Правильность. Да, очень трудно определить, что мы имеем в виду под словом “правильный”, но это важная часть работы. Часто это понятие в рамках конкретного проекта определяют для нас другие люди, но в этом случае мы должны интерпретировать то, что они говорят.
• Легкость сопровождения. Любая успешная программа со временем изменяется; она настраивается на новое аппаратное обеспечение и платформу, дополняется новыми возможностями и при этом из нее необходимо удалить новые ошибки. В следующих разделах мы покажем, как структура программы позволяет достичь этого.
• Производительность. Производительность (эффективность) — понятие относительное. Она должна быть адекватной цели программы. Часто программисты утверждают, что эффективный код по необходимости должен быть низкоуровневым, а высокоуровневая структура ухудшает эффективность программы. В противоположность этому мы считаем, что следование рекомендуемым нами принципам часто позволяет обеспечивать высокую эффективность кода. Примером такого кода является библиотека STL, которая одновременно является абстрактной и очень эффективной. Низкая производительность часто может быть следствием как чрезмерного увлечения низкоуровневыми деталями, так и пренебрежения ими.
• Своевременная поставка. Поставка совершенной программы на год позже запланированного срока — не слишком хорошее событие. Очевидно, что люди хотят невозможного, но мы должны создать качественное программное обеспечение за разумный срок. Бытует миф, утверждающий, что законченная в срок программа не может быть высококачественной. В противоположность этому мы считаем, что упор на хорошую структуру (например, управление ресурсами, инварианты и проект интерфейса), ориентация на тестирование и использование подходящих библиотек (часто разработанных для конкретных приложений или предметных областей) позволяют полностью уложиться в сроки.
Все сказанное стимулирует наш интерес к структуре кода.
• Если в программе есть ошибка (каждая большая программа содержит ошибки), то найти ее легче, если программа имеет четкую структуру.
• Если программу необходимо объяснить постороннему или как-то модифицировать, то четкую структуру понять намного легче, чем мешанину низкоуровневых деталей.
• Если программа испытывает проблемы с производительностью, то настроить высокоуровневую программу, как правило, намного легче (поскольку она точнее соответствует общим принципам и имеет хорошо определенную структуру), чем низкоуровневую. Для начинающих программистов высокоуровневая структура намного понятнее. Кроме того, высокоуровневый код намного легче тестировать и настраивать, чем низкоуровневый.
Программа обязательно должна быть понятной. Хорошим считается все, что помогает нам понимать программу и размышлять о ней. В принципе порядок лучше беспорядка, если только порядок не является результатом чрезмерного упрощения.
22.1.2.2. Общие подходы
Существуют два подхода к созданию правильного программного обеспечения.
• Снизу–вверх. Система компонуется только из составляющих частей, правильность которых уже доказана.
• Сверху–вниз. Система компонуется из составляющих частей, предположительно содержащих ошибки, а затем вылавливаются все ошибки.
Интересно, что наиболее надежные системы созданы с помощью сочетания обоих подходов, хотя они очевидным образом противоречат друг другу. Причина проста: для крупных реальных систем ни один из этих подходов не гарантирует требуемой правильности, адаптируемости и удобства сопровождения.
• Мы не можем создать и проверить основные компоненты, заранее устранив все источники ошибок.
• Мы не можем полностью компенсировать недостатки основных компонентов (библиотек, подсистем, иерархий классов и т.д.), объединив их в законченную систему.
Однако сочетание этих двух подходов может дать больше, чем каждый из них по отдельности: мы можем создать (или позаимствовать, или приобрести) компоненты, имеющие достаточно высокое качество, так что остальные проблемы можно