Подтвердить что ты не робот

Наиболее важные элементы в легком стандарте кодирования на С++

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

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

Итак, чтобы подчеркнуть суть здесь:

Какие элементы стандарта кодирования С++ наиболее важны для поддержки?

  • Правила ответа/голосования

    • 1 кандидат на каждый ответ, желательно с краткой мотивацией.

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

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

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

    • Не отдавайте свой голос в любом направлении по кандидатам, о которых вы не уверены. Даже если они кажутся разумными и умными или, наоборот, "что-то наверняка никто не будет использовать", ваш голос должен основываться на ясном понимании и опыте.

4b9b3361

Ответ 1

Предпочитает RAII.

Утилиты STL auto (и совместно используемые в boost и С++ 0x) могут помочь.

Ответ 2

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

Обе переменные-члены и методы будут объявлены const, а также аргументы функции. const переменные-члены обеспечивают правильное использование списка инициализаторов.

Побочный эффект этого правила: избегайте методов с побочными эффектами.

Ответ 3

Используйте С++ cast вместо C cast

использование:

  • static_cast
  • const_cast
  • reinterpret_cast
  • dynamic_cast

но никогда не прикладывает C-стиль.

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

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

Кроме того, это позволяет рецензенту искать их, а затем, чтобы кодер оправдывал их при необходимости.

Ответ 4

Используйте ссылки вместо указателей, где это возможно. Это предотвращает постоянные защитные проверки NULL.

Ответ 5

Удостоверьтесь, что уровень предупреждения вашего компилятора установлен достаточно высоким (/Wall предпочтительно), чтобы он обнаружил глупые ошибки, такие как:

if (p = 0)

когда вы действительно имели в виду

if (p == 0)

чтобы вам не приходилось прибегать к еще более сумасшедшим трюкам:

if (0 == p)

которые ухудшают читаемость вашего кода.

Ответ 6

Используйте вектор и строку вместо массивов C-стиля и char *

Используйте std::vector, когда вам нужно создать буфер данных, даже если размер исправлен.

Используйте std::string, когда вам нужна строка.

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

std::vector: Пользователь вектора всегда может найти свой размер, и при необходимости вектор может быть изменен. Его можно даже присвоить (через (& (myVector [0])) обозначение) API C. Конечно, вектор очистится после себя.

std::string: Почти по тем же причинам выше. И тот факт, что он всегда будет правильно инициализирован, что он не может быть переполнен, что он будет обрабатывать изменения изящно, например, конкатенации, и т.д., и естественным образом (используя операторы вместо функций)

Ответ 7

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

Ответ 8

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

Использовать код обработки ошибок для условий, которые вы ожидаете; используйте утверждения для условий, которые никогда не должны возникать. Обработка ошибок обычно проверяет наличие плохих входных данных; утверждения проверяют наличие ошибок в коде.

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

  • Макконнелл, Стив. Код завершен, второе издание. Microsoft Press © 2004. Глава 8 - Оборонительное программирование

Ответ 9

Знайте, кто является владельцем этой памяти.

  • как можно больше создавать объекты в стеке (бесполезно новое)
  • Избегайте передачи права собственности, если это действительно необходимо
  • Используйте RAII и интеллектуальные указатели
  • Если передача права собственности обязательна (без интеллектуальных указателей), тогда четко документируйте код (функции должны иметь недвусмысленное имя, всегда используя один и тот же шаблон имен, например "char * allocateMyString()" и "void deallocateMyString (char * p)".

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

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

Насколько это возможно, функция/объект, выделяющий память, должна быть функцией/объектом, освобождающей ее.

Ответ 10

Примечание: не налагайте SESE (Single Single Single Exit) (т.е. не запрещайте более одного return, использование break/continue/...)

В С++ это утопия, поскольку throw - это другая точка возврата. SESE имеет два преимущества в C и языках без исключения:

  • детерминированный выпуск ресурсов, который теперь аккуратно обрабатывается идиомой RAII в С++,
  • упрощение функций, что не должно вызывать беспокойства, поскольку функции должны быть короткими (как указано в правиле "одна функция, одна ответственность" )

Ответ 11

Преждевременная оптимизация - это корень всех злых

Сначала напишите безопасный и правильный код.

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

Никогда не верьте, что вы оптимизируете фрагменты кода лучше, чем компилятор.

При поиске оптимизаций изучите используемые алгоритмы и потенциально лучшие альтернативы.

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

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

И, конечно, оптимизация перед профилированием часто приводит к нулевому усилению производительности.

Ответ 12

Кудрявые фигурные скобки для любого управляющего оператора. (Благодаря собственному опыту и подкрепляется чтением Code Complete v2):

// bad example - what the writer wrote
if( i < 0 ) 
    printf( "%d\n", i );
    ++i; // this error is _very_ easy to overlook!  

// good example - what the writer meant
if( i < 0 ) {
    printf( "%d\n", i );
    ++i;
}

Ответ 13

Предпочитайте стандартный код. Предпочитаете использовать стандартные библиотеки.

Ответ 14

Только тривиальное использование?: оператор, т.е.

float x = (y > 3) ? 1.0f : -1.0f;

нормально, но это не так:

float x = foo(2 * ((y > 3) ? a : b) - 1);

Ответ 15

Используйте инструмент lint, т.е. PC-Lint. Это уловит многие из "структурных" проблем с кодированием. Значение вещей, которые читаются в реальных ошибках, а не в стиле/удобочитаемости. (Не то, что читаемость не важна, но она меньше, чем фактические ошибки).

Пример, а не требование этого стиля:

if (5 == variable)

Как способ предотвращения ошибки "непреднамеренного присваивания", пусть lint найдет его.

Ответ 16

Никогда не используйте структуры без соответствующих конструкторов

structs - это законные конструкции С++, используемые для объединения данных вместе. Тем не менее, данные должны быть всегда правильно инициализированы.

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

struct MyStruct // BAD
{
   int i ; bool j ; char * k ;
}

struct MyStruct // GOOD
{
   MyStruct() : i(0), j(true), k(NULL) : {}

   int i ; bool j ; char * k ;
}

И если они обычно инициализируются каким-то образом, предоставьте конструктор, чтобы позволить пользователю избежать инициализации структуры C-стиля:

MyStruct oMyStruct = { 25, true, "Hello" } ; // BAD
MyStruct oMyStruct(25, true, "Hello") ;      // GOOD

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

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

void doSomething()
{
   MyStruct s = { 25, true, "Hello" } ;
  // Etc.
}

void doSomethingElse()
{
   MyStruct s = { 25, true, "Hello" } ;
  // Etc.
}

// Etc.

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

Ответ 17

Не добавляйте типы или функции в глобальное пространство имен.

Ответ 18

Принцип наименьшего удивления.

Возможно, это не "вкус" правил, которые вы ищете, но я определенно положил бы его первым.

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

Он также охватывает единственную разумную меру качества кода, с которой я когда-либо сталкивался - WTF в минуту.

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

Ответ 19

Запретить t[i]=i++; f(i++,i); и т.д., поскольку нет (переносных) гарантий относительно того, что выполняется в первую очередь.

Ответ 20

Всегда, всегда, всегда выполняйте правильную инициализацию элемента данных при построении объекта.

У меня возникла проблема, когда конструктор объекта полагался на некоторую инициализацию по умолчанию для своих членов данных. Построение кода на двух платформах (Windows/Linux) дало разные результаты и трудно найти ошибку памяти. В результате элемент данных не был инициализирован в конструкторе и использовался до его инициализации. На одной платформе (Linux) компилятор инициализировал ее для того, что, по мнению автора кода, было по умолчанию. В Windows значение было инициализировано чем-то - но мусором. При использовании члена данных все пошло не так. После исправления инициализации - больше проблем.

Ответ 21

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

Обновление: здесь - это другое объяснение для "Встроенный С++", который, как представляется, исключает исключения. Он делает следующие пункты:

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

На этой странице более сложный текст, я не хотел его копировать. Кроме того, ему 10 лет, так что это может быть бесполезно, поэтому я включил часть о toolchain. Возможно, это также должно быть прочитано "если память не считается серьезной проблемой" и/или "если предсказуемый ответ в реальном времени не требуется" и т.д.

Ответ 22

Имена методов и переменных в общей схеме именования для согласованности; Я не склонен больше беспокоиться ни о чем другом при чтении источника.

Ответ 23

Публичное наследование должно моделировать принцип замещения Лискова (LSP).

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

Ответ 24

Опасайтесь C API

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

например:.

// char * d, * s ;
strcpy(d, s) ; // BAD

// std::string d, s ;
d = s ;        // GOOD

Никогда не используйте strtok

strtok не реентерабелен. Это означает, что если один strtok запущен, а другой еще не закончен, он повредит "внутренние данные" другого.

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

Использование C API означает использование необработанных типов, что может привести к интересным ошибкам, таким как переполнение буфера (и потенциальное повреждение стека), когда sprintf заходит слишком далеко (или обрезание строк при использовании snprintf, что является своего рода повреждением данных). Даже при работе с необработанными данными malloc можно легко злоупотреблять, как показано в следующем коде:

int * i = (int *) malloc(25) ; // Now, I BELIEVE I have an array of 25 ints!
int * j = new int[25] ;        // Now, I KNOW I have an array of 25 ints!

Etc. и т.д..

Как и для strtok: C и С++ - это языки с поддержкой стека, которые позволяют пользователю не заботиться о том, какие функции выше его собственных в стеке, и какие функции будут вызываться под его собственным в стеке. strtok удаляет эту свободу "не заботясь"

Ответ 25

Избегайте использования сгенерированного конструктора копирования и оператора = по умолчанию.

  • Если вы хотите, чтобы ваш объект был гибким.
    • Если каждый атрибут может быть тривиально скопирован, четко прокомментируйте, что вы используете неявный конструктор копирования и оператор = намеренно.
    • В противном случае напишите свои собственные конструкторы, используя поле инициализации для инициализации атрибутов и следуя порядку заголовка (который является реальным порядком построения).
  • Если вы еще не знаете (опция по умолчанию) или вы думаете, что не хотите копировать объекты определенного класса, объявите его конструктор копирования и operator = as private. Таким образом, компилятор сообщит вам, когда вы делаете то, что вы не хотите делать.
    class foo
    {
       //...
    private:
       foo( const foo& );
       const foo& operator=( const foo& );
    };

Или в более чистый способ, если вы используете boost:

    class foo : private boost::noncopyable
    {
      ...
    };

Ответ 26

Независимо от рекомендаций, сделайте очень легким распознать применимость: чем меньше у вас выбора, тем меньше времени вы выберете. И чем проще, тем лучше для мозгового анализа кода.

Примеры "трудно распознать":

  • Отсутствие фигурных скобок, если только одна строка в условном теле
  • Используйте K & R размещение скобок для пространств имен, но поместите скобки под условиями в коде определения функции.
  • ...

Ответ 27

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

См. также Контрольный список для написания оператора constuctor и оператора присваивания в С++

Ответ 28

Наверное, без проблем, но тем не менее важное правило:

Избегайте поведения undefined.

Там очень много этого в С++, и, вероятно, невозможно написать нетривиальное приложение, которое никак не зависит от него, но общее правило все равно должно быть "undefined поведение плохо". (Потому что, к сожалению, есть программисты на С++, которые чувствуют, что "это работает на моей машине/компиляторе" достаточно хорошо.)

Если вам нужно полагаться на это, дайте понять всем, что, почему, где и как.

Ответ 29

Пропустить входные аргументы с помощью ссылки const и вывода или ввода-вывода с помощью указателя. Это правило вышло из руководства по стилю Google.

Я имел абсолютное отвращение к указателям и предпочитал использовать ссылки, когда это было возможно (как это предлагает один из плакатов в этом потоке). Тем не менее, принятие этого вывода-arg-as-pointer соглашение сделало мои функции более читаемыми. Например,

SolveLinearSystem(left_hand_side, right_hand_side, &params);

дает понять, что "params" записывается в.

Ответ 30

Требуются фигурные скобки, если у вас есть несколько шагов отступов:

if (bla) {
  for (int i = 0; i < n; ++i)
    foo();
}

Это помогает сохранить отступы в соответствии с тем, как компилятор видит код.