Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 769
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
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. Взаимодействие этого метода с другими следовало бы сделать бо- лее непосредственным, без использования глобальных переменных.
쐽
Цель метода размыта. Он инициализирует ряд переменных, записывает дан- ные в БД, выполняет вычисления — все эти действия не кажутся связанными между собой. Метод должен иметь одну четко определенную цель.