Правила программирования на Си и Си++

Решайте конкретную проблему, а не общий случай


Поучительно использовать OLE 2.0 как пример того, что случается со многими слишком сложными проектами. Имеется две главные причины сложности интерфейса OLE. Во-первых, он безуспешно пытается быть независимым от языка программирования. Идея таблицы виртуальных функций Си++ является центральной для OLE 2.0. Спецификация OLE даже пользуется нотацией классов Си++ для документирования того, как должны работать различные интерфейсы OLE. Для реализации OLE на другом языке программирования (не Си++) вы должны имитировать на этом языке таблицу виртуальных функций Си++, что фактически ограничивает ваш выбор Си++, Си или языком ассемблера (если вы не разработчик компиляторов, который может добавить к выбранному вами языку нужные свойства). Если честно, то вы должны быть сумасшедшим, чтобы программировать OLE не на Си++; потребуется гораздо меньше времени на изучение Си++, чем на написание имитатора Си++. То есть, эта идея независимости от языка программирования является неудачной. Интерфейс мог бы быть существенно упрощен за счет отказа от нее.

Возвращаясь к истории из предыдущего раздела, нужно заметить, что библиотека MFC в действительности решает проблему сложности, связанную с OLE, при помощи простого и легко понятного интерфейса, реализующего все возможности, нужные для большинства приложений OLE 2.0. Тот факт, что никто не хотел программировать с использованием OLE, пока для этого не появилась оболочка на основе MFC, впечатляет. Разработка хорошей оболочки вокруг плохого интерфейса не может быть решением лежащей в основе проблемы.

Если оболочка с использованием MFC столь проста, то почему лежащий в основе пласт так сложен? Ответ на этот вопрос является основным предметом проектирования. Создатели интерфейса OLE никогда не задавали себе два основных вопроса:

·

Какие основные возможности должно поддерживать настоящее приложение?

· Как реализовать эти возможности простейшим способом?

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


предполагается делать при помощи этого интерфейса, и получив в результате систему, которая может делать все, но при этом слишком сложная, чтобы быть пригодной для использования. (Вероятно, они совсем не пробовали реализовать этот интерфейс в каком-либо приложении, иначе они бы обнаружили эти проблемы).
Процесс объектно-ориентированного проектирования является в какой-то мере попыткой решения этой проблемы. Относительно просто добавить новую возможность в объектно-ориентированную систему или посредством наследования, или добавив новых обработчиков сообщений к существующим классам. Скрывая определения данных от пользователя класса, вы оставляете за собой право полностью менять внутреннюю организацию класса, включая определения данных, не беспокоя пользователей этого класса, при условии, что вы сохраняете его существующий интерфейс.
В структурном проектировании вам не нужна такая роскошь. Вы обычно проектируете сперва структуры данных, и модификация структуры данных является серьезным делом, потому что нужно проверить каждую подпрограмму, использующую эту структуру данных, чтобы убедиться в том, что она еще работает. Как следствие, "структурные" программы склонны иметь много ничего не делающего кода. Это потому, что кто-нибудь может захотеть воспользоваться некой возможностью в будущем. На деле многие проектировщики структурных программ горды своей способностью предсказывать направление, в котором может развиваться программа. Все это приводит к большому объему ненужной работы и программам, имеющим больший размер, чем необходимо.
Вместо того, чтобы учитывать в проекте все возможные случаи, проектируйте свой код так, чтобы он мог быть легко расширен при необходимости добавления новых возможностей. Объектно-ориентированные проекты, как правило, тут работают лучше.

Содержание раздела