Материал: Руководство по стилю программирования и конструированию по

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

152
ЧАСТЬ II Высококачественный код
Избегайте создания «божественных» классов Избегайте создания классов,
которые все знают и все могут. Если класс извлекает и задает данные других классов с использованием методов
Get() и Set() (т. е. вмешивается в их дела и указывает им, что делать), спросите себя, не следует ли его функциональность реализовать в тех классах, а не выделять в божественный класс (Riel, 1996).
Устраняйте нерелевантные классы Если класс имеет только данные, но не формы поведения, спросите себя, дей- ствительно ли это класс. Возможно, этот класс следует раз- жаловать, сделав его данные-члены атрибутами одного или нескольких других классов.
Избегайте классов, имена которых напоминают глаголы Как правило,
класс, имеющий только формы поведения, но не данные, на самом деле классом не является. Подумайте о превращении класса вроде
DatabaseInitialization() или
StringBuilder() в метод какого-нибудь другого класса.
Резюме причин создания класса
Вот список разумных причин создания класса:

моделирование объектов реального мира;

моделирование абстрактных объектов;

снижение сложности;

изоляция сложности;

сокрытие деталей реализации;

ограничение влияния изменений;

сокрытие глобальных данных;

упрощение передачи параметров в методы;

создание центральных точек управления;

облегчение повторного использования кода;

планирование создания семейства программ;

упаковка родственных операцией;

выполнение специфического вида рефакторинга.
6.5. Аспекты, специфические для языков
Использование классов в разных языках программирования имеет интересные различия. Рассмотрим, например, переопределение метода-члена в производном классе при реализации полиморфизма. В Java все методы переопределяемы по умолчанию, а чтобы в производном классе метод нельзя было переопределить, его нужно объявить как
final. В C++ методы по умолчанию непереопределяемы. Чтобы сделать метод переопределяемым, его нужно объявить в базовом классе как
virtual.
В Visual Basic переопределяемый метод должен быть объявлен в базовом классе как
overridable, а в производном классе нужно использовать ключевое слово overrides.
Перекрестная ссылка Такой вид класса обычно называют структу- рой. О структурах см. раздел 13.1.

1   ...   16   17   18   19   20   21   22   23   ...   104
ГЛАВА 6 Классы
153
Вот некоторые другие аспекты классов, во многом зависящие от языка:

поведение переопределенных конструкторов и деструкторов в дереве насле- дования;

поведение конструкторов и деструкторов при обработке исключений;

важность конструкторов по умолчанию (конструкторов без аргументов);

время вызова деструктора или метода финализации;

целесообразность переопределения встроенных операторов языка, в том числе операторов присваивания и сравнения;

управление памятью при создании и уничтожении объектов или при их объяв- лении и выходе из области видимости.
Подробно эти вопросы мы рассматривать не будем, но в разделе «Дополнительные ресурсы» я указал несколько хороших книг, посвященных конкретным языкам.
6.6. Следующий уровень: пакеты классов
В настоящее время использование классов — лучший спо- соб достижения модульности. Однако модульность — обшир- ная тема, и она никак не ограничивается классами. В по- следние десятилетия отрасль разработки ПО развивалась во многом благодаря увеличению агрегаций, с которыми нам приходится работать. Первой агрегацией были операторы, что при сравнении с машинными командами казалось в то время большим достижением. Затем появи- лись методы, а позднее придуманы классы.
Ясно, что мы могли бы лучше выполнять абстракцию и инкапсуляцию, если бы имели эффективные средства агрегации групп объектов. Java поддерживает па- кеты, а язык Ada поддерживал их уже десять лет назад. Если используемый вами язык не поддерживает пакеты непосредственно, вы можете создать собственные версии пакетов, подкрепив их стандартами программирования, такими как:

конвенции именования, проводящие различие между классами, которые мож- но применять вне пакета, и классами, предназначенными только для закрыто- го использования;

конвенции именования, конвенции организации кода (структура проекта) или и те, и другие конвенции, определяющие принадлежность каждого класса к тому или иному пакету;

правила, определяющие возможность использования конкретных пакетов дру- гими пакетами, в том числе возможность наследования, включения или того и другого.

Это еще один удачный пример различия между программированием
на языке и программированием
с использованием языка (см. раздел 34.4).
Перекрестная ссылка О разли- чии между классами и пакетами см. также подраздел «Уровни проектирования» раздела 5.2.

154
ЧАСТЬ II Высококачественный код
Контрольный список: качество классов
Абстрактные типы данных

Обдумали ли вы классы программы как абстрактные типы данных, оценив их интерфейсы с этой точки зрения?
Абстракция

Имеет ли класс главную цель?

Удачное ли имя присвоено классу? Описывает ли оно глав- ную цель класса?

Формирует ли интерфейс класса согласованную абстрак- цию?

Ясно ли интерфейс описывает использование класса?

Достаточно ли абстрактен интерфейс, чтобы вы могли не думать о реали- зации класса? Можно ли рассматривать класс как «черный ящик»?

Достаточно ли полон набор сервисов класса, чтобы другие классы могли не обращаться к его внутренним данным?

Исключена ли из класса нерелевантная информация?

Обдумали ли вы разделение класса на классы-компоненты? Разделен ли он на максимально возможное число компонентов?

Сохраняется ли целостность интерфейса при изменении класса?
Инкапсуляция

Сделаны ли члены класса минимально доступными?

Избегает ли класс предоставления доступа к своим данным-членам?

Скрывает ли класс детали реализации от других классов в максимально возможной степени, допускаемой языком программирования?

Избегает ли класс предположений о своих клиентах, в том числе о произ- водных классах?

Независим ли класс от других классов? Слабо ли он связан?
Наследование

Используется ли наследование только для моделирования отношения «яв- ляется», т. е. придерживаются ли производные классы принципа подстановки
Лисков?

Описана ли в документации класса стратегия наследования?

Избегают ли производные классы «переопределения» непереопределяемых методов?

Перемещены ли общие интерфейсы, данные и формы поведения как мож- но ближе к корню дерева наследования?

Не слишком ли много уровней включают иерархии наследования?

Все ли данные — члены базового класса сделаны закрытыми, а не защи- щенными?
Другие вопросы реализации

Класс содержит около семи элементов данных-членов или меньше?

Минимально ли число встречающихся в классе непосредственных и опо- средованных вызовов методов других классов?

Сведено ли к минимуму сотрудничество класса с другими классами?

Все ли данные-члены инициализируются в конструкторе?
http://cc2e.com/0672
Перекрестная ссылка Этот кон- трольный список позволяет определить качество классов.
Об этапах создания класса см.
контрольный список «Процесс программирования с псевдоко- дом» в главе 9.

ГЛАВА 6 Классы
155

Спроектирован ли класс для использования полного, а не ограниченного копирования, если нет убедительной причины создания ограниченных копий?
Аспекты, специфические для языков

Изучили ли вы особенности работы с классами, характерные для выбран- ного языка программирования?
Дополнительные ресурсы
Классы в общем
Meyer, Bertrand.
Object-Oriented Software Construction, 2d ed. —
New York, NY: Prentice Hall PTR, 1997. В этой книге Мейер рассматривает абстрактные типы данных и объясняет, как они формируют основу классов. В главах 14–16 подробно обсуждается наследование.
В главе 15 Мейер приводит довод в пользу множественного наследования.
Riel, Arthur J.
Object-Oriented Design Heuristics. — Reading, MA: Addison-Wesley, 1996.
Эта книга включает множество советов по улучшению проектирования, относя- щихся большей частью к уровню классов. Я избегал ее несколько лет, потому что она казалась слишком большой — воистину сапожник без сапог! Однако основ- ная часть книги занимает только около 200 страниц. Книга написана доступным и занимательным языком, а ее содержание сжато и практично.
C++
Meyers, Scott.
Effective C++: 50 Specific Ways to Improve Your
Programs and Designs, 2d ed. — Reading, MA: Addison-Wesley,
1998.
Meyers, Scott.
More Effective C++: 35 New Ways to Improve Your Programs and Designs.
— Reading, MA: Addison-Wesley, 1996. Обе книги Мейерса являются канонически- ми для программистов на C++. Они очень интересны и позволяют приобрести глу- бокие знания некоторых нюансов C++.
Java
Bloch, Joshua.
Effective Java Programming Language Guide. —
Boston, MA: Addison-Wesley, 2001. В книге Блоха можно найти много полезных советов по Java, а также описания более общих объектно-ориентированных подходов.
Visual Basic
Ниже указаны книги, в которых хорошо рассмотрена работа с классами в контексте Visual Basic.
Foxall, James.
Practical Standards for Microsoft Visual Basic .NET.
— Redmond, WA: Microsoft Press, 2003.
Cornell, Gary, and Jonathan Morrison.
Programming VB .NET: A Guide for Experienced
Programmers. — Berkeley, CA: Apress, 2002.
Barwell, Fred, et al.
Professional VB.NET, 2d ed. — Wrox, 2002.
http://cc2e.com/0679
http://cc2e.com/0686
http://cc2e.com/0693
http://cc2e.com/0600

156
ЧАСТЬ II Высококачественный код
Ключевые моменты

Интерфейс класса должен формировать согласованную абстракцию. Многие проблемы объясняются нарушением одного этого принципа.

Интерфейс класса должен что-то скрывать — особенности взаимодействия с системой, аспекты проектирования или детали реализации.

Включение обычно предпочтительнее, чем наследование, если только вы не моделируете отношение «является».

Наследование — полезный инструмент, но оно повышает сложность, что про- тиворечит Главному Техническому Императиву Разработки ПО, которым явля- ется управление сложностью.

Классы — главное средство управления сложностью. Уделите их проектирова- нию столько времени, сколько нужно для достижения этой цели.

ГЛАВА 7 Высококачественные методы
157
Г Л А В А 7
Высококачественные
методы
Содержание

7.1. Разумные причины создания методов

7.2. Проектирование на уровне методов

7.3. Удачные имена методов

7.4. Насколько объемным может быть метод?

7.5. Советы по использованию параметров методов

7.6. Отдельные соображения по использованию функций

7.7. Методы-макросы и встраиваемые методы
Связанные темы

Этапы конструирования методов: раздел 9.3

Классы: глава 6

Общие методики проектирования: глава 5

Архитектура ПО: раздел 3.5
В главе 6 мы подробно рассмотрели создание классов. В этой главе мы обратим внимание на методы и характеристики, отличающие хорошие методы от плохих.
Если вам хотелось бы сначала разобраться в вопросах, влияющих на проектиро- вание методов, прочитайте главу 5 и потом вернитесь к этой главе. Некоторые важные атрибуты высококачественных методов обсуждаются также в главе 8. Если вас больше интересуют этапы создания методов и классов, см. главу 9.
Прежде чем перейти к деталям, определим два базовых термина. Что такое «метод»?
Метод — это отдельная функция или процедура, выполняющая одну задачу. В раз- личных языках методы могут называться по-разному, но их суть от этого не меня- ется. Иногда макросы C и C++ также полезно рассматривать как методы. Многие советы по созданию высококачественных методов относятся и к макросам.
Что такое
высококачественный метод? На этот вопрос ответить сложнее. Возможно,
лучше всего просто показать, что
не является высококачественным методом. Вот пример низкокачественного метода:
http://cc2e.com/0778

158
ЧАСТЬ II Высококачественный код
Пример низкокачественного
метода (C++)
void HandleStuff( CORP_DATA & inputRec, int crntQtr, EMP_DATA empRec,
double & estimRevenue, double ytdRevenue, int screenX, int screenY,
COLOR_TYPE & newColor, COLOR_TYPE & prevColor, StatusType & status,
int expenseType )
{
int i;
for ( i = 0; i < 100; i++ ) {
inputRec.revenue[i] = 0;
inputRec.expense[i] = corpExpense[ crntQtr ][ i ];
}
UpdateCorpDatabase( empRec );
estimRevenue = ytdRevenue * 4.0 / (double) crntQtr;
newColor = prevColor;
status = SUCCESS;
if ( expenseType == 1 ) {
for ( i = 0; i < 12; i++ )
profit[i] = revenue[i] - expense.type1[i];
}
else if ( expenseType == 2 ) {
profit[i] = revenue[i] - expense.type2[i];
}
else if ( expenseType == 3 )
profit[i] = revenue[i] - expense.type3[i];
}
Что тут не так? Подскажу: вы должны найти минимум 10 недостатков. Составив свой список, сравните его с моим.

Неудачное имя:
HandleStuff() ничего не говорит о роли метода.

Метод недокументирован (вопрос документирования не ограничивается отдель- ными методами и обсуждается в главе 32).

Метод плохо форматирован. Физическая организация кода почти не дает пред- ставления о его логической организации. Стратегии форматирования исполь- зуются непоследовательно: сравните стили операторов
if с условиями
expenseType == 2 и expenseType == 3 (о форматировании см. главу 31).

Входная переменная
inputRec внутри метода изменяется. Если это входная пе- ременная, изменять ее нежелательно (в случае C++ ее следовало бы объявить как
const). Если изменение значения предусмотрено, переменную не стоило называть
inputRec.

Метод читает и изменяет глобальные переменные: читает
corpExpense и изме- няет
profit. Взаимодействие этого метода с другими следовало бы сделать бо- лее непосредственным, без использования глобальных переменных.

Цель метода размыта. Он инициализирует ряд переменных, записывает дан- ные в БД, выполняет вычисления — все эти действия не кажутся связанными между собой. Метод должен иметь одну четко определенную цель.
Источник: https://files.student-it.ru/previewfile/206183