Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
278
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

с 300

Итоги

Часть II ^ Обьектно-ориентированное г1рогра1^^ирование на 0+"^

в данной-^^лаве рассмотрены функции С-(-+ как основной инструмент построе­ ния программ. Язык C+ + , развиваюн^ий возможности С, является уникальным языком программирования — он требует от программиста включения прототипов функций, применяемых в каждом исходном файле. Данное правило поддерживает раздельную компиляцию и упрощает управление сложными проектами, но создает дополнительные проблемы для разработчиков ПО и сопровождающих его про­ граммистов.

Передача параметров — сложная технология C++ . Программисту приходится координировать написанный для функции код в четырех местах: в клиенте (вызов функции), в заголовке функции-сервера, в ее прототипе и в теле. Это трудная задача, и иногда возникают ошибки, вызывающие различного рода проблемы.

Передача параметров по значению относительно проста, но не поддерживает изменения фактических аргументов. Передача параметров по указателю поддер­ живает побочные эффекты в клиентском коде, но сложна и ведет к ошибкам. C + + унаследовал эти два режима передачи параметров из языка С. Чтобы свести к минимуму ошибки, в C + + сделана попытка использовать передачу параметров по указателю как можно реже. Это достигается с помощью еще одного режима — передачи по ссылке. Он кажется хорошим компромиссом, но даже передача по ссылке приводит к некоторому непониманию терминологии и путанице в обозна­ чениях.

Для структур передача по ссылке имеет еще один недостаток: она требует до­ полнительных времени и памяти для копирования фактических аргументов в стек (для параметра функции выделяется память в стеке). Передача по ссылке устра­ няет копирование данных и не усложняет процесс, как это происходит в случае передачи по указателю. Но при передаче параметра по ссылке трудно сообщить намерения разработчика программисту, сопровождающему приложение, указать, какие параметры модифицируются функцией, а какие нет. Применение специфи­ катора const решает данную проблему. Это чрезвычайно полезная техника.

Для массивов доступен только один режим, а для входных и выходных пара­ метров синтаксис совпадает. При этом затрудняется понимание потока данных в программе сопрово>вдающим программистом: не ясно, какие именно параметры модифицируются функцией, а какие сохраняют свои значения.

Использование спецификаторов const позволяет разработчику передать про­ граммисту информацию о том, что массивы не изменяются в результате вызова функции. Предположение, что массивы без модификатора const обязательно из­ меняются функцией, не всегда имеет под собой основание, и, чтобы эта техника действительно была полезна программисту, сопровождающему приложение, раз­ работчик должен действовать осторожно.

Мы рассмотрели также преобразование и приведение типов аргументов. Когда типы аргументов и параметров несовместимы, преобразование не допускается. Типы не совместимы, если они принадлежат к разным категориям (скалярное значение, структура, указатель, массив). Преобразование между категориями не допускается. В этом плане в C + + соблюдается строгий контроль типов. Между тем, C + + допускает неявное преобразование между скалярными числовыми типами, не задавая лишних вопросов. Кроме того, С+ поддерживает явное пре­ образование (приведение) между указателями или массивами разных типов. Эти преобразования предоставляют программисту более гибкие возможности, но спо­ собствуют ошибкам и могут затруднять понимание и сопровождение программы.

Мы обсудили также встраиваемые функции, позволяющие избавиться от непроизводительных издержек вызова функции. При корректном использовании эти функции могут повышать производительность программы, а при некорректном способны увеличить размер объектного кода и даже снизить производительность из-за избыточного свопинга.

Глава 7 • Программирование с использованием функций C-f+

301

Кроме того, в данной главе рассказывалось о присваивании параметрам значе­ ний по умолчанию и о перегрузке имен функций. Это превосходные средства язы­ ка, уменьшаюш^ие ограничения пространств имен в программных проектах C+ + . Они открывают также новые перспективы в сопровождении программ, позволяя обойтись без изменения существуюи;его клиентского кода, когда вызываемые из этого кода функции требуют изменений. Между тем данные средства должны ис­ пользоваться по возможности реже. Они сложны, и слишком многое происходит в программе C++ "за кулисами". Непродуманное использование данных возмож­ ностей может запутать не только компилятор, но и сопровождаюш,его ПО про­ граммиста.

Методы программирования функций составляют основу C+ + . Не освоив на хорошем уровне функции C+ + , невозможно создавать высококачественные объектно-ориентированные программы. На самом деле, без этого нельзя созда­ вать любые качественные программы — объектно-ориентированные или нет. Следуюш,ая глава начинается с изучения объектно-ориентированного программи­ рования — самого мош,ного способа создания высококачественных программ.

%Г^^6^

^

объектно-ориентированное программирование с использованием функций

Темы данной главы

•^ Сцепление

^Связность

у^ Инкапсуляция данных ^ Сокрытие информации

^ Большой пример инкапсуляции ^ Недостатки инкапсуляции в функциях ь^ Итоги

еэтой главы начинается обсуждение принципов и методов объектноориентированного программирования. Некоторые из них можно отнести к обычным навыкам программирования, другие сформулированы и адап­

тированы для использования с C++ . Такие принципы и методы редко обсу>кдаются в других книгах по C+ + , поэтому даже программистам, уже имеюш^им опыт работы на C + + , не стоит пропускать данную главу.

В предыдуш,их главах основное внимание уделялось правилам языка C+ + , определяюш,им, что синтаксически допустимо и что недопустимо в языке C+ + . Подобно естественным языкам, недопустимые конструкции должны исключаться не только из соображений их неоднозначности или плохого стиля, а просто потому, что компилятор не сможет преобразовать их в объектный код. Что касается допус­ тимых конструкций, то они позволяют выразить одно и то же разными способами. В предшествующих главах сравнивались разные способы применения различных конструкций — часто с точки зрения корректности программы, ее производитель­ ности, и, конечно, стиля. Между тем основным вопросом было удобство сопровож­ дения программы — нужно добиться того, чтобы сопрово>едаюш,ий ее программист не тратил лишние усилия на попытки понять, что имел в виду разработчик, когда писал исходный код.

В этой главе (и в следуюш,их главах) понятность исходного кода также будет важным вопросом. Но фокус дискуссии сместится с написания управляюш,их кон­ струкций в исходном коде на более высокий уровень программирования: разбие­ ние программы на взаимодействуюш,ие части (функции и классы).

Глава 8 • Программирование с использованием функций

303

Не будем углубляться в системный анализ и разбираться в том, какие функции должны присутствовать в приложении для достижения поставленных целей. Это чрезмерно расширило бы тему данной книги. Будем предполагать, что необходи­ мые функции для достижения целей программы уже имеются, и сконцентрируемся на способах использования дополнительных функций, улучшающих удобство со­ провождения и повторного использования программы.

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

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

Еще один важный критерий — простота написания программного кода. Этот критерий до сих пор подходит и для небольших программ, разрабатываемых не­ сколькими программистами и используемых непродолжительное время (после чего они заменяются новыми), и для крупных систем, эксплуатируемых очень долго, в создании которых участвуют большие коллективы разработчиков. В то же время экономика разработки ПО предполагает в этих случаях другой подход. Лучшая версия программы — та, у которой отдельные части можно использовать повтор­ но и делать это легче (что предполагает экономию при разработке приложения и создания следующих версий), или та версия, которая проще в сопровождении (что предполагает экономию при развитии и совершенствовании программы).

Удобство сопровождения и повторного использования — это две наиболее важные характеристики качества ПО. Однако эти характеристики слишком общие. Вовсе не очевидно, какую версию кода легче и дешевле сопровождать, а какую проще использовать повторно.

Возможность повторного использования тесно связана с независимостью от­ дельных частей программы. Среди нескольких версий кода С4-+ версию, в кото­ рой разобраться проще и быстрее (предпочтительнее, не обращаясь к другим сегментам программы), как правило легче изменять без нежелательного влияния на другие фрагменты кода.

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

Все это хорошо, но для программиста-практика недостаточно специфично и точно. Концепции "понятности" и "независимости" должны поддерживаться более специфическими техническими критериями, которые легче распознавать и использовать. В данной главе предлагается несколько технических критериев. Два из них — сцепление и связность — относительно стары, а два других — инкапсуляция и сокрытие информации — довольно новы, и отрасль не накопила достаточно опыта их использования. Кроме инкапсуляции и сокрытия информа­ ции, будем использовать несколько разновидностей критериев, связанных с по­ нятностью и независимостью кода:

• Перенос обязанностей с функции-клиента на функцию-сервер

• Ограниченность знания, используемого клиентом и сервером

I 304

Часть II * Объвктио-орыешырошаииое протрать. '^;г414В НО C++

• Разделение задач клиентской и серверной функции

Не разделение тех частей, которые должны быть вместе

Передача знания разработчика сопровождаюидему приложение программисту в самой программе, а не в комментариях

Никакого всеохватывающего термина для этих принципов подобрать не удалось (принцип максимальной независимости?; принцип Штерна?; разделения знания по принципу необходимости?; принцип самодокументируемого кода?). Как будет понятно дальше, данные принципы в чем-то перекрещиваются друг с другом и с критериями сцепления, связности, сокрытия информации и инкапсуляции. Практикующие программисты должны быть знакомы со всеми перечисленными принципами. Их основное достоинство состоит в том, что все они применимы в работе и показывают, в каком направлении нужно двигаться, чтобы улучшить архитектуру программы и ее качество, как нужно усовершенствовать методы программирования.

В основе данных критериев лежит идея, что функции программы взаимодей­ ствуют друг с другом, выполняя части общей работы. Как бы ни распределялись между ними обязанности, функции должны использовать какие-то общие знания, иметь общие цели, работать над частью одной задачи. Все это производят разные, функции, но они — части одной программы. Чтобы сделать данные функции по­ нятными, чтобы их можно было повторно использовать, нужно так распределить между ними обязанности, спроектировать систему таким образом, чтобы зависи­ мости между функциями были минимальными.

Как это часто бывает, написание программы более высокого качества требует дополнительных усилий, а программа содержит больше строк, чем менее качест­ венная программа. Некоторые программисты (и менеджеры) будут, наверное, ра­ зочарованы таким увеличением объема работы. Но можно привести интересную аналогию с правилами дорожного движения.

Когда я стою на красном сигнале светофора, то иногда думаю, что без ограни­ чивающих правил дорожного движения добрался бы до места быстрее. Возможно, это и так, но не для всех мест назначения и не для всех водителей. Езда без правил приведет к авариям и пробкам на дорогах. Водители, избежавшие аварий и про­ бок, могут действительно добраться до места быстрее. Но многие другие попадут в пункт назначения значительно позже ожидаемого времени. Правила движения отнимают у нас время, чтобы, в конечном счете, сэкономить его.

Аналогично игнорирование правил удобства сопровождения и повторного ис­ пользования программы позволит написать ее быстрее, но так будет не для всех приложений и не для всех программистов. Время, сэкономленное на написании программы, будет существенно меньше времени, которое придется потратить, чтобы разобраться в ней и понять, каких целей стремились добиться разработчики (и где они ошиблись).

Вот почему в индустрии ПО столь большое внимание уделяется написанию комментариев. Комментарии в программе — это своего рода инвестиции, в конеч­ ном счете окупающие себя (когда они ясные, полные и не устаревшие). Между тем часто строки комментариев неполны, непонятны и не отражают изменений, вне­ сенных после написания программы. Затраты на написание понятного програм­ много кода предпочтительнее затрат на комментарии.

При написании небольшой программы правила создания качественного, по­ нятного кода не очень важны, но если разрабатывается большое приложение, то затраты на разработку качественного кода имеют решающее значение и в резуль­ тате дадут отдачу.

Глава 8 • Программирование с использованием функций

305

Сцепление

Сцеплением называют связанность шагов, реализованных в одном сегменте кода, например в функции.

Если функция обладает сильным сцеплением (high cohesion), то она выполняет одну задачу с одним вычислительным объектом или структурой данных. При сла­ бом сцеплении функция выполняет несколько задач с одним объектом или даже несколько задач с несколькими объектами. Функция со слабым сцеплением вклю­ чает в себя вычисления, не имеющие отношения друг к другу и выполняемые с независимыми объектами. Это означает, что разработчик совместил в одной функции шаги, которые не должны выполняться вместе.

Функциям с сильным сцеплением прош^е давать имена. Обычно используется комбинация "глагол + существительное". Глагол обозначает выполняемое данной функцией действие, а существительное — объект (субъект) действия. Например, insertltemO, findAccountO и т. д. (если, конечно, имя функции соответствует ее содержанию, что бывает не всегда).

Для функций со слабым сцеплением пришлось бы использовать несколько глаголов или существительных, например findOrlnsertltemO.

Вот еще один пример, хотя и несколько неуклюжий (все хорошие примеры функций со слабым сцеплением неуклюжие, так как описывают плохо спроекти­ рованные функции):

void

initializeGlobalObjects ()

/ /

один вычислительный объект

{ numaccts

= 0;

fstream

i n f ( " t r a n s . d a t " , i o s : : i n ) ;

/ /

файл транзакций

numtrans

= 0;

/ /

еще один вычислительный объект

i f

(inf==NULL) exit(1); }

/ /

снова файл транзакций

В данном примере переменную numaccts следует инициализировать там, где обрабатывается accounts (где выполняются операции со счетами). Аналогично numtrans нужно инициализировать при обработке транзакций, а не при инициа­ лизации счетов. В этой функции разделено и включено в функцию со слабым сцеплением то, что должно быть вместе (т. е. группироваться с другими шагами обработки).

Преодолеть проблему можно, спроектировав функцию заново. Как уже упо­ миналось в главе 1, перепроектирование означает изменение списка отдельных частей (функций) и их обязанностей. В случае слабого сцепления перепроекти­ рование обычно предполагает разбиение функций со слабым сцеплением на не­ сколько функций с сильным сцеплением. В результате число функций может значительно увеличиться. Кроме потенциального негативного влияния на произ­ водительность, это затруднит сопровождение программы — специалистам по со­ провождению придется очень много запоминать (имена функций и их интерфейсы). Для маленьких функций, таких, как initializeGlobalObjectsO, разбиение не имеет смысла. Вероятно, такие функции следует исключить.

Сцепление нельзя считать очень строгим критерием. Решение о перепроекти­ ровании и разделении функций — не абсолютное требование. В случае сомнения следует руководствоваться другими критериями. Сцепление важно для оценки проекта. Им нужно руководствоваться при выборе альтернативных вариантов — распределении работ между функциями.

Связность

Связность (coupling) — намного более сильный и полезный критерий, чем сцепление. Он описывает интерфейс или поток данных между вызываемой функ­ цией (функцией-сервером) и вызываюш,ей функцией (функцией-клиентом).

306 I

Часть II ^ Обьектно-ориентировонное програтттровоитв но С-^г-^

Связность может быть неявной (когда функции взаимодействуют через глобальные переменные) или явной (клиент и сервер взаимодействуют через параметры). Неявная связность дает более сильную зависимость между функциейклиентом и функцией-сервером. Неявная связность предпочтительнее: при ком­ муникациях через параметры функции легче понять, повторно использовать и модифицировать.

Степень связности определяется числом значений, передаваемых от клиент­ ской функции серверной и обратно. Большое число значений означает сильную связность (высокую степень зависимости между функциями), а малое число значе­ ний означает слабую связность (низкую степень зависимости между функциями (клиентом и сервером).

Неявная связность

Клиентская функция передает серверной функции входные данные и исполь­ зует вычисленный серверной функцией результат (выходные значения сервера). Связность неявна, когда коммуникации с функцией осуш,ествляются через гло­ бальные переменные, не перечисленные в интерфейсе функции.

Рассмотрим, например, интерактивную программу, которая просит пользова­ теля ввести год и выводит сообш,ение о том, високосный ли он.

i nt

year,

remainder; bool leap;

/ /

данные программы

cout «

"Введите

год: ";

, / /

приглашение пользователю

Gin »

year;

 

 

 

 

/ /

получение данных от пользователя

remainder

= year % 4;

 

 

 

i f

(remainder

!=

0)

 

/ /

не делится на 4

 

leap

= false;

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

{

i f

(year%100 == 0 && year%400

!=0)

 

 

 

 

leap

= false;

/ /

делится на 100, но не на 400

 

else

 

 

 

 

 

 

 

 

 

 

leap

= true;

}

/ / в противном случае это високосный год

i f

(leap)

 

 

 

 

 

 

 

 

cout

«

year

«

"

високосный год\п";

/ /

вывод результатов

else

 

 

 

 

 

 

 

 

 

cout

«

year

«

"

не високосный

год\п";

 

 

}

 

 

 

 

 

 

 

 

 

 

Эта программа аналогична той, которая уже обсуждалась в главе 4 (листинги 4.8 и 4.9). Она небольшая и не нуждается в разбиении на модули. Но программа, где модульность действительно дает преимуш,ества, должна быть достаточно боль­ шого размера. Детальное изучение таких программ и сравнение альтернативных вариантов само по себе могло бы стать нелегкой задачей и отвлечь нас от прин­ ципов модульности, на которых сейчас стоит сконцентрироваться. Ведь именно сами эти принципы, а не детали различных примеров следует применять в реаль­ ной ситуации.

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

Итак, мы имеем монолитную программу, которую желательно разделить на управляемые компоненты. Для простоты разделим ее только на две функции: функцию main(), отвечаюшую за интерфейс с пользователем и обш,ие вычисления, и функцию isLeapO, использующую значения year и remainder для вычисления значения leap, на основе которого main() выводит результат.

void

isLeapO

 

{

i f (remainder != 0)

/ / не делится на 4

 

leap = false;

 

Введите год: 1999 1999 не високосный год
Рис. 8.1.
Результат, программы из лист^инга 8.1

Глава 8 • Программирование с использованием функций

307

else i f

(year%100 == О && year%400! = 0)

 

leap

= false;

/ / делится на 100, но не на 400

 

else

 

 

 

leap

= true; }

/ / в противном случае это високосный год

Здесь есть одна техническая проблема, которая относится к обсуждавшейся в главе 6 концепции области действия. Значения year и remainder, используемые функцией isLeapO, устанавливаются в функции main(). Вычисляемое функцией isLeapC) значение leap применяется в main(). Однако, если определяем эти пере­ менные в main(), они будут видимы только там. Правила области действия C+-f предотвращают "видимость" этих значений в любых других функциях, и isLeapO

не сможет манипулировать данными переменными. Если определить эти переменные в isLeapO, они будут видимы только в функции isLeapO. Правила области действия C++ сделают их невидимыми в функции main(). Чтобы переменные были доступны и в main(), и в isLeapO, для обеих функций эти переменные нужно определить как глобальные.

Это решение демонстрируется в листинге 8.1. Пример выполнения программы показан на рис. 8.1.

Листинг 8.1. Пример неявного связывания через глобальные переменные

#include <iostream> using namespace std;

int year, remainder; bool leap;

void isLeapO

{ i f (remainder != 0) leap = false;

else i f (year%100==0 && year%400!=0) leap = false;

else

leap = true; }

int mainO

{cout « "Введите год: "; cin » year;

remainder = year % 4; isLeapO;

if (leap)

cout « year « " високосный год\п"; else

cout « year « " не високосный год\п"

/ /

глобальные переменные

(вход)

/ /

глобальная переменная

(выход)

/ /

не делится на 4

 

/ /

делится на 100, но не на 400: не високосный год

/ /

в противном случае это високосный год

/ /

получение данных от пользователя

/ / вывод результатов

return 0;

}

В данной программе функция main() вызывает функцию isLeapO. Функция main() является клиентом и выполняет свою задачу, вызывая другие функции. Функция isLeapO — это сервер. Она делает некую работу для вызываюш1,его ее клиента. Соотношение между функциями показано на рис. 8.2. Эта структурная диаграмма демонстрирует пото­ ки данных между функциями. Переменные year и remainder устанав­ ливаются в функции mainO и используются в функции isLeapO как входные значения для вычисления результата. Подсчитанное функцией isLeap() значение переменной leap — это ее выходное значение. Оно используется функцией main() после вызова isLeapO.

 

main()

Уear

i

remainder

 

f

leap

 

isLeapO

Рис. 8.2. Ст,рукт,урная диаграмма для программы

из листинга 8.1

308 Часть!! * Объек^

Заметим, что перед вызовом функции isLeapO в функции main() входные пе­ ременные year и remainder должны иметь допустимые значения. Функция-клиент должна убедиться, что эти значения правильно инициализированы. Функциясервер isLeapO не проверяет допустимость значений. Она предполагает, что функция main О исполняет свои обязательства.

Аналогично выходные переменные (в данном случае leap) не обязаны содер­ жать допустимое значение перед вызовом функции-сервера isLeapO. Эта функ­ ция сама должна установить выходное значение, а клиент — позднее, после вызова (но не перед ним), его использовать.

Очень важно представлять поток данных между функциями. Если известно, что переменные year и remainder являются входными переменными функции isLeapO, то можно ожидать, что функция-сервер использует эти значения, но не изменяет их. Было бы крайне странно предполагать, что функция isLeap() делает что-то вроде следующего:

void

isLeapO

 

{

remainder = 4; year = 2000; ...

/ / нонсенс!

Кроме того, если известно, что переменная leap — выходная переменная функции isLeapO, то не стоит ожидать, что клиент main() инициализирует эту переменную перед вызовом isLeapO или будет изменять ее значение сразу после вызова, предварительно не использовав его для тех или иных целей.

int mainO

 

 

{ cout

«

"Введите год:

";

 

cin

»

year;

/ /

получение данных от пользователя

remainder - year % 4;

 

 

leap = false;

 

 

isLeapO;

/ /

вводит в заблуждение (и некорректно),

leap

= true;

 

 

 

/ /

если выполняется сразу после вызова

Что будет думать сопровождающий приложение программист, прочитав при­ веденную выше функцию? После определения цели присваивания remainder (эта переменная используется в isLeapO для вычисления значения переменной leap), программисту придется снова исследовать функцию isLeapO и попытаться понять, для чего выполняется присваивание leap. Для маленькой функции достаточно не­ скольких секунд, чтобы сделать вывод: значение, присвоенное в клиенте main() переменной leap, не используется функцией-сервером isLeapO и даже самим клиентом main(). Но это лишь для маленькой функции. Для крупной программы потребуется гораздо больше времени. Сопровождающий ее программист может запутаться и сделать неверные выводы.

Действительно, некоторые программисты настолько не любят неинициализиро­ ванных переменных, что инициализируют их, даже когда в том нет необходимости. По их мнению, это помогает, когда функция-сервер по тем или иным причинам не присваивает значение. Однако isLeapO не относится к таким функциям! Как и большинство других функций. Если программисты понимают поток данных между функциями, то не возникает ситуация, когда функция не присваивает значе­ ния выходной переменной.

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

Глава 8 • Программирование с использованием функций

309

приложение программисту, какие значения будут использоваться сервером в каче­ стве ввода (инициализируя их в клиенте), а какие являются выходными перемен­ ными сервера (не инициализируя их).

Надеюсь, вы следите за дискуссией и понимаете важность передачи сопровож­ дающему приложение программисту знания разработчика о потоке данных между функциями. Давайте вернемся к обсуждению связности.

Связность определяет, сколько усилий и времени потребуется для понимания потока данных между функциями. Часто для этого необходимо исследовать обра­ ботку данных клиентом и функцией-сервером. Например, в листинге 8.1 функция mainO присваивает значения переменным year и remainder, а isLeapO использует эти значения, а также что main() не инициализирует leap, isLeapO присваивает значение leap, а main() использует это значение после вызова1з1еар(). Все так.

Однако, чтобы выявить эти простые зависимости, надо изучить функцию-клиент и функцию-сервер во всей полноте. В таком тривиальном примере это сделать легко, но в более реалистичной и сложной функции значительного размера потре­ буется гораздо больше времени. Можно ли усовершенствовать данную трудоем­ кую и подверженную ошибкам технику? Конечно. С помощью явной связности.

Явная связность

Явная связность осуществляется через параметры функции: все переменные (вход и выход), используемые функцией-сервером, включаются в параметры этой функции, и глобальные переменные в потоке данных между клиентом и сервером не используются. Листинг 8.2 показывает тот же пример, что и в листинге 8.1, но неявный поток данных через глобальные переменные заменен на явные пара­ метры. Эта программа выполняется аналогично программе из листинга 8.1.

Листинг 8.2. Пример явного связывания через параметры

ttinclude

<iostream>

 

 

 

 

 

 

using

namespace std;

 

 

 

 

 

 

void

isLeap(int

year,

int

remainder, bool &leap)

/ /

параметры

 

 

/ / ввод: year, remainder;

вывод: leap

 

 

 

 

{

i f

(remainder

!=

0)

 

 

 

 

 

 

 

leap

= false;

 

 

 

 

 

 

 

else

i f

(year%100==0 && year%400!=0)

 

 

 

 

 

 

leap

= false;

 

 

 

 

 

 

 

else

 

 

 

 

 

 

 

 

 

 

 

 

leap

= true;

}

 

 

 

 

 

 

int

mainO

 

 

 

 

 

 

 

 

 

{ int year,

remainder;

 

/ /

локальные

переменные

(ввод)

bool

leap;

 

 

 

 

 

/ /

локальная

переменная

(выход)

cout

«

"Введите

год: ";

 

 

 

 

 

Gin »

year;

 

 

 

 

/ /

получение данных от пользователя

remainder = year % 4;

 

 

 

 

 

isLeapO (year,

reminder,

leap);

 

 

 

 

i f

(leap)

 

 

 

 

 

 

 

 

 

cout

«

year

«

"

високосный год\п";

 

 

 

 

else

 

 

 

 

 

 

 

 

 

 

 

cout

«

year

«

"

не високосный год\п";

 

 

 

 

return

0;

 

 

 

 

 

 

 

 

 

Соседние файлы в предмете Программирование на C++