Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 779
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 19 Общие вопросы управления
443
// Обрабатываем транзакцию в зависимости от ее типа.
if ( transaction.Type == TransactionType_Deposit ) {
// Обрабатываем транзакцию-вклад.
if ( transaction.AccountType == AccountType_Checking ) {
if ( transaction.AccountSubType == AccountSubType_Business )
MakeBusinessCheckDep( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountSubType == AccountSubType_Personal )
MakePersonalCheckDep( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountSubType == AccountSubType_School )
MakeSchoolCheckDep( transaction.AccountNum, transaction.Amount );
}
else if ( transaction.AccountType == AccountType_Savings )
MakeSavingsDep( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountType == AccountType_DebitCard )
MakeDebitCardDep( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountType == AccountType_MoneyMarket )
MakeMoneyMarketDep( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountType == AccountType_Cd )
MakeCDDep( transaction.AccountNum, transaction.Amount );
}
else if ( transaction.Type == TransactionType_Withdrawal ) {
// Обрабатываем снятие денег.
if ( transaction.AccountType == AccountType_Checking )
MakeCheckingWithdrawal( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountType == AccountType_Savings )
MakeSavingsWithdrawal( transaction.AccountNum, transaction.Amount );
else if ( transaction.AccountType == AccountType_DebitCard )
MakeDebitCardWithdrawal( transaction.AccountNum, transaction.Amount );
}
Вот транзакция перевода — TransactionType_Transfer.
else if ( transaction.Type == TransactionType_Transfer ) {
MakeFundsTransfer(
transaction.SourceAccountType,
transaction.TargetAccountType,
transaction.AccountNum,
transaction.Amount
);
}
else {
// Обрабатываем неизвестный тип транзакции.
LogTransactionError( “Unknown Transaction Type”, transaction );
}
}
Этот код сложен, но бывает и хуже. Он имеет всего четыре уровня вложенности,
содержит комментарии и логические отступы, а его функциональная декомпози- ция достаточно адекватна, особенно для типа транзакции
TransactionType_Transfer.
И все же вы можете улучшить этот код, вынеся содержимое внутренних
if-прове- рок в отдельные методы.
>
444
ЧАСТЬ IV Операторы
Пример правильно вложенного кода после декомпозиции на методы (C++)
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись.
transaction = ReadTransaction();
// Обрабатываем транзакцию в зависимости от ее типа.
if ( transaction.Type == TransactionType_Deposit ) {
ProcessDeposit(
transaction.AccountType,
transaction.AccountSubType,
transaction.AccountNum,
transaction.Amount
);
}
else if ( transaction.Type == TransactionType_Withdrawal ) {
ProcessWithdrawal(
transaction.AccountType,
transaction.AccountNum,
transaction.Amount
);
}
else if ( transaction.Type == TransactionType_Transfer ) {
MakeFundsTransfer(
transaction.SourceAccountType,
transaction.TargetAccountType,
transaction.AccountNum,
transaction.Amount
);
}
else {
// Обрабатываем неизвестный тип транзакции.
LogTransactionError(“Unknown Transaction Type”, transaction );
}
}
Код новых методов просто был изъят из исходного фраг- мента и оформлен в виде новых методов (они здесь не по- казаны). Такой код имеет несколько преимуществ. Во-пер- вых, двухуровневая вложенность делает структуру проще и понятнее. Во-вторых, вы можете читать, исправлять и отла- живать более короткий цикл
while, помещающийся на од- ном экране — не требуется переходить между экранами или страницами напечатанного текста. В-третьих, при вынесе- нии функциональности в методы
ProcessDeposit() и ProcessWithdrawal() приобре- таются все остальные преимущества модульности. В-четвертых, теперь легко можно увидеть, что этот код может быть преобразован в оператор
case, что еще более упростит чтение:
Перекрестная ссылка Этот спо- соб функциональной декомпози- ции особенно прост, если вы из- начально строили методы по ме- тодике, описанной в главе 9.
О принципах функциональной де- композиции см. подраздел «Раз- деляй и властвуй» раздела 5.4.
1 ... 50 51 52 53 54 55 56 57 ... 104
ГЛАВА 19 Общие вопросы управления
445
Пример правильно вложенного кода после декомпозиции
и использования оператора case (C++)
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись.
transaction = ReadTransaction();
// Обрабатываем транзакцию в зависимости от ее типа.
switch ( transaction.Type ) {
case ( TransactionType_Deposit ):
ProcessDeposit(
transaction.AccountType,
transaction.AccountSubType,
transaction.AccountNum,
transaction.Amount
);
break;
case ( TransactionType_Withdrawal ):
ProcessWithdrawal(
transaction.AccountType,
transaction.AccountNum,
transaction.Amount
);
break;
case ( TransactionType_Transfer ):
MakeFundsTransfer(
transaction.SourceAccountType,
transaction.TargetAccountType,
transaction.AccountNum,
transaction.Amount
);
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError(“Unknown Transaction Type”, transaction );
break;
}
}
Используйте более объектно-ориентированный подход Самый прямой под- ход для упрощения именно этого кода в объектно-ориентированной среде состоит в создании абстрактного базового класса
Transaction и его подклассов Deposit,
Withdrawal и Transfer.
446
ЧАСТЬ IV Операторы
Пример хорошего кода, использующего полиморфизм (C++)
TransactionData transactionData;
Transaction *transaction;
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись.
transactionData = ReadTransaction();
// Создаем объект транзакции в зависимости от ее типа.
switch ( transactionData.Type ) {
case ( TransactionType_Deposit ):
transaction = new Deposit( transactionData );
break;
case ( TransactionType_Withdrawal ):
transaction = new Withdrawal( transactionData );
break;
case ( TransactionType_Transfer ):
transaction = new Transfer( transactionData );
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError(“Unknown Transaction Type”, transactionData );
return;
}
transaction->Complete();
delete transaction;
}
В системах любого размера оператор
switch можно заменить вызовом специаль- ного метода фабрики объекта, который может повторно использоваться в любом месте, где нужно создать объект типа
Transaction. Если бы этот код принадлежал такой системе, то он мог бы стать еще проще:
Пример хорошего кода, использующего
полиморфизм и фабрику объекта (C++)
TransactionData transactionData;
Transaction *transaction;
while ( !TransactionsComplete() ) {
// Читаем транзакционную запись и выполняем транзакцию.
transactionData = ReadTransaction();
transaction = TransactionFactory.Create( transactionData );
transaction->Complete();
delete transaction;
}
ГЛАВА 19 Общие вопросы управления
447
Чтобы вам было понятно, код метода
TransactionFactory-
.Create() представляет собой простую адаптацию операто- ра
switch из предыдущего примера:
Пример хорошего кода для фабрики объекта (C++)
Transaction *TransactionFactory::Create(
TransactionData transactionData
) {
// Создаем объект транзакции на основе ее типа.
switch ( transactionData.Type ) {
case ( TransactionType_Deposit ):
return new Deposit( transactionData );
break;
case ( TransactionType_Withdrawal ):
return new Withdrawal( transactionData );
break;
case ( TransactionType_Transfer ):
return new Transfer( transactionData );
break;
default:
// Обрабатываем неизвестный тип транзакции.
LogTransactionError( “Unknown Transaction Type”, transactionData );
return NULL;
}
}
Перепроектируйте глубоко вложенный код Некоторые эксперты утверждают,
что операторы
case в объектно-ориентированном программировании практически всегда сигнализируют о плохо факторизованном коде и должны использоваться как можно реже (Meyer, 1997). И показанное преобразование из операторов
case,
вызывающих методы, к фабрике объекта с вызовами полиморфных методов — один из таких примеров.
Обобщая, можно сказать, что сложный код — это признак того, что вы недоста- точно хорошо понимаете свою программу, чтобы сделать ее простой. Глубокая вложенность — это знак, предупреждающий о том, что нужно добавить вызов метода или перепроектировать сложную часть кода. Это не значит, что вы обяза- ны переписать весь метод, но у вас должна быть веская причина не делать этого.
Сводка методик уменьшения глубины вложенности
Далее перечислены способы, позволяющие уменьшить вложенность. Рядом ука- заны ссылки на разделы этой книги, в которых эти способы обсуждаются:
쐽
повторная проверка части условия (этот раздел);
쐽
конвертирование в блоки
if-then-else (этот раздел);
Перекрестная ссылка Дополни- тельные полезные улучшения кода наподобие этого см. в гла- ве 24.
448
ЧАСТЬ IV Операторы
쐽
преобразование к оператору
case (этот раздел);
쐽
факторизация глубоко вложенного кода в отдельный метод (этот раздел);
쐽
использование объектной и полиморфной диспетчеризации (этот раздел);
쐽
изменение кода с целью использования статусной переменной (раздел 17.3);
쐽
использование сторожевых операторов для выхода из метода и пояснения номинального хода алгоритма (раздел 17.1);
쐽
использование исключений (раздел 8.4);
쐽
полное перепроектирование глубоко вложенного кода (этот раздел).
19.5. Основа программирования:
структурное программирование
Термин «структурное программирование» был введен в исторической статье «Struc- tured Programming», представленной Эдсжером Дейкстрой на конференции НАТО
по разработке ПО в 1969 году (Dijkstra, 1969). С тех самых пор термин «структур- ный» применялся к любой деятельности в области разработки ПО, включая струк- турный анализ, структурный дизайн и структурное валяние дурака. Различные структурные методики не имели между собой ничего общего, кроме того, что все они создавались в то время, когда слово «структурный» придавало им большую значимость.
Суть структурного программирования состоит в простой идее: программа должна использовать управляющие конструкции с одним входом и одним выходом. Такая конструкция представляет собой блок кода, в котором есть только одно место, где он может начинаться, и одно — где может заканчиваться. У него нет других входов и выходов. Структурное программирование — это не то же самое, что и структур- ное проектирование сверху вниз. Оно относится только к уровню кодирования.
Структурная программа пишется в упорядоченной, дисциплинированной мане- ре и не содержит непредсказуемых переходов с места на место. Вы можете чи- тать ее сверху вниз, и практически так же она выполняется. Менее дисциплини- рованные подходы приводят к такому исходному коду, который содержит менее понятную и удобную для чтения картину того, как программа выполняется. Меньшая читабельность означает худшее понимание и в конце концов худшее качество программы.
Главные концепции структурного программирования, касающиеся вопросов исполь- зования
break, continue, throw, catch, return и других тем, применимы до сих пор.
Три компонента структурного программирования
В следующих разделах описаны три конструкции, составляющие основу структур- ного программирования.
Последовательность
Последовательность — это набор операторов, выполняющих- ся по порядку. Типичные последовательные операторы содер- жат присваивания и вызовы методов. Вот два примера:
Перекрестная ссылка Об ис- пользовании последовательно- стей см. главу 14.
ГЛАВА 19 Общие вопросы управления
449
Примеры последовательного кода (Java)
// Последовательность операторов присваивания.
a = “1”;
b = “2”;
c = “3”;
// Последовательность вызовов методов.
System.out.println( a );
System.out.println( b );
System.out.println( c );
Выбор
Выбор — это такая управляющая конструкция, которая зас- тавляет операторы выполняться избирательно. Наиболее час- тый пример — выражение
if-then-else. Выполняется либо блок
if-then, либо else, но не оба сразу. Один из блоков «выбирается» для выполнения.
Оператор
case — другой пример управляющего элемента выбора. Оператор switch
в C++ и Java, оператор
select — все это примеры case. В каждом случае для выпол- нения выбирается один из вариантов. Концептуально операторы
if и case похо- жи. Если ваш язык не поддерживает операторы
case, вы можете эмулировать их с помощью набора
if. Вот два примера выбора:
Пример выбора (Java)
// Выбор в операторе if.
if ( totalAmount > 0.0 ) {
// Делаем что-то.
}
else {
// Делаем что-то еще.
}
// Выбор в операторе case.
switch ( commandShortcutLetter ) {
case ‘a’:
PrintAnnualReport();
break;
case ‘q’:
PrintQuarterlyReport();
break;
case ‘s’:
PrintSummaryReport();
break;
default:
DisplayInternalError( “Internal Error 905: Call customer support.” );
}
Перекрестная ссылка Об исполь- зовании выбора см. главу 15.
450
ЧАСТЬ IV Операторы
Итерация
Итерация — это управляющая структура, которая заставля- ет группу операторов выполняться несколько раз. Итерацию обычно называют «циклом». К итерациям относятся струк- туры
For-Next в Visual Basic и while и for в C++ и Java. Этот фрагмент кода содержит примеры итераций на Visual Basic:
Примеры итераций на Visual Basic
‘ Пример итерации в виде цикла For.
For index = first To last
DoSomething( index )
Next
’ Пример итерации в виде цикла while.
index = first
While ( index <= last )
DoSomething ( index )
index = index + 1
Wend
’ Пример итерации в виде цикла с выходом.
index = first
Do
If ( index > last ) Then Exit Do
DoSomething ( index )
index = index + 1
Loop
Основной тезис структурного программирования гласит, что любая управляющая логика программы может быть реализована с помощью трех конструкций: пос- ледовательности, выбора и итерации (B
ö
hm Jacopini, 1966). Программисты иног- да предпочитают языковые конструкции, увеличивающие удобство, но програм- мирование, похоже, развивается во многом благодаря ограничению того, что мы можем делать на наших языках программирования. До введения структурного про- граммирования использовать
goto представлялось очень удобным, но код, напи- санный таким образом, оказался малопонятным и не поддающимся сопровожде- нию. Я считаю, что использование любых управляющих структур, отличных от этих трех стандартных конструкций, т. е.
break, continue, return, throw-catch и т. д., дол- жны рассматриваться под критическим углом зрения.
19.6. Управляющие структуры и сложность
Одна из причин, по которой столько внимания уделялось управляющим структу- рам, заключается в том, что они вносят большой вклад в общую сложность про- граммы. Неправильное применение управляющих структур увеличивает сложность,
правильное — уменьшает ее.
Перекрестная ссылка Об ис- пользовании итераций см. гла- ву 16.