Следует серьезно относиться к предупреждениям компилятора и использовать максимальный уровень вывода предупреждений вашим компилятором. Компиляция должна выполняться без каких-либо предупреждений. Вы должны понимать все выдаваемые предупреждения и устранять их путем изменения кода, а не снижения уровня вывода предупреждений.
Нажимайте на одну (единственную) кнопку: используйте полностью автоматизированные ("в одно действие") системы, которые собирают весь проект без вмешательства пользователя.
Как гласит китайская пословица, плохие чернила лучше хорошей памяти: используйте системы управления версиями. Не оставляйте файлы без присмотра на долгий срок. Проверяйте их всякий раз после того, как обновленные модули проходят тесты на работоспособность. Убедитесь, что внесенные обновления не препятствуют корректной сборке программы.
Регулярно просматривайте код всей командой. Чем больше глаз — тем выше качество кода. Покажите ваш код другим и познакомьтесь с их кодом — это принесет пользу всем.
Концентрируйтесь одновременно только на одной проблеме. Каждый объект (переменная, класс, функция, пространство имен, модуль, библиотека) должны решать одну точно поставленную задачу. С ростом объектов, естественно, увеличивается область их ответственности, но они не должны отклоняться от своего предназначения.
Корректность лучше быстроты. Простота лучше сложности. Ясность лучше хитроумия. Безопасность лучше ненадежности (см. рекомендации 83 и 99).
Всегда помните о возможном росте данных. Подумайте об асимптотической сложности без преждевременной оптимизации. Алгоритмы, которые работают с пользовательскими данными, должны иметь предсказуемое и, желательно, не хуже, чем линейно зависящее от количества обрабатываемых данных время работы. Когда становится важной и необходимой оптимизация, в особенности из-за роста объемов данных, в первую очередь следует улучшать O-сложность алгоритма, а не заниматься микрооптимизациями типа экономии на одном сложении.
Как гласит пословица, не подгоняйте скачущую лошадь. Преждевременная оптимизация непродуктивна и быстро входит в привычку. Первое правило оптимизации: не оптимизируйте. Второе правило оптимизации (только для экспертов): не оптимизируйте ни в коем случае. Семь раз отмерь, один раз оптимизируй.
То, что просто для вас, — просто и для кода. При прочих равных условиях, в особенности — сложности и удобочитаемости кода, ряд эффективных шаблонов проектирования и идиом кодирования должны естественным образом "стекать с кончиков ваших пальцев" и быть не сложнее в написании, чем их пессимизированные альтернативы. Это не преждевременная оптимизация, а избежание излишней пессимизации.
Совместное использование вызывает споры и раздоры. Избегайте совместного использования данных, в особенности глобальных данных. Совместно используемые данные усиливают связность, что приводит к снижению сопровождаемоcти, а зачастую и производительности.
Не выпускайте внутреннюю информацию за пределы объекта, обеспечивающего абстракцию.
Если ваше приложение использует несколько потоков или процессов, следует минимизировать количество совместно используемых объектов, где это только можно (см. рекомендацию 10), и аккуратно работать с оставшимися.
Не работайте вручную, если у вас есть мощные инструменты. Идиома C++ "выделение ресурса есть инициализация" (resource acquisition is initialization — RAII) представляет собой мощный инструмент для корректной работы с ресурсами. RAII позволяет компилятору автоматически обеспечить строгую гарантию того, что в других языках надо делать вручную. При выделении ресурса передайте его объекту-владельцу. Никогда не выделяйте несколько ресурсов в одной инструкции.
Не стоит откладывать до выполнения программы выявление ошибок, которые можно обнаружить при ее сборке. Предпочтительно писать код, который использует компилятор для проверки инвариантов в процессе компиляции, вместо того, чтобы проверять их во время работы программы. Проверки времени выполнения зависят от выполнимого кода и данных, так что вы только изредка можете полностью полагаться на них. Проверки времени компиляции, напротив, не зависят от данных и предыстории исполнения, что обычно обеспечивает более высокую степень надежности.
const — ваш друг: неизменяемые значения проще понимать, отслеживать и мотивировать, т.е. там, где это целесообразно, лучше использовать константы вместо переменных. Сделайте const описанием по умолчанию при определении значения — это безопасно, проверяемо во время компиляции (см. рекомендацию 14) и интегрируемо с системой типов C++. Не выполняйте преобразований типов с отбрасыванием const кроме как при вызове некорректной с точки зрения употребления const функции (см. рекомендацию 94).
Макрос — самый неприятный инструмент С и C++, оборотень, скрывающийся под личиной функции, кот, гуляющий сам по себе и не обращающий никакого внимания на границы ваших областей видимости. Берегитесь его!
Избегайте использования в коде литеральных констант наподобие 42 или 3.1415926. Такие константы не самоочевидны и усложняют сопровождение кода, поскольку вносят в него трудноопределимый вид дублирования. Используйте вместо них символьные имена и выражения наподобие width*aspectRatiо.
Избегайте "раздувания" областей видимости. Переменных должно быть как можно меньше, а время их жизни — как можно короче. Эта рекомендация по сути является частным случаем рекомендации 10.
Неинициализированные переменные — распространенный источник ошибок в программах на С и C++. Избегайте их, выработав привычку очищать память перед ее использованием; инициализируйте переменные при их определении.
Краткость — сестра таланта. Чересчур длинные функции и чрезмерно вложенные блоки кода зачастую препятствуют реализации принципа "одна функция — одна задача" (см. рекомендацию 5), и обычно эта проблема решается лучшим разделением задачи на отдельные части.
Объекты уровня пространств имен в разных единицах компиляции не должны зависеть друг от друга при инициализации, поскольку порядок их инициализации не определен. В противном случае вам обеспечена головная боль при попытках разобраться со сбоями в работе программы после внесения небольших изменений в ваш проект и невозможностью его переноса даже на новую версию того же самого компилятора.
Избегайте излишних зависимостей. Не включайте при помощи директивы #include определения там, где достаточно предварительного объявления.
Избегайте взаимозависимостей. Циклические зависимости возникают, когда два модуля непосредственно или опосредованно зависят друг от друга. Модуль представляет собой обособленную единицу; взаимозависимые модули не являются полностью отдельными модулями, будучи по сути частями одного большего модуля. Таким образом, циклические зависимости являются противниками модульности и представляют угрозу большим проектам. Избегайте их.
Убедитесь, что каждый написанный вами заголовочный файл компилируем самостоятельно, т.е. что он включает все заголовочные файлы, от которых зависит его содержимое.
Предотвращайте непреднамеренное множественное включение ваших заголовочных файлов директивой #include, используя в них защиту с уникальными именами.
Вы должны четко уяснить разницу между входными, выходными параметрами и параметрами, предназначенными и для ввода, и для вывода информации, а также между передачей параметров по значению и по ссылке, и корректно их использовать.
Программисты ненавидят сюрпризы. Перегружайте операторы только в случае веских на то оснований, и сохраняйте при этом их естественную семантику. Если это оказывается сложным, возможно, вы неверно используете перегрузку операторов.
Если можно записать а+b, то необходимо, чтобы можно было записать и а+=b. При определении бинарных арифметических операторов одновременно предоставляйте и их присваивающие версии, причем делайте это с минимальным дублированием и максимальной эффективностью.
Особенность операторов инкремента и декремента состоит в том, что у них есть префиксная и постфиксная формы с немного отличающейся семантикой. Определяйте операторы operator++ и operator-- так, чтобы они подражали поведению своих встроенных двойников. Если только вам не требуется исходное значение — используйте префиксные версии операторов.
Не преумножайте объекты сверх необходимости (Бритва Оккама): неявное преобразование типов обеспечивает определенное синтаксическое удобство (однако см. рекомендацию 40), но в ситуации, когда допустима оптимизация (см. рекомендацию 8) и желательно избежать создания излишних объектов, можно обеспечить перегруженные функции с сигнатурами, точно соответствующими распространенным типам аргументов, и тем самым избежать преобразования типов.
Мудрость — это знание того, когда надо воздержаться. Встроенные операторы &&, || и , (запятая) трактуются компилятором специальным образом. После перегрузки они становятся обычными функциями с весьма отличной семантикой (при этом вы нарушаете рекомендации 26 и 31), а это прямой путь к трудноопределимым ошибкам и ненадежности. Не перегружайте эти операторы без крайней необходимости и глубокого понимания.
Порядок вычисления аргументов функции не определен, поэтому никогда не полагайтесь на то, что аргументы будут вычисляться в той или иной очередности.
Существует большое количество различных видов классов, и следует знать, какой именно класс вы создаете.
Разделяй и властвуй: небольшие классы легче писать, тестировать и использовать. Они также применимы в большем количестве ситуаций. Предпочитайте такие небольшие классы, которые воплощают простые концепции, классам, пытающимся реализовать как несколько концепций, так и сложные концепции (см. рекомендации 5 и 6).
Избегайте "налога на наследство": наследование — вторая по силе после отношения дружбы взаимосвязь, которую можно выразить в C++. Сильные связи нежелательны, и их следует избегать везде, где только можно. Таким образом, следует предпочитать композицию наследованию, кроме случаев, когда вы точно знаете, что делаете и какие преимущества дает наследование в вашем проекте.
Классы, предназначенные для автономного использования, подчиняются правилам проектирования, отличным от правил для базовых классов (см. рекомендацию 32). Использование автономных классов в качестве базовых является серьезной ошибкой проектирования и его следует избегать. Для добавления специфического поведения предпочтительно вместо функций-членов добавлять обычные функции (см. рекомендацию 44). Для того чтобы добавить состояние, вместо наследования следует использовать композицию (см. рекомендацию 34). Избегайте наследования от конкретных базовых классов.
Вы любите абстракционизм? Абстрактные интерфейсы помогают вам сосредоточиться на проблемах правильного абстрагирования, не вдаваясь в детали реализации или управления состояниями. Предпочтительно проектировать иерархии, реализующие абстрактные интерфейсы, которые моделируют абстрактные концепции.
Открытое наследование позволяет указателю или ссылке на базовый класс в действительности обращаться к объекту некоторого производного класса без изменения существующего кода и нарушения его корректности.
Не применяйте открытое наследование для того, чтобы повторно использовать код (находящийся в базовом классе); открытое наследование необходимо для того, чтобы быть повторно использованным (существующим кодом, который полиморфно использует объекты базового класса).
Ответственно подходите в перекрытию функций. Когда вы перекрываете виртуальную функцию, сохраняйте заменимость; в частности, обратите внимание на пред- и постусловия в базовом классе. Не изменяйте аргументы по умолчанию виртуальных функций. Предпочтительно явно указывать перекрываемые функции как виртуальные. Не забывайте о сокрытии перегруженных функций в базовом классе.
В базовых классах с высокой стоимостью изменений (в частности, в библиотеках) лучше делать открытые функции невиртуальными. Виртуальные функции лучше делать закрытыми, или защищенными — если производный класс должен иметь возможность вызывать их базовые версии (этот совет не относится к деструкторам; см. рекомендацию 50).
Не все изменения прогрессивны: неявные преобразования зачастую приносят больше вреда, чем пользы. Дважды подумайте перед тем, как предоставить возможность неявного преобразования к типу и из типа, который вы определяете, и предпочитайте полагаться на явные преобразования (используйте конструкторы, объявленные как explicit, и именованные функции преобразования типов).
Данные-члены должны быть закрыты. Только в случае простейших типов в стиле структур языка C, объединяющих в единое целое набор значений, не претендующих на инкапсуляцию и не обеспечивающих поведение, делайте все данные-члены открытыми. Избегайте смешивания открытых и закрытых данных, что практически всегда говорит о бестолковом дизайне.
Избегайте возврата дескрипторов внутренних данных, управляемых вашим классом, чтобы клиенты не могли неконтролируемо изменять состояние вашего объекта, как своего собственного.
C++ делает закрытые члены недоступными, но не невидимыми. Там, где это оправдывается получаемыми преимуществами, следует подумать об истинной невидимости, достигаемой применением идиомы Pimpl (указателя на реализацию) для реализации брандмауэров компилятора и повышения сокрытия информации (см. рекомендации 11 и 41).
Там, где это возможно, предпочтительно делать функции не членами и не друзьями классов.
Каждая перегрузка void* operator new(parms) в классе должна сопровождаться соответствующей перегрузкой оператора void operator delete(void*, parms), где parms — список типов дополнительных параметров (первый из которых всегда std::size_t). To же относится и к операторам для массивов new[] и delete[].
Если класс определяет любую перегрузку оператора new, он должен перегрузить все три стандартных типа этого оператора — обычный new, размещающий и не генерирующий исключений. Если этого не сделать, то эти операторы окажутся скрытыми и недоступными пользователям вашего класса.