Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 13 Нестандартные типы данных 335 Не считайте, что вы не используете глобальные переменные, поместив все данные в чудовищный объект и передавая его всюду Размещение всех возможных данных в одном огромном объекте может формально удовлетворять принципу отказа от глобальных переменных, но это приводит исключительно к накладным расходам и не создает преимуществ реальной инкапсуляции. Если вы используете глобальные данные, делайте это открыто. Не пытайтесь замаскиро- вать это с помощью объектов, страдающих ожирением. Дополнительные ресурсы Далее указаны дополнительные ресурсы, в которых освеща- ются необычные типы данных: Maguire Steve. Writing Solid Code. Redmond, WA: Microsoft Press, 1993. Глава 3 содержит отличное обсуждение опасностей использования указате- лей и множество специальных советов по решению проблем с указателями. Meyers Scott. Effective C++, 2d ed. Reading, MA: Addison-Wesley, 1998; Meyers Scott. More Effective C++. Reading, MA: Addison-Wesley, 1996. Как говорится в названии, эти книги содержат большое количество советов по улучшению программ на C++, включая руководство по безопасному и эффективному использованию указателей. В част- ности, «More Effective C++» содержит отличное обсуждение вопросов управления памятью в языке C++. Контрольный список: применение необычных типов данных Структуры Используете ли вы структуры вместо отдельных переменных для организа- ции и манипуляции группами взаимосвязанных данных? Рассматривали ли вы создание класса как альтернативу использованию структуры? Глобальные данные Действительно ли все переменные объявлены локально или в области види- мости класса, если только они не обязательно должны быть глобальными? Различаются ли в соглашениях по именованию переменных локальные, классовые и глобальные данные? Документированы ли все глобальные переменные? Свободен ли код от псевдоглобальных данных — мамонтообразных объек- тов, содержащих мешанину из данных, передающихся в каждый метод? Используются ли методы доступа вместо глобальных данных? Организованы ли данные и методы доступа к ним в классы? Предоставляют ли методы доступа уровень абстракции, независимый от реализации используемого типа данных? Находятся ли все методы доступа на одном уровне абстракции? Указатели Изолированы ли операции с указателями в методах? Корректны ли обращения к указателям или они могут быть «висячими»? http://cc2e.com/1385 http://cc2e.com/1392 336 ЧАСТЬ III Переменные Проверяет ли код корректность указателей перед их использованием? Проверяется ли корректность переменной, на которую ссылается указатель, перед ее использованием? Присваивается ли указателям пустое значение после их освобождения? Использует ли код все необходимые для читабельности переменные-указа- тели? Освобождаются ли указатели в связных списках в правильном порядке? Выделяет ли программа «резервный парашют» памяти, чтобы иметь воз- можность аккуратно завершить выполнение в случае нехватки памяти? Используются ли указатели только как последнее средство, когда другие методы неприменимы? Ключевые моменты 쐽 Структуры могут помочь сделать программы менее сложными, упростить их понимание и сопровождение. 쐽 Принимая решение использовать структуру, подумайте, не будет ли класс под- ходить лучше. 쐽 Работа с указателями чревата ошибками. Обезопасьте себя, используя методы или классы для доступа к ним и практику защитного программирования. 쐽 Избегайте глобальных переменных не только потому, что они опасны, но и потому что их можно заменить чем-то лучшим. 쐽 Если вы не можете отказаться от глобальных переменных, работайте с ними через методы доступа. Эти методы предоставляют все то же и даже больше, что и глобальные переменные. ГЛАВА 13 Нестандартные типы данных 337 Часть IV ОПЕРАТОРЫ 쐽 Глава 14. Организация последовательного кода 쐽 Глава 15. Условные операторы 쐽 Глава 16. Циклы 쐽 Глава 17. Нестандартные управляющие структуры 쐽 Глава 18. Табличные методы 쐽 Глава 19. Общие вопросы управления 338 ЧАСТЬ IV Операторы Г Л А В А 1 4 Организация последовательного кода Содержание 쐽 14.1. Операторы, следующие в определенном порядке 쐽 14.2. Операторы, следующие в произвольном порядке Связанные темы 쐽 Общие вопросы управления: глава 19 쐽 Код с условными операторами: глава 15 쐽 Код с операторами цикла: глава 16 쐽 Область видимости переменных и объектов: раздел 10.4 В этой главе мы начнем рассматривать программирование не с точки зрения дан- ных, а с точки зрения выражений. Глава представляет самую простую управляю- щую логику программы: размещение выражений и их блоков в последовательном порядке. Хотя размещение последовательного кода относительно простая задача, некото- рые организационные тонкости влияют на качество, корректность, читабельность и управляемость кода. 14.1. Операторы, следующие в определенном порядке Проше всего организовать такие выражения, для которых важен порядок следо- вания. Вот пример: Пример выражений, для которых важен порядок следования (Java) data = ReadData(); results = CalculateResultsFromData( data ); PrintResults( results ); http://cc2e.com/1465 ГЛАВА 14 Организация последовательного кода 339 Если только в этом фрагменте кода не произойдет нечто непонятное, выражения должны выполняться в указанном порядке. Данные должны быть прочитаны прежде, чем результаты могут быть вычислены, а результаты должны быть вычислены прежде, чем их можно будет напечатать. Основная идея этого примера состоит в зависимостях. Третье выражение зави- сит от второго, второе — от первого. Факт зависимости одного выражения от другого в этом примере понятен из имен методов. А вот здесь зависимости менее очевидны: Пример выражений, для которых порядок следования важен, но не настолько очевиден (Java) revenue.ComputeMonthly(); revenue.ComputeQuarterly(); revenue.ComputeAnnual(); В этом случае квартальный доход вычисляется в предположении, что месячные доходы уже подсчитаны. Знание бухучета, даже в общих чертах, может вам под- сказывать, что квартальные доходы должны вычисляться перед годовыми. Это зависимость, но при простом прочтении кода она не видна. А здесь зависимости не просто не очевидны, но буквально скрыты: Пример выражений, для которых порядковые зависимости скрыты (Visual Basic) ComputeMarketingExpense ComputeSalesExpense ComputeTravelExpense ComputePersonnelExpense DisplayExpenseSummary Допустим, метод ComputeMarketingExpense() инициализирует переменные-члены класса, в которые все остальные методы помещают данные. В этом случае его нужно вызывать перед остальными методами. Как это узнать при прочтении кода? Ис- ходя из того, что вызовы методов не содержат параметров, вы могли бы предпо- ложить, что каждый из этих методов использует данные класса. Но вы не можете знать это наверняка, прочитав этот код. Если зависимости между выражениями требуют размещения их в опре- деленном порядке, требуются дополнительные действия, чтобы сделать зависимости явными. Организуйте код так, чтобы зависимости были очевидными В предыду- щем примере на Visual Basic ComputeMarketingExpense() не должен инициализи- ровать классовые переменные. Имя метода предполагает, что ComputeMarketing- Expense() работает аналогично ComputeSalesExpense(), ComputeTravelExpense() толь- ко с маркетинговыми данными, а не с данными о продажах или другими расхо- дами. То, что ComputeMarketingExpense() инициализирует переменные-члены класса, — случайность, которой следует избегать. Почему инициализация должна выпол- няться в этом методе, а не в двух других? Пока вы не сможете придумать хоро- шую причину для этого, инициализацию классовых переменных следует осуще- 340 ЧАСТЬ IV Операторы ствлять иным методом, например InitializeExpenseData(). Имя метода явно указы- вает на то, что он должен быть вызван перед другими расчетами расходов. Называйте методы так, чтобы зависимости были очевидными В при- мере на Visual Basic метод ComputeMarketingExpense() назван неправильно, посколь- ку он делает больше, чем просто вычисляет расходы на маркетинг: он еще ини- циализирует члены класса. Если вы против создания отдельного метода для ини- циализации данных, дайте по крайней мере методу ComputeMarketingExpense() имя, описывающее все выполняемые им функции. В данном случае ComputeMarke- tingExpenseAndInitializeMemberData() будет более адекватным именем. Вы можете сказать, что это имя ужасно, потому что слишком длинное. Но оно описывает то, что делает метод и вовсе не ужасно. Ужасен сам метод! Используйте параметры методов, чтобы сделать за- висимости очевидными Возвращаясь к примеру на Visual Basic, можно сказать, что, поскольку никакие данные меж- ду методами не передаются, неизвестно, используют ли эти методы одни и те же данные. Переписав код так, чтобы происходила передача дан- ных, вы сообщаете, что порядок выполнения имеет значение. Новый код может выглядеть, например, так: Пример данных, которые позволяют предположить порядковую зависимость (Visual Basic) InitializeExpenseData( expenseData ) ComputeMarketingExpense( expenseData ) ComputeSalesExpense( expenseData ) ComputeTravelExpense( expenseData ) ComputePersonnelExpense( expenseData ) DisplayExpenseSummary( expenseData ) Поскольку все методы используют expenseData, это наводит на мысль, что они могут работать с одними и теми же данными и что порядок выражений может быть важен. В этом примере лучшим подходом может быть преобразование процедур в функ- ции, которые принимают expenseData на входе и возвращают обновленное зна- чение expenseData. Это сделает наличие зависимостей в коде еще более явным. Пример данных и вызовов методов, которые указывают на порядковую зависимость (Visual Basic) expenseData = InitializeExpenseData( expenseData ) expenseData = ComputeMarketingExpense( expenseData ) expenseData = ComputeSalesExpense( expenseData ) expenseData = ComputeTravelExpense( expenseData ) expenseData = ComputePersonnelExpense( expenseData ) DisplayExpenseSummary( expenseData ) Данные могут также указывать, что порядок выполнения не имеет значения, как в этом случае: Перекрестная ссылка Об ис- пользовании методов и их па- раметров см. главу 5. ГЛАВА 14 Организация последовательного кода 341 Пример данных, которые не указывают на порядковую зависимость (Visual Basic) ComputeMarketingExpense( marketingData ) ComputeSalesExpense( salesData ) ComputeTravelExpense( travelData ) ComputePersonnelExpense( personnelData ) DisplayExpenseSummary( marketingData, salesData, travelData, personnelData ) Так как методы в первых четырех строках не имеют общих данных, код подразу- мевает, что порядок их вызова значения не имеет. Поскольку метод в пятой стро- ке использует данные каждого из первых четырех методов, вы можете предполо- жить, что его надо выполнять после всех этих методов. Документируйте неявные зависимости с помощью коммента- риев Попробуйте, во-первых, написать код без порядковых зависимо- стей, во-вторых — написать код, который делает зависимости очевидными. Если вам все еще кажется, что зависимости видны недостаточно ясно, задокумен- тируйте их. Документирование неявных зависимостей — один из аспектов доку- ментирования допущений, сделанных при кодировании, что необходимо для на- писания систем, пригодных для сопровождения и модификации. В примере на Visual Basic будет полезно поместить такие комментарии: Пример выражений, в которых порядковые зависимости скрыты, но разъясняются с помощью комментариев (Visual Basic) ‘ Рассчитываем расходы. В каждом методе используется переменная класса ’ expenseData. Метод DisplayExpenseSummary должен вызываться последним, ’ так как он зависит от данных, вычисленных другими методами. InitializeExpenseData ComputeMarketingExpense ComputeSalesExpense ComputeTravelExpense ComputePersonnelExpense DisplayExpenseSummary В этом коде не используются методики, проясняющие порядковые зависимости. Было бы лучше положиться на такие методики, а не на простые комментарии, но если вы сопровождаете код, находящийся под строгим контролем, или почему- либо не можете его улучшать, используйте документирование для компенсации недостатков кодирования. Проверяйте зависимости с помощью утверждений или кода обработки ошибок Если последовательность кода достаточно критична, вы можете исполь- зовать утверждения или статусные переменные и код обработки ошибок, чтобы за- документировать необходимый порядок. Например, в конструкторе класса вы мо- жете инициализировать член класса isExpenseDataInitialized значением false. Затем в InitializeExpenseData() вы устанавливаете isExpenseDataInitialized в true. Каждая функция, зависящая от инициализации expenseData, может проверить, установле- но ли значение isExpenseDataInitialized в true, прежде чем выполнять операции с expenseData. Если зависимости между методами глубже, вам могут потребоваться такие переменные, как isMarketingExpenseComputed, isSalesExpenseComputed и т. д. 342 ЧАСТЬ IV Операторы Этот способ требует создания новых переменных, нового кода инициализации и нового кода проверки ошибок, что увеличивает возможность добавления ошибок. Преимущества этого подхода должны сравниваться с привнесенной им сложнос- тью и увеличением вероятности появления вторичных ошибок. 14.2. Операторы, следующие в произвольном порядке Вам могут встречаться ситуации, когда кажется, что порядок выполнения несколь- ких выражений или нескольких блоков кода не имеет значения. Одно выражение не зависит от другого и логически из него не следует. Но поскольку упорядочен- ность влияет на читабельность, производительность и качество сопровождения, вы можете использовать второстепенные критерии для определения порядка сле- дования выражений или блоков кода. Главный принцип — это Принцип Схожес- ти: Располагайте взаимосвязанные действия вместе. Размещение кода для чтения сверху вниз Основная идея в том, что необходимо позволить читать программу сверху вниз, а не перескакивая с места на место. Эксперты согласны, что порядок просмотра кода сверху вниз способствует улучшению читабельности. Однако простого раз- мещения последовательности команд сверху вниз недостаточно. Если тому, кто читает ваш код, приходится просматривать всю программу в поиске необходи- мой информации, то такой код нужно реорганизовать. Рассмотрим пример: Пример плохого кода, в котором приходится перескакивать с места на место (C++) MarketingData marketingData; SalesData salesData; TravelData travelData; travelData.ComputeQuarterly(); salesData.ComputeQuarterly(); marketingData.ComputeQuarterly(); salesData.ComputeAnnual(); marketingData.ComputeAnnual(); travelData.ComputeAnnual(); salesData.Print(); travelData.Print(); marketingData.Print(); Допустим, вы хотите выяснить, как рассчитывается marketingData. Вам придется начать с последней строки и проследить все упоминания marketingData вплоть до первой строки. marketingData встречается только в нескольких других местах, но вы должны помнить, как marketingData используется в каждом случае между ГЛАВА 14 Организация последовательного кода 343 первым и последним своим упоминанием. Иначе говоря, вам придется просмот- реть и осмыслить каждую строку кода в этом фрагменте, чтобы выяснить, как вычисляется marketingData. И, разумеется, этот пример гораздо проще, чем код, встречающийся в реальных системах. Вот тот же код, но лучше организованный: Пример хорошего, последовательного кода, который читается сверху вниз (C++) MarketingData marketingData; marketingData.ComputeQuarterly(); marketingData.ComputeAnnual(); marketingData.Print(); SalesData salesData; salesData.ComputeQuarterly(); salesData.ComputeAnnual(); salesData.Print(); TravelData travelData; travelData.ComputeQuarterly(); travelData.ComputeAnnual(); travelData.Print(); Этот код лучше по нескольким причинам. Упоминания каж- дой переменной располагаются вместе — они «локализова- ны». Число строк кода, в которых объекты являются «живы- ми», невелико. И, возможно, самое важное: код теперь вы- глядит так, что его можно разбить на отдельные методы для данных по маркетингу, продажам и поездкам. Первый фраг- мент не содержал подсказки, что эта декомпозиция возможна. Группировка взаимосвязанных выражений Размещайте взаимосвязанные выражения вместе. Они мо- гут быть связаны, так как работают с одними и теми же дан- ными, выполняют схожие задачи или зависят от порядка выполнения друг друга. Есть простой способ убедиться, что взаимосвязанные выра- жения хорошо сгруппированы. Распечатайте текст вашего метода и обведите рамкой взаимосвязанные выражения. Если они хорошо упоря- дочены, вы получите картинку, похожую на рис. 14-1, где рамки не перекрываются. Перекрестная ссылка Более формальное определение «жи- вых» переменных см. в подраз- деле «Измерение времени жиз- ни переменной» раздела 10.4. Перекрестная ссылка Если вы придерживаетесь Процесса Про- граммирования с Псевдокодом, ваш код будет автоматически груп- пироваться в блоки взаимосвязан- ных выражений (см. главу 9). 344 ЧАСТЬ IV Операторы Рис. 14-1. Если код хорошо организован в группы, то рамки вокруг взаимосвязанных разделов не перекрываются; они могут быть вложенными Если выражения плохо организованы, вы получите картинку, похожую на рис. 14-2, где рамки перекрываются. Если выяснится, что перекрытие происходит, реорганизуйте ваш код, чтобы взаимосвязанные выражения были лучше сгруппированы. Рис. 14-2. Если код организован неудачно, то рамки вокруг связанных разделов пересекаются После того, как вы сгруппируете взаимосвязанные выражения, может выяснить- ся, что они сильно связаны между собой, а к предшествующему и последующему коду не имеют значимого отношения. В этом случае, возможно, следует выделить эти выражения в отдельный метод. Контрольный список: организация последовательного кода Способствует ли код выявлению зависимостей между выражениями? Способствуют ли имена методов выявлению зависимостей? Способствуют ли параметры методов выявлению зависимостей? Описывают ли комментарии такие зависимости, которые иначе не будут явными? Используются ли вспомогательные переменные для проверки последователь- ных действий в критических частях кода? Возможно ли прочтение кода сверху вниз? Сгруппированы ли вместе взаимосвязанные выражения? Перенесены ли относительно независимые группы выражений в отдельные методы? Перекрестная ссылка Об объе- динении операций над перемен- ными см. раздел 10.4. http://cc2e.com/1472 |