Стандарты программирования на С++. 101 правило и рекомендация
Шрифт:
Последней (не по важности) темой книги является корректность типов — очень важное свойство программ, которое вы должны изо всех сил стараться поддерживать. Теоретически корректная с точки зрения типов функция не может обратиться к нетипизированной памяти или вернуть неверные значения. На практике, если ваш код поддерживает корректность типов, он тем самым избегает большого количества неприятных ошибок, от непереносимости программ до порчи содержимого памяти и неопределенного поведения программ.
Основная идея поддержки корректности типов — всегда считывать информацию в том формате, в котором она была записана. Иногда С++ позволяет легко нарушить это правило; приведенные в этом разделе рекомендации помогут вам избежать подобных ошибок.
В этом разделе мы считаем
90. Избегайте явного выбора типов — используйте полиморфизм
Избегайте явного выбора типа объекта для настройки поведения. Используйте шаблоны и виртуальные функции для того, чтобы поведение объекта определялось его типом, а не вызывающим кодом.
Настройка поведения в зависимости от типа объекта с использованием инструкции выбора
В идеале добавление новых возможностей в программу должно осуществляться добавлением нового кода, а не изменением старого (см. рекомендацию 37). В реальной жизни это не всегда так — зачастую в дополнение к написанию нового кода мы вынуждены вносить изменения в уже имеющийся код. Такие изменения, однако, крайне нежелательны и должны быть минимизированы по двум причинам. Во-первых, изменения могут нарушить имеющуюся функциональность. Во-вторых, они препятствуют масштабируемости при росте системы и добавлении новых возможностей, поскольку количество "узлов поддержки", к которым надо возвращаться и вносить изменения, все время возрастает. Это наблюдение приводит к принципу Открытости-Закрытости, который гласит: любая сущность (например, класс или модуль) должна быть открыта для расширений, но закрыта для изменений (см. [Martin96c] и [Meyer00]).
Каким же образом мы можем написать код, который будет легко расширяем без внесения изменений? Используйте полиморфизм для написания кода в терминах абстракций (см. также рекомендацию 36), после чего при необходимости добавления функциональности это можно будет сделать путем разработки и добавления различных реализаций упомянутых абстракций. Шаблоны и виртуальные функции образуют барьер для зависимостей между кодом, использующим абстракции, и кодом, их реализующим (см. рекомендацию 64).
Конечно, управление зависимостями обусловлено выбором верных абстракций. Если абстракции несовершенны, добавление новой функциональности потребует изменений интерфейса (а не просто добавления новых реализаций интерфейса), которые обычно влекут за собой значительные изменения существующего кода. Но абстракции потому и называются "абстракциями", что предполагается их большая стабильность по сравнению с "деталями", т.е. возможными реализациями абстракций.
Совсем иначе обстоит дело с предельно детализированным кодом, который использует мало абстракций или вовсе обходится без них, работая исключительно с конкретными типами и их отдельными операциями. Добавление новой функциональности в такой код — сущее мучение.
Пример. Рисование фигур. Классический пример — рисование различных объектов. Типичный подход в стиле С использует выбор типа. Для этого определяется член-перечисление
Такой код сгибается под собственным весом, он хрупок, ненадежен и сложен. В частности, он страдает транзитивной циклической зависимостью, о которой говорилось в рекомендации 22. Ветвь по умолчанию конструкции
В качестве альтернативы (или в качестве дополнения) рассмотрим реализацию, которая следует совету по возможности принимать решения во время компиляции (см. рекомендацию 64):
Теперь ответственность за рисование каждой геометрической фигуры переходит к реализации самой фигуры, и синдром "не знаю, что делать с этим типом" просто невозможен.
[Dewhurst03] §69, §96 • [Martin96c] • [Meyer00] • [Stroustrup00] §12.2.5 • [Sutter04] §36
91. Работайте с типами, а не с представлениями
Не пытайтесь делать какие-то предположения о том, как именно объекты представлены в памяти. Как именно следует записывать и считывать объекты из памяти — пусть решают типы объектов.
Стандарт С++ дает очень мало гарантий по поводу представления типов в памяти.
• Целые числа используют двоичное представление.
• Для отрицательных чисел используется дополнительный код числа в двоичной системе.
• Обычные старые типы (Plain Old Data, POD [5] ) имеют совместимое с С размещение в памяти: переменные-члены хранятся в порядке их объявления.
5
Неформально POD означает любой тип, представляющий собой набор простых данных, возможно, с пользовательскими функциями-членами для удобства. Говоря более строго, POD представляет собой класс или объединение, у которого нет пользовательского конструктора, копирующего присваивания, и деструктора, а также нет (нестатических) членов-данных, являющихся ссылками, указателями на члены или не являющихся POD. — Прим. ред.