Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 799
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
176
ЧАСТЬ II Высококачественный код
Пример явной идентификации параметров (Visual Basic)
Private Function Distance3d( _
Объявления формальных параметров.
ByVal xDistance As Coordinate, _
ByVal yDistance As Coordinate, _
ByVal zDistance As Coordinate _
)
End Function
Private Function Velocity( _
ByVal latitude as Coordinate, _
ByVal longitude as Coordinate, _
ByVal elevation as Coordinate _
)
Сопоставление фактических параметров с формальными.
Distance = Distance3d( xDistance := latitude, yDistance := longitude, _
zDistance := elevation )
End Function
Данный подход особенно полезен при использовании длинных списков параметров одинакового типа, потому что в этом случае вероятность неправильного сопо- ставления параметров более высока, а компилятор эту ошибку определить не мо- жет. Во многих средах явное сопоставление параметров может оказаться пальбой из пушки по воробьям, но в средах, от которых зависит безопасность людей, или других средах с повышенными требованиями к надежности дополнительный спо- соб гарантии правильного сопоставления параметров не помешает.
Убедитесь, что фактические параметры соответствуют формаль-
ным Формальные параметры, известные также как «фиктивные параметры» (dum- my parameters), — это переменные, объявленные в определении метода. Факти- ческими параметрами называют переменные, константы или выражения, на са- мом деле передаваемые в метод.
По невнимательности довольно часто передают в метод переменную неверного типа — например, целое число вместо числа с плавающей запятой. (Эта пробле- ма характерна только для слабо типизированных языков, таких как C, при исполь- зовании неполного набора предупреждений компилятора. Строго типизирован- ные языки, такие как C++ и Java, не имеют этого недостатка.) Если аргументы яв- ляются исключительно входными, это редко становится проблемой: обычно ком- пилятор при вызове метода преобразует фактический тип в формальный. Если это приводит к проблеме, компилятор обычно генерирует предупреждение. Но иногда,
особенно если аргумент является и входным, и выходным, передача аргумента неверного типа может привести к серьезным последствиям.
>
>
1 ... 19 20 21 22 23 24 25 26 ... 104
ГЛАВА 7 Высококачественные методы
177
Постарайтесь всегда проверять типы аргументов в списках параметров и внима- тельно изучайте предупреждения компилятора о несоответствии типов параметров.
7.6. Отдельные соображения
по использованию функций
Современные языки, такие как C++, Java и Visual Basic, поддерживают и функции,
и процедуры. Функция — это метод, возвращающий значение; процедуры значе- ний не возвращают. В C++ все методы обычно называют «функциями», однако, с точки зрения семантики, функция, «возвращающая»
void, является процедурой.
Различие между функциями и процедурами выражено в семантике не слабее, чем в синтаксисе, и именно семантике следует уделять наибольшее внимание.
Когда использовать функцию, а когда процедуру?
Пуристы утверждают, что функция должна возвращать только одно значение по- добно математической функции. В этом случае все функции принимали бы толь- ко входные параметры и возвращали единственное значение традиционным пу- тем. Функции всегда назывались бы в соответствии с возвращаемым значением:
sin(), CustomerID(), ScreenHeight() и т. д. Процедуры, с другой стороны, могли бы принимать входные, изменяемые и выходные параметры в любом количестве.
Довольно часто можно встретить функцию, работающую как процедура, но воз- вращающую при этом код статуса. Логически она является процедурой, но из-за возврата значения официально ее следует называть функцией. Так, объект
report
мог бы иметь метод
FormatOutput(), используемый подобным образом:
if ( report.FormatOutput( formattedReport ) = Success ) then ...
В этом примере метод
report.FormatOutput() работает как процедура в том смыс- ле, что он имеет входной параметр
formattedReport, но технически это функция,
потому что сам метод тоже возвращает значение. В защиту этого подхода вы мог- ли бы сказать, что возвращаемое функцией значение не имеет отношения ни к главной цели функции (форматированию вывода), ни к имени метода (
report.-
FormatOutput()). В этом смысле метод больше похож на процедуру, пусть даже технически он является функцией. Если возвращаемое значение служит для определения успеха или неудачи выполнения процедуры согласованно, это не вызывает замешательства.
Альтернативный подход — создание процедуры, принимающей переменную ста- туса в качестве явного параметра, например:
report.FormatOutput( formattedReport, outputStatus )
if ( outputStatus = Success ) then ...
Я предпочитаю именно этот вариант, но не потому, что трепетно отношусь к раз- личию между функциями и процедурами, а потому, что такой код ясно разделяет вызов метода и проверку переменной статуса. Объединение вызова и проверки в одной строке увеличивает «плотность» команды, а значит, и ее сложность. Следу- ющий вариант использования функции также хорош:
178
ЧАСТЬ II Высококачественный код outputStatus = report.FormatOutput( formattedReport )
if ( outputStatus = Success ) then ...
Словом, используйте функцию, если основная цель метода — возврат значения, указанного в имени функции. Иначе применяйте процедуру.
Возврат значения из функции
Использование функции сопряжено с риском того, что функция возвратит некор- ректное значение. Обычно это объясняется наличием нескольких путей выпол- нения функции, один из которых не устанавливает возвращаемого значения. Следуя моим советам, вы сведете этот риск к минимуму.
Проверяйте все возможные пути возврата Создав функцию, проработай- те в уме каждый возможный путь ее выполнения, дабы убедиться, что функция воз- вращает значение во всех возможных обстоятельствах. Целесообразно инициа- лизировать возвращаемое значение в начале функции значением по умолчанию:
это будет страховкой на тот случай, если функция не установит корректное воз- вращаемое значение.
Не возвращайте ссылки или указатели на локальные данные Как только выполнение метода завершается и локальные данные выходят из области види- мости, ссылки и указатели на локальные данные становятся некорректными. Если объект должен возвращать информацию о своих внутренних данных, пусть он сохранит ее в форме данных — членов класса. Реализуйте для него функции до- ступа, возвращающие данные-члены, а не ссылки или указатели на локальные данные.
7.7. Методы-макросы и встраиваемые методы
С методами-макросами связаны некоторые уникальные со- ображения. Следующие правила и примеры рассматриваются в контексте препроцессора C++. Если вы используете дру- гой язык или препроцессор, адаптируйте правила к своей ситуации.
Разрабатывая макрос, заключайте в скобки все, что
можно Так как макросы и их аргументы расширяются в код, следите за тем, чтобы они расширялись так, как вам нужно. Одну частую проблему иллюстрирует сле- дующий макрос:
Пример макроса, который расширяется неверно (C++)
#define Cube( a ) a*a*a
Если вы передадите в этот макрос неатомарное значение
a, он выполнит умноже- ние неверно. Так, выражение
Cube( x+1 ) расширится в x+1 * x + 1 * x + 1, что из-за приоритета операций умножения и сложения приведет к получению ошибочного результата. Вот улучшенная, но все еще не совсем правильная версия этого макроса:
Перекрестная ссылка Даже если ваш язык не поддерживает пре- процессор макросов, вы може- те создать собственный препро- цессор (см. раздел 30.5).
ГЛАВА 7 Высококачественные методы
179
Пример макроса, который все еще расширяется неверно (C++)
#define Cube( a ) (a)*(a)*(a)
Цель уже близка. Однако, если вы используете макрос
Cube() в выражении, включа- ющем операции с более высоким приоритетом, чем умножение, выражение
(a)*(a)*(a)
будет вычислено неверно. Что делать? Заключите в скобки все выражение:
Пример макроса, с которым все в порядке (C++)
#define Cube( a ) ((a)*(a)*(a))
Заключайте макрос, включающий несколько команд, в фигурные скобки
Макрос может включать несколько команд, что может привести к проблемам, если вы будете рассматривать их как единый блок, например:
Пример неправильного макроса, состоящего
из нескольких команд (C++)
#define LookupEntry( key, index ) \
index = (key - 10) / 5; \
index = min( index, MAX_INDEX ); \
index = max( index, MIN_INDEX );
for ( entryCount = 0; entryCount < numEntries; entryCount++ )
LookupEntry( entryCount, tableIndex[ entryCount ] );
Этот макрос работает не так, как работал бы обычный метод: единственной час- тью макроса, выполняемой в цикле
for, является первая строка:
index = (key - 10) / 5;
Чтобы устранить эту проблему, заключите макрос в фигурные скобки:
Пример правильного макроса, состоящего из нескольких команд (C++)
#define LookupEntry( key, index ) { \
index = (key - 10) / 5; \
index = min( index, MAX_INDEX ); \
index = max( index, MIN_INDEX ); \
}
Замена вызовов методов макросами обычно считается рискованным и малопонят- ным (короче, плохим) подходом, так что используйте его только при необходи- мости.
Называйте макросы, расширяющиеся в код подобно методам, так, что-
бы при необходимости их можно было заменить методами Конвенция именования макросов в C++ подразумевает использование только заглавных букв.
Если же макрос может быть заменен методом, называйте его в соответствии с конвенцией именования методов. Это позволит вам заменять макросы на методы и наоборот, не изменяя остального кода.
180
ЧАСТЬ II Высококачественный код
Следование этой рекомендации связано с риском. Если вы часто используете операции ++ и –– ради их побочных эффектов (в составе других выражений), то,
принимая макросы за методы, вы столкнетесь с неприятностями. Это еще одна причина избегать побочных эффектов.
Ограничения использования методов-макросов
Современные языки вроде C++ поддерживают много альтернатив макросам:
쐽
ключевое слово
const для объявления констант;
쐽
ключевое слово
inline для определения функций, которые будут компилиро- ваться как встраиваемый код;
쐽
шаблоны для безопасного в плане типов определения стандартных операций,
таких как
min, max и т. д.;
쐽
ключевое слово
enum для определения перечислений;
쐽
директиву
typedef для простых замен одного типа другим.
Бьерн Страуструп, создатель C++, пишет: «Макрос почти всегда указыва- ет на недостаток языка программирования, программы или программи- ста… Если вы используете макросы, значит, вам не хватает возможностей отладчиков, инструментов, генерирующих перекрестные ссылки, средств профи- лирования и т. д.» (Stroustrup, 1997). Макросы полезны для выполнения условной компиляции (см. раздел 8.6), но добросовестные программисты обычно исполь- зуют макросы вместо методов только в крайнем случае.
Встраиваемые методы
Язык C++ поддерживает ключевое слово
inline, служащее для определения встра- иваемых методов. Иначе говоря, программист может разрабатывать код как ме- тод, но во время компиляции компилятор постарается встроить каждый экземп- ляр метода прямо в код. Теоретически встраивание методов может повысить бы- стродействие кода, позволяя избежать затрат, связанных с вызовами методов.
Не злоупотребляйте встраиваемыми методами Встраиваемые методы на- рушают инкапсуляцию, потому что C++ требует, чтобы программист поместил код встраиваемого метода в заголовочный файл, доступный остальным программистам.
При встраивании метода каждый его вызов заменяется на полный код метода, что во всех случаях увеличивает объем кода и само по себе может создать проблемы.
Практическое применение встраивания аналогично применению прочих мето- дик повышения быстродействия кода: профилируйте код и оценивайте результа- ты. Если ожидаемое повышение быстродействия не оправдывает забот, связанных с профилированием, нужным для проверки выгоды, оно не оправдывает и сни- жения качества кода.
Контрольный список: высококачественные методы
Общие вопросы
Достаточна ли причина создания метода?
Все ли части метода, которые целесообразно поместить в отдельные методы, сделаны отдельными методами?
http://cc2e.com/0792
ГЛАВА 7 Высококачественные методы
181
Имеет ли имя процедуры вид «выразительный глагол +
объект»? Описывает ли имя функции возвращаемое из нее значение?
Описывает ли имя метода все выполняемые в методе действия?
Задали ли вы конвенции именования часто выполняе- мых операций?
Имеет ли метод высокую функциональную связность?
Решает ли он только одну задачу и хорошо ли он с ней справляется?
Имеют ли методы слабое сопряжение? Являются ли связи метода с други- ми методами малочисленными, детальными, заметными и гибкими?
Обусловлена ли длина метода его ролью и логикой, а не искусственным стандартом кодирования?
Передача параметров
Формирует ли в целом список параметров метода согласованную абстрак- цию интерфейса?
Разумно ли упорядочены параметры метода? Соответствует ли их порядок порядку параметров аналогичных методов?
Документированы ли выраженные в интерфейсе предположения?
Метод имеет семь параметров или меньше?
Все ли входные параметры используются?
Все ли выходные параметры используются?
Не используются ли входные параметры в качестве рабочих переменных?
Если метод является функцией, возвращает ли он корректное значение во всех возможных случаях?
Ключевые моменты
쐽
Самая важная, но далеко не единственная причина создания методов — улуч- шение интеллектуальной управляемости программы. Сокращение кода — не такая уж и важная причина; повышение его удобочитаемости, надежности и облегчение его изменения куда важнее.
쐽
Иногда огромную выгоду можно извлечь, создав отдельный метод для простой операции.
쐽
Связность методов можно разделить на несколько видов. Самая лучшая — функ- циональная — достижима практически всегда.
쐽
Имя метода является признаком его качества. Плохое, но точное имя часто указывает на плохое проектирование метода. Плохое и неточное имя не опи- сывает роль метода. Как бы то ни было, плохое имя предполагает, что программу нужно изменить.
쐽
Функцию следует использовать, только когда главной целью метода является возврат конкретного значения, описываемого именем функции.
쐽
Добросовестные программисты используют методы-макросы с осторожностью и только в крайнем случае.
Перекрестная ссылка Этот кон- трольный список позволяет определить качество методов.
Вопросы, касающиеся этапов создания метода, приведены в контрольном списке «Процесс
Программирования Псевдокода»
(глава 9).