Пользователь
0,0
рейтинг
24 марта 2013 в 14:23

Разработка → 90 рекомендаций по стилю написания программ на C++ перевод

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

1 Введение


Настоящий документ содержит рекомендации по написанию программ на языке C++.

Рекомендации основаны на установившихся стандартах, собранных из различных источников, личного опыта, частных требований и потребностей определённых проектов, а также почерпнутых из источников (см. ниже).

Но для появления ещё одного списка рекомендаций, помимо указанных источников, есть несколько причин. Основная причина — их излишняя обобщённость, поскольку зачастую требуется задать частные правила (в особенности правила именования). Данный документ содержит комментарии, что делает его более удобным в использовании при проведении ревизий кода, чем другие уже существующие документы. К тому же, рекомендации по программированию обычно вперемешку содержат описания проблем стиля и технических проблем, что не совсем удобно. Этот документ не содержит каких-либо технических рекомендаций по C++, делая упор на вопросах стиля.

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

1.1 Формат документа

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

Рекомендации отображаются следующим образом:

n. Короткое описание рекомендации.
// Пример кода (если возможно)
Объяснение, происхождение и дополнительная информация.

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

1.2 Важность рекомендаций

Рекомендации разделены по степени важности: обязательные, настоятельно рекомендуемые и общие.

2 Общие рекомендации


1. Допускаются любые нарушения рекомендаций, если это улучшает читаемость.

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

2. Правила могут быть нарушены, если против них есть персональные возражения.

Это попытка создать набор общих рекомендаций, не навязывая всем единый стиль. Опытные программисты обычно всё равно подгоняют стиль под себя. Подобный список рекомендаций, имеющийся под рукой (или хотя бы требование ознакомиться с ним), обычно заставляет людей задумываться о стиле программирования и оценке их собственных практик в этой области.

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

3 Соглашения об именовании


3.1 Общие соглашения об именовании

3. Имена, представляющие типы, должны быть обязательно написаны в смешанном регистре, начиная с верхнего.

Line, SavingsAccount

Общая практика в сообществе разработчиков C++.

4. Имена переменных должны быть записаны в смешанном регистре, начиная с нижнего.

line, savingsAccount

Общая практика в сообществе разработчиков C++. Позволяет легко отличать переменные от типов, предотвращает потенциальные коллизии имён, например: Line line;

5. Именованные константы (включая значения перечислений) должны быть записаны в верхнем регистре с нижним подчёркиванием в качестве разделителя.

MAX_ITERATIONS, COLOR_RED, PI

Общая практика в сообществе разработчиков C++. Использование таких констант должно быть сведено к минимуму. В большинстве случаев реализация значения в виде метода — лучшее решение:

  int getMaxIterations() // НЕЛЬЗЯ: MAX_ITERATIONS = 25
  {
    return 25;
  }

Эта форма более читаемая и гарантирует единый интерфейс к значениям, хранящимся в классе.

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

getName(), computeTotalWidth()

Совпадает с правилом для переменных, но отличие между ними состоит в их специфических формах.

7. Названия пространств имён следует записывать в нижнем регистре.

model::analyzer, io::iomanager, common::math::geometry

Общая практика в сообществе разработчиков C++.

8. Следует называть имена типов в шаблонах одной заглавной буквой.

template<class T> ...
template<class C, class D> ...

Общая практика в сообществе разработчиков C++. Позволяет выделить имена шаблонов среди других используемых имён.

9. Аббревиатуры и сокращения в именах должны записываться в нижнем регистре.
exportHtmlSource(); // НЕЛЬЗЯ: exportHTMLSource();
openDvdPlayer();    // НЕЛЬЗЯ: openDVDPlayer();

Использование верхнего регистра может привести к конфликту имён, описанному выше. Иначе переменные бы имели имена dVD, hTML и т. д., что не является удобочитаемым. Другая проблема уже описана выше; когда имя связано с другим, читаемость снижается; слово, следующее за аббревиатурой, не выделяется так, как следовало бы.

10. Глобальные переменные всегда следует использовать с оператором разрешения области видимости (::).

::mainWindow.open(), ::applicationContext.getName()

Следует избегать использования глобальных переменных. Предпочтительнее использование синглтонов.

11. Членам класса с модификатором private следует присваивать суффикс-подчёркивание.

class SomeClass {
  private:
    int length_;
}

Не считая имени и типа, область видимости — наиболее важное свойство переменной. Явное указание модификатора доступа в виде подчёркивания избавляет от путаницы между членами класса и локальными переменными. Это важно, поскольку переменные класса имеют большее значение, нежели переменные в методах, и к ним следует относиться более осторожно.

Дополнительным эффектом от суффикса-подчёркивания является разрешение проблемы именования в методах, устанавливающих значения, а также в конструкторах:

  void setDepth (int depth)
  {
    depth_ = depth;
  }

Проблема заключается в том, что существует два варианта подчёркивания — в виде суффикса и в виде префикса. Оба варианта широко используются, но рекомендуется именно первый вариант, потому что он обеспечивает лучшую читаемость. Следует отметить, что определение модификатора доступа у переменных — иногда спорный вопрос. Хотя кажется, что рекомендуемая практика набирает сторонников и становится всё более распространённой в среде профессионалов.

12. Настраиваемым переменным следует давать то же имя, что и у их типа.

void setTopic(Topic* topic)      // НЕЛЬЗЯ: void setTopic(Topic* value)
                                 // НЕЛЬЗЯ: void setTopic(Topic* aTopic)
                                 // НЕЛЬЗЯ: void setTopic(Topic* t)

void connect(Database* database) // НЕЛЬЗЯ: void connect(Database* db)
                                 // НЕЛЬЗЯ: void connect (Database* oracleDB)

Сокращайте сложность путём уменьшения числа используемых терминов и имён. Также упрощает распознавание типа просто по имени переменной.

Если по какой-то причине эта рекомендация кажется неподходщей, это означает, что имя типа выбрано неверно.

Не являющиеся настраиваемыми переменные могут быть названы по их назначению и типу:

  Point  startingPoint, centerPoint;
  Name   loginName;


13. Все имена следует записывать по-английски.

fileName;   // НЕ РЕКОМЕНДУЕТСЯ: imyaFayla

Английский наиболее предпочитетелен для интернациональной разработки.

14. Переменные, имеющие большую область видимости, следует называть длинными именами, имеющие небольшую область видимости — короткими.

Имена временных переменных, использующихся для хранения временных значений или индексов, лучше всего делать короткими. Программист, читающий такие переменные, должен иметь возможность предположить, что их значения не используются за пределами нескольких строк кода. Обычно это переменные i, j, k, l, m, n (для целых), а также c и d (для символов).

15. Имена объектов не указываются явно, следует избегать указания названий объектов в именах методов.

line.getLength();   // НЕ РЕКОМЕНДУЕТСЯ: line.getLineLength();

Второй вариант смотрится вполне естественно в объявлении класса, но совершенно избыточен при использовании, как это и показано в примере.

(Пункт № 16 отсутствует.— Примечание переводчика.)

3.2 Особые правила именования

17. Слова get/set должны быть использованы везде, где осуществляется прямой доступ к атрибуту.

employee.getName();
employee.setName(name);

matrix.getElement(2, 4);
matrix.setElement(2, 4, value);

Общая практика в сообществе разработчиков C++. В Java это соглашение стало более-менее стандартным.

18. Слово compute может быть использовано в методах, вычисляющих что-либо.

valueSet->computeAverage();
matrix->computeInverse()

Дайте читающему сразу понять, что это времязатратная операция.

19. Слово find может быть использовано в методах, осуществляющих какой-либо поиск.

vertex.findNearestVertex();
matrix.findMinElement();

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

20. Слово initialize может быть использовано там, где объект или сущность инициализируется.

printer.initializeFontSet();

Следует отдавать предпочтение американскому варианту initialize, нежели британскому initialise. Следует избегать сокращения init.

21. Переменным, представляющим GUI, следует давать суффикс, соответствующий имени типа компонента.

mainWindow, propertiesDialog, widthScale, loginText,
leftScrollbar, mainForm, fileMenu, minLabel, exitButton, yesToggle и т. д.

Улучшает читаемость, поскольку имя даёт пользователю прямую подсказку о типе переменной и, следовательно, ресурсах объектов.

22. Множественное число следует использовать для представления наборов (коллекций) объектов.

vector<Point>  points;
int            values[];

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

23. Префикс n следует использовать для представления числа объектов.

nPoints, nLines

Обозначение взято из математики, где оно является установившимся соглашением для обозначения числа объектов.

24. Суффикс No следует использовать для обозначения номера сущности.

tableNo, employeeNo

Обозначение взято из математики, где оно является установившимся соглашением для обозначения номера сущности.

Другой неплохой альтернативой является префикс i: iTable, iEmployee. Он ясно даёт понять, что перед нами именованный итератор.

25. Переменным-итераторам следует давать имена i, j, k и т. д.

for (int i = 0; i < nTables); i++) {
  :
}

for (vector<MyClass>::iterator i = list.begin(); i != list.end(); i++) {
  Element element = *i;
  ...
}

Обозначение взято из математики, где оно является установившимся соглашением для обозначения итераторов.

Переменные с именами j, k и т. д. рекомендуется использовать только во вложенных циклах.

26. Префикс is следует использовать только для булевых (логических) переменных и методов.

isSet, isVisible, isFinished, isFound, isOpen

Общая практика в сообществе разработчиков C++, иногда используемая и в Java.

Использование этого префикса избавляет от таких имён, как status или flag. isStatus или isFlag просто не подходят, и программист вынужден выбирать более осмысленные имена.

В некоторых ситуациях префикс is лучше заменить на другой: has, can или should:

  bool hasLicense();
  bool canEvaluate();
  bool shouldSort();


27. Симметричные имена должны использоваться для соответствующих операций.

get/set, add/remove, create/destroy, start/stop, insert/delete,
increment/decrement, old/new, begin/end, first/last, up/down, min/max,
next/previous, old/new, open/close, show/hide, suspend/resume, и т. д.

Уменьшайте сложность за счёт симметрии.

28. Следует избегать сокращений в именах.

computeAverage();   // НЕЛЬЗЯ: compAvg();

Рассмотрим два вида слов. Первые — обычные слова, перечисляемые в словарях, которые нельзя сокращать. Никогда не сокращайте:

cmd    вместо    command
cp     вместо    copy
pt     вместо    point
comp   вместо    compute
init   вместо    initialize
и т. д.


Второй вид — слова, специфичные для какой-либо области, которые известны по своему сокращению/аббревиатуре. Их следует записывать сокращённо. Никогда не пишите:

 HypertextMarkupLanguage  вместо    html
 CentralProcessingUnit    вместо    cpu
 PriceEarningRatio        вместо    pe
 и т. д.


29. Следует избегать дополнительного именования указателей.

Line* line; // НЕ РЕКОМЕНДУЕТСЯ: Line* pLine;
            // НЕ РЕКОМЕНДУЕТСЯ: Line* linePtr;

Множество переменных в C/C++ являются указателями. Только в том случае, когда тип объекта в языке C++ особенно важен, имя должно отражать его.

30. Нельзя давать булевым (логическим) переменным имена, содержащие отрицание.

bool isError; // НЕЛЬЗЯ: isNoError
bool isFound; // НЕЛЬЗЯ: isNotFound

Проблема возникает, когда такое имя используется в конъюнкции с оператором логического отрицания, что влечёт двойное отрицание. Результат не обязательно будет отрицанием !isNotFound.

31. Константы в перечислениях могут иметь префикс — общее имя типа.

enum Color {
  COLOR_RED,
  COLOR_GREEN,
  COLOR_BLUE
};

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

Другим подходом является обращение к константам по их общему типу: Color::RED, Airline::AIR_FRANCE и т. д.

Обратите внимание, что имя перечисления обычно записано в единственном числе, например: enum Color {...}. Имя во множественном числе хорошо выглядит при объявлении, но не очень хорошо подходит для практического использования.

32. Классам исключений следует присваивать суффикс Exception.

class AccessException
{
  :
}

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

33. Функциям (методам, возвращающим какие-либо значения) следует давать имена в зависимости от того, что они возвращают, а процедурам — в зависимости от того, что они выполняют (методы void).

Улучшайте читаемость. Такое именование даёт понять, что метод делает, а что нет, а также избавляет код от потенциальных побочных эффектов.

4 Файлы


4.1 Файлы исходных кодов

34. Заголовочным файлам C++ следует давать расширение .h (предпочтительно) либо .hpp. Файлы исходных кодов могут иметь расширения .c++ (рекомендуется), .C, .cc либо .cpp.

MyClass.c++, MyClass.h

Это расширения, одобряемые стандартом C++.

35. Класс следует объявлять в заголовочном файле и определять (реализовывать) в файле исходного кода, имена файлов совпадают с именем класса.

MyClass.h, MyClass.c++


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

36. Все определения должны находиться в файлах исходного кода.

class MyClass
{
public:
  int getValue () {return value_;}  // НЕЛЬЗЯ!
  ...

private:
  int value_;
}

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

37. Содержимое файлов не должно превышать 80 колонок.

80 колонок — широко распространённое разрешение для редакторов, эмуляторов терминалов, принтеров и отладчиков; файлы передаются между различными людьми, поэтому нужно придерживаться этих ограничений. Уместная разбивка строк улучшает читаемость при совместной работе над исходным кодом.

38. Нельзя использовать специальные символы (например, TAB) и разрывы страниц.

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

39. Незавершённость разбитых строк должна быть очевидна.

totalSum = a + b + c +
           d + e;

function (param1, param2,
          param3);

setText ("Long line split"
         "into two parts.");

for (int tableNo = 0; tableNo < nTables;
     tableNo += tableStep) {
  ...
}

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

В общем случае:
  • разрыв после запятой;
  • разрыв после оператора;
  • выравнивание новой строки с началом выражения на предыдущей строке.


4.2 Включения файлов

40. Заголовочные файлы должны содержать защиту от вложенного включения.

#ifndef COM_COMPANY_MODULE_CLASSNAME_H
#define COM_COMPANY_MODULE_CLASSNAME_H
  :
#endif // COM_COMPANY_MODULE_CLASSNAME_H

Конструкция позволяет избегать ошибок компиляции. Это соглашение позволяет увидеть положение файла в структуре проекта и предотвращает конфликты имён.

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

#include <fstream>
#include <iomanip>

#include <qt/qbutton.h>
#include <qt/qtextfield.h>

#include "com/company/ui/PropertiesDialog.h"
#include "com/company/ui/MainWindow.h"

Пути включения не должны быть абсолютными. Вместо этого следует использовать директивы компилятора.

42. Директивы включения должны располагаться только в начале файла.

Общая практика. Избегайте нежелательных побочных эффектов, которые может вызвать «скрытое» включение где-то в середине файла исходного кода.

5 Выражения


5.1 Типы

43. Локальные типы, используемые в одном файле, должны быть объявлены только в нём.

Улучшает сокрытие информации.

44. Разделы класса public, protected и private должны быть отсортированы. Все разделы должны быть явно указаны.

Сперва должен идти раздел public, что избавит желающих ознакомиться с классом от чтения разделов protected/private.

45. Приведение типов должно быть явным. Никогда не полагайтесь на неявное приведение типов.

floatValue = static_cast<float>(intValue); // НЕЛЬЗЯ: floatValue = intValue;

Этим программист показывает, что ему известно о различии типов, что смешение сделано намеренно.

5.2 Переменные

46. Следует инициализировать переменные в месте их объявления.

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

  int x, y, z;
  getCenter(&x, &y, &z);

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

47. Переменные никогда не должны иметь двойной смысл.

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

48. Следует избегать использования глобальных переменных.

Не существует причины использовать глобальные переменные в C++ (на самом деле существует.— Примечание переводчика). То же касается глобальных функций и (статических) переменных, область видимости которых — весь файл.

49. Не следует объявлять переменные класса как public.

Эти переменные нарушают принципы сокрытия информации и инкапсуляции. Вместо этого используйте переменные с модификатором private и соответствующие функции доступа. Исключение — класс без поведения, практически структура данных (эквивалент структур языка C). В этом случае нет смысла скрывать эти переменные.

Обратите внимание, что структуры в языке C++ оставлены только для совместимости с C; их использование ухудшает читаемость кода. Вместо структур используйте классы.

(Пункт № 50 отсутствует.— Примечание переводчика.)

51. Символ указателя или ссылки в языке C++ следует ставить сразу после имени типа, а не с именем переменной.

float* x; // НЕ РЕКОМЕНДУЕТСЯ: float *x; 
int& y;   // НЕ РЕКОМЕНДУЕТСЯ: int &y;

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

(Пункт № 52 отсутствует.— Примечание переводчика.)

53. Следует избегать неявного сравнения булевых (логических) переменных и указателей с нулём.

if (nLines != 0)  // НЕ РЕКОМЕНДУЕТСЯ: if (nLines)
if (value != 0.0) // НЕ РЕКОМЕНДУЕТСЯ: if (value)

Стандарт C++ не гарантирует, что значения переменных int и float, равные нулю, будут представлены как бинарный 0. Также при явном сравнении видно сравниваемый тип.

Логично было бы предположить, что также и указатели не следует неявно сравнивать с нулём, например, if (line == 0) вместо if (line). Последнее является очень распространённой практикой в C/C++, поэтому также может быть использовано.

54. Переменные следует объявлять в как можно меньшей области видимости.

Это упрощает контроль над действием переменной и сторонними эффектами.

5.3 Циклы

55. Нельзя включать в конструкцию for() выражения, не относящиеся к управлению циклом.

sum = 0;                       // НЕЛЬЗЯ: for (i = 0, sum = 0; i < 100; i++)
for (i = 0; i < 100; i++)                sum += value[i];
  sum += value[i];

Улучшайте поддержку и читаемость. Строго разделяйте контроль над циклом и то, что в нём содержится.

56. Переменные, относящиеся к циклу, следует инициализировать непосредственно перед ним.

isDone = false;           // НЕ РЕКОМЕНДУЕТСЯ: bool isDone = false;
while (!isDone) {         //      :
  :                       //      while (!isDone) {
}                         //        :
                          //      }


57. Можно избегать циклов do-while.

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

Циклы do-while вообще не являются острой необходимостью. Любой такой цикл может быть заменён на цикл while или for.

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

58. Следует избегать использования break и continue в циклах.

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

(Пункт № 59 отсутствует.— Примечание переводчика.)

60. Для бесконечных циклов следует использовать форму while (true) .

while (true) {
  :
}

for (;;) {  // НЕТ!
  :
}

while (1) { // НЕТ!
  :
}

Проверка на единицу не является необходимой и бессмысленна. Форма for (;;) не очень читаема; также не является очевидным, что цикл бесконечный.

5.4 Условные выражения

61. Строго избегайте сложных уловных выражений. Вместо этого вводите булевы переменные.

bool isFinished = (elementNo < 0) || (elementNo > maxElement);
bool isRepeatedEntry = elementNo == lastElement;
if (isFinished || isRepeatedEntry) {
  :
}

// NOT:
if ((elementNo < 0) || (elementNo > maxElement)||
     elementNo == lastElement) {
  :
}

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

62. Ожидаемую часть следует располагать в части if, исключение — в части else.

bool isOk = readFile (fileName);
if (isOk) {
  :
}
else {
  :
}

Это позволяет убедиться, что исключения не вносят неясности в нормальный ход выполнения. Важно для читаемости и производительности.

63. Условие следует размещать в отдельной строке.

if (isDone)       // НЕ РЕКОМЕНДУЕТСЯ: if (isDone) doCleanup();
  doCleanup();

Применяется для отладки.

64. Следует строго избегать исполнимых выражений в условиях.

File* fileHandle = open(fileName, "w");
if (!fileHandle) {
  :
}

// НЕЛЬЗЯ:
if (!(fileHandle = open(fileName, "w"))) {
  :
}

Исполняемые выражения в условиях усложняют читаемость. Особенно это касается новичков в С/С++.

5.5 Разное

65. Следует избегать «магических» чисел в коде. Числа, отличные от 0 или 1, следует объявлять как именованные константы.

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

66. Константы с плавающей точкой следует записывать с десятичной точкой и с указанием по крайней мере одной цифры после запятой.

double total = 0.0;    // НЕ РЕКОМЕНДУЕТСЯ:  double total = 0;
double speed = 3.0e8;  // НЕ РЕКОМЕНДУЕТСЯ:  double speed = 3e8;

double sum;
:
sum = (a + b) * 10.0;

Это подчёркивает различные подходы при работе с целыми числами и числами с плавающей точкой. С точки зрения математики, эти две модели совершенно различны и не совместимы.

А также (как это показано в последнем примере выше) делается акцент на типе переменной (sum) в том месте, где это не является очевидным.

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

double total = 0.5;  // НЕ РЕКОМЕНДУЕТСЯ:  double total = .5;


Система чисел и выражений в C++ заимствована из математики, и следует придерживаться традиционных форм записи, где это возможно. Помимо прочего, 0.5 — более читаемо, чем .5 (первый вариант никак не спутать с числом 5).

68. У функций нужно обязательно указывать тип возвращаемого значения.

int getValue()   // НЕЛЬЗЯ: getValue()
{
  :
}

Если это не указано явно, C++ считает, что возвращаемое значение имеет тип int. Никогда нельзя полагаться на это, поскольку такой способ может смутить программистов, не знакомых с ним.

69. Не следует использовать goto.

Этот оператор нарушает принципы структурного программирования. Следует использовать только в очень редких случаях (например, для выхода из глубоко вложенного цикла), когда иные варианты однозначно ухудшат читаемость.

70. Следует использовать «0» вместо «NULL».

NULL является частью стандартной библиотеки C и устарело в C++.

6 Оформление и комментарии


6.1 Оформление

71. Основной отступ следует делать в два пробела.

for (i = 0; i < nElements; i++)
  a[i] = 0;

Отступ в один пробел достаточно мал, чтобы отражать логическую структуру кода. Отступ более 4 пробелов делает глубоко вложенный код нечитаемым и увеличивает вероятность того, что строки придётся разбивать. Широко распространены варианты в 2, 3 или 4 пробела; причём 2 и 4 — более широко.

72. Блоки кода следует оформлять так, как показано в примере 1 (рекомендуется) или в примере 2, но ни в коем случае не так, как показано в примере 3. Оформление функций и классов должно следовать примеру 2.

while (!done) {
  doSomething();
  done = moreToDo();
}


while (!done)
{
  doSomething();
  done = moreToDo();
}


while (!done)
  {
    doSomething();
    done = moreToDo();
  }

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

73. Объявления классов следует оформлять следующим образом:

class SomeClass : public BaseClass
{
  public:
    ...

  protected:
    ...

  private:
    ...
}

Частное следствие из правила, указанного выше.

74. Определения методов следует оформлять следующим образом:

void someMethod()
{
  ...
}

Следствие из правила, указанного выше.

75. Конструкцию if-else следует оформлять следующим образом:

if (condition) {
  statements;
}

if (condition) {
  statements;
}
else {
   statements;
}

if (condition) {
  statements;
}
else if (condition) {
  statements;
}
else {
  statements;
}

Следствие из правила, указанного выше. Причём написание else на той же строке, где стоит закрывающая фигурная скобка первого блока, не является запрещённым:

  if (condition) {
    statements;
  } else {
    statements;
  }

Лучше каждую часть if-else помещать на отдельной строке. Это упрощает действия с кодом, например, перемещение блока else.

76. Цикл for следует оформлять следующим образом:

for (initialization; condition; update) {
  statements;
}

Следствие из правила, указанного выше.

77. Цикл for с пустым телом следует оформлять следующим образом:

for (initialization; condition; update)
  ;

Делает акцент для читающего на том, что тело пусто. Однако циклов, не имеющих тела, следует избегать.

78. Цикл while следует оформлять следующим образом:

while (condition) {
  statements;
}

Следствие из правила, указанного выше.

79. Цикл do-while следует оформлять следующим образом:

do {
  statements;
} while (condition);

Следствие из правила, указанного выше.

80. Конструкцию switch следует оформлять следующим образом:

switch (condition) {
  case ABC :
    statements;
    // Отсутствует "break"

  case DEF :
    statements;
    break;

  case XYZ :
    statements;
    break;

  default :
    statements;
    break;
}

Обратите внимание, что каждое слово case имеет отступ относительно всей конструкции, что помогает её выделить. Также обратите внимание на пробелы перед двоеточиями. Если где-то отсутствует ключевое слово break, то предупреждением об этом должен служить комментарий. Программисты часто забывают ставить это слово, поэтому случай нарочного его пропуска должен описываться специально.

81. Конструкцию try-catch следует оформлять следующим образом:

try {
  statements;
}
catch (Exception& exception) {
  statements;
}

Следствие из правила, указанного выше. Вопросы, касающиеся закрывающих фигурных скобок у конструкции if-else, применимы и здесь.

82. Если конструкция if-else содержит только одно выражение в теле, фигурные скобки можно опускать.

if (condition)
  statement;

while (condition)
  statement;

for (initialization; condition; update)
  statement;

Рекомендуется всё же не опускать фигурные скобки.

83. Возвращаемый функцией тип может располагаться над именем самой функции.

void
MyClass::myMethod(void)
{
  :
}

Так функции выровнены в одну колонку.

6.2 Пробелы

84.

— Операторы следует отбивать пробелами.
— После зарезервированных ключевых слов языка C++ следует ставить пробел.
— После запятых следует ставить пробелы.
— Двоеточия следует отбивать пробелами.
— После точек с запятой в цикле for следует ставить пробелы.

a = (b + c) * d; // НЕ РЕКОМЕНДУЕТСЯ: a=(b+c)*d

while (true)   // НЕ РЕКОМЕНДУЕТСЯ: while(true) 
{
  ...

doSomething(a, b, c, d);  // НЕ РЕКОМЕНДУЕТСЯ: doSomething(a,b,c,d);

case 100 :  // НЕ РЕКОМЕНДУЕТСЯ: case 100:

for (i = 0; i < 10; i++) {  // НЕ РЕКОМЕНДУЕТСЯ: for(i=0;i<10;i++){
  ...

Выделяет отдельные части выражений. Улучшает читаемость. Сложно дать всеобъемлющий набор рекомендаций относительно пробелов в языке C++. Рекомендации выше должны показать общие принципы.

85. После имён методов может идти пробел, если далее следует другое имя.

doSomething (currentFile);

Выделяет отдельные имена. Улучшает читаемость. Если далее нет никакого имени, пробел можно опускать (doSomething()).

Другим подходом является указание пробела сразу после открывающей скобки. Использующие его также обычно ставят пробел и перед закрывающей скобкой: doSomething( currentFile );. Это позволяет выделять отдельные имена; пробел перед закрывающей скобкой выглядит неестественно, но без него выражение выглядит несимметрично (doSomething( currentFile);).

86. Логические блоки в коде следует отделять пустой строкой.

Matrix4x4 matrix = new Matrix4x4();

double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);

matrix.setElement(1, 1,  cosAngle);
matrix.setElement(1, 2,  sinAngle);
matrix.setElement(2, 1, -sinAngle);
matrix.setElement(2, 2,  cosAngle);

multiply(matrix);

Улучшает читаемость.

87. Методы рекомендуется отделять тремя пустыми строками.

Это позволяет лучше их выделять.

88. Переменные в объявлениях можно выравнивать.

AsciiFile* file;
int        nPoints;
float      x, y;

Улучшает читаемость. Чётче видны пары тип — переменная.

89. Используйте выравнивание везде, где это улучшает читаемость.

if      (a == lowValue)    compueSomething();
else if (a == mediumValue) computeSomethingElse();
else if (a == highValue)   computeSomethingElseYet();

value = (potential        * oilDensity)   / constant1 +
        (depth            * waterDensity) / constant2 +
        (zCoordinateValue * gasDensity)   / constant3;

minPosition     = computeDistance(min,     x, y, z);
averagePosition = computeDistance(average, x, y, z);

switch (value) {
  case PHASE_OIL   : strcpy(phase, "Oil");   break;
  case PHASE_WATER : strcpy(phase, "Water"); break;
  case PHASE_GAS   : strcpy(phase, "Gas");   break;
}

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

6.3 Комментарии

90. Сложный код, написанный с использованием хитрых ходов, следует не комментировать, а переписывать!

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

91. Все комментарии следует писать на английском.

В интернациональной среде английский — предпочтительный язык.

92. Используйте // для всех комментариев, включая многострочные.
// Комментарий, расположенный
// на нескольких строках.

Если следовать этой рекомендации, многострочные комментарии /* */ можно использовать для отладки и иных целей.

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

93. Комментарии следует располагать так, чтобы они относились к тому, что они описывают.

while (true) {       // НЕ РЕКОМЕНДУЕТСЯ:  while (true) { 
  // Do something                          // Do something
  something();                               something();
}                                          }

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

94. Комментарии к классам и заголовкам методов следует делать в соответствии с соглашениями JavaDoc.

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

Подобные средства есть и в C++. Они следуют тем же соглашениям о синтаксисе тегов, что и JavaDoc (см., например, Doc++ или Doxygen).

7 Ссылки

Перевод: geosoft.no
Dyzzet @Di_Zed
карма
30,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (213)

  • +10
    Много спорного, хотя бы о выравнивании и символы указателя/ссылки. Дополнительное именование указателей часто очень выручает, а выделение переменной класса подчеркиванием вместо логичного this. нелепа.
    Хотя конечно каждому своё, кому то наоборот что я перечислил кажется удобным, а называть переменные tHiSiSvArIaBlE удобным.
    • 0
      Да, мне и самому некоторые моменты кажутся спорными (например, пункт № 48). Но документ довольно целостный, как мне кажется.
  • +6
    Не согласен только с 11 пунктом. Очень многие привыкли, что перед скрытыми переменными добавляется 'm' (mDepth), к тому же все сеттеры должны иметь основной параметр с именем value, иначе получается тавтология.

    void setDepth (int value)
    {
    mDepth = value;
    }
    • +6
      Не очень понял, почему заминусовали человека.
      Префикс «m» у скрытых членов класса — достаточно популярный coding style, и многим, включая меня, он кажется удобным. А если Ваша IDE имеет средства автодополнения, то, написав «m», Вы сразу получаете список всех скрытых членов класса. С постфиксом "_" такого не получится, да и, как кажется мне, он куда менее нагляден.

      К слову, по причине использования многими программистами IDE с автодополнениями, я также рекомендую везде писать get/set, т.к. можно просто вбить «get» и получить список всех функции класса, которые возвращают значения.
      • 0
        Собственно, ниже в комментах на это тоже указали (раз, два, три).
      • 0
        Это изначально спорный вопрос – сколько людей, столько и мнений. Если посмотреть проекты на github, то много кто использует именно такую нотацию (например вот, библиотека folly от facebook).
        • +3
          Я не спорю с этим. Например, Скотт Майерс тоже пишет с постфиксом "_". Другое дело, что использование префиксов выглядит куда логичнее — это первые буквы при наборе переменной, а, значит, во всяких списках переменных классов они будут удобно расположены и отсортированы в одном месте. Можно использовать _member, можно mMember, можно m_member, но во всех случаях первый символ у Вас однозначно будет указывать на внутренний член класса. Это правда дико удобно.
          • +1
            _member нельзя использовать — подчеркивание в начале слово зарезервивровано для компилятора… А для списка всех членов в текстовом редакторе открывается второе окно с описанием класса. Никто из динозавров IDE не использует.
            • 0
              Вроде бы зарезервированы идентификаторы, начинающиеся с двух подчёркиваний или подчёркивания и заглавной буквы. В boost подчёркивание ставят в конце, т. е. member_.
              • 0
                Зарезервированы идентификаторы:
                * содержащие два подчёркивания подряд где угодно,
                * начинающиеся с подчёркивания и большой буквы,
                * в глобальном namespace — начинающиеся с подчёркивания,
                * имена из стандартной библиотеки.
                • 0
                  Ну да, получается, что _member — вполне себе адекватное имя для члена класса, ибо не global namespace.
          • +2
            Я не сиплюсплюсник, но есть некоторые возражения. Первые символы действительно важны для группировки. Они отражают то, как мыслит программист. И хотелось бы, чтобы мысли вращались вокруг предметной области, а не реализации. Т.е. важнее то, что значит переменная, а не то, где она находится и ее тип.

            А вообще, очень бы неплохо было бы, чтобы IDE поддерживали не просто выпадающий список, но и «кнопки» для группировки. Т.е. выпадающий список по умолчанию, но сверху (снизу) тулбар, с «private», «static» и т.д.
  • +1
    Никак не могу уловить суть рекомендации 93. Комментарии следует располагать так, чтобы они относились к тому, что они описывают.
    Мне кажется, что в примере оба варианта (рекомендуемый и не рекомендуемый) одинаковы. Или я чего-то не замечаю…
    • +1
      Обратите внимание на вторую строку.
      • +5
        Спасибо, действительно не заметил отступ… Хотя смысл рекомендации для меня очевиднее не стал.

        Кстати, некоторые рекомендации можно было бы поправить с учетом С++11, например:
        — 31. Константы в перечислениях могут иметь префикс — общее имя типа: используйте enum class;
        — 66. Константы с плавающей точкой следует записывать с десятичной точкой и с указанием по крайней мере одной цифры после запятой: используйте литералы;
        — 70. Следует использовать «0» вместо «NULL»: используйте nullptr.
        • 0
          Документ 2011 года, но редактировать я его не стал. Сам пользуюсь теми вариантами, которые указаны Вами.
        • –1
          Учусь в институте, на c++ только лабы и курсовые. Всегда думал, что правильнее использовать NULL, ибо 0 — цифра, NULL — скорее пустота. Даже в небольших лабах это дает повышение читабельности, зачем нужно использовать 0, если используется не c++11?
          • 0
            0 — C++98, C++03. В C++11 тоже можно 0, но лучше nullptr.
            • +2
              То что можно и видимо нужно — я понимаю. Не понимаю почему, ведь 0 — цифра, а NULL — пустота. В целом вопрос — почему нужно использовать 0, когда NULL понятнее?
              PS то есть
              for(int i=0; i<n; i++){}//  ясно
              node *temp = NULL// вот тут явно NULL яснее смотрится чем 0?
              
              • +3
                Дело в том, что NULL в C++ должен быть определён как
                #define NULL 0
                


                Определять NULL как (void *)0 нельзя. Поэтому следующий код является корректным:
                f(int x);
                
                void g() {
                  f(NULL);
                }
                


                Я согласен что это неинтуитивно. Что именно использовать в C++03 — NULL или 0 — определяется стилем кодирования в каждом проекте, так как саму проблему с null pointer constant в C++03 не решает.
                • –4
                  А почему код должен быть не корректен? Срабатывает приведение типов — NULL -> int(0).
                  Просто всегда бесило в коде знакомых студентов
                  Foo *a = new Foo();
                  // код код
                  if(a == 0)
                  

                  Ну как указатель можно сравнивать с нулем? Это же логически не верно, пусть даже NULL на самом деле — 0.
                  • 0
                    > Срабатывает приведение типов — NULL -> int(0).

                    Нет такого неявного приведения типов в C++: T* не приводится неявно к int.

                    void f(int x);
                    
                    void g() {
                      int *p = 0;
                      f(p); // compile error
                    }
                    
                    • –2
                      В примере выше ваша функция принимала переменную, а не указатель. Мое, если можно так сказать возмущение как раз и было в том, что для чисел — использовать числа, а для указания пустоты — строки, или указателя, или возможно еще чего-то о чем я не знаю — NULL
                      • 0
                        Вы, кажется, не предвидели всей полноты последствий:
                        $ cat zz.cc
                        #include <iostream>
                        #include <stddef.h>
                        
                        void f(int *x) { std::cout << "int *x" << std::endl; }
                        void f(int x)  { std::cout << "int x" << std::endl; }
                        
                        int main() {
                          f(0);
                        }
                        


                        Overload resolution выбирает f(int) для NULL (если #define NULL 0), точно также как и для 0.
                        • –1
                          Конечно выбирает, ведь 0 === (int)0, а NULL = либо 0, а может nullptr
                          Если изменить f(0) на f(NULL) — компиляторы не сообразят какую из функций выбрать.
                          • 0
                            Это потому что в компиляторах именно из-за этого примера есть костыль, который не совместим с C++03. В C++03 нужно
                            #define NULL 0
                            


                            Однако в GCC и Clang:
                            #define NULL __null
                            


                            И дальше компилятор разбирается по правилам, которых нет в языке.
                  • 0
                    А как же тогда работает код у ваших студентов? Дело в том, что integral constant expression, равный нулю, является null pointer constant и может быть преобразован к T*.

                    void f(int *x);
                    
                    void g() {
                      f(2 * 21 - 42); // OK
                    }
                    
                    • –1
                      А что там должно не работать? Собственно ваш пример должен бы работать, в функцию же отправляется результат выражения, или я чего-то не понимаю?
                      А используется такое в переборе указателей в списках, деревьях и тд
                      • +1
                        То есть вы считаете нормальным, что f(2*24 — 42) компилируется, а f(2*42-43) — нет?
                        • –1
                          Конечно нет! И потому не согласен с тем, что NULL можно заменять в коде 0.
                          Что и как реализовано в языке в действительности — один вопрос. Области видимости в классах — важен только на этапе компиляции, однако позволяет не натворить глупостей.
                          И еще раз — потому надо разделять NULL и 0.
                          • +1
                            > Конечно нет! И потому не согласен с тем, что NULL можно заменять в коде 0.

                            Но NULL проблемы не решает, а словить пару часов интереснейшей отладки overload resolution можно элементарно. Если же там был бы написан ноль, было бы всё понятно.

                            И вообще, это сегодня неактуально. nullptr.
                      • 0
                        Да, пример использования в студию.
                        • –1
                          Кода других студентов под рукой нет — вот мое бинарное дерево. Заменив NULL на 0 простой заменой по файлу вроде никаких изменений нет, кроме утечек памяти, которые судя по всему и так должны быть. Надо бы исправить.
                          Скрытый текст
                          #include <iostream>
                          #include <fstream>
                          #include <string>
                          using namespace std;
                          
                          class Product{
                          private:
                          
                              int num;
                              // string name;
                              // string supplier;
                              // int count;
                              // string date;
                          
                          public:
                          
                              void input(){
                                  // cout<<"Product:            "; getline(cin, name);
                                  // cout<<"Supplier:           "; getline(cin, supplier);
                                  // cout<<"Date:               "; getline(cin, date);
                                  // cout<<"Amount:             "; cin>>count;
                                  cout<<"Number of delivery: "; cin>>num;
                              }
                              static Product getkey(){
                                  Product key;
                                  cout<<"Key: ";
                                  cin>>key.num;
                                  return key;
                              }
                              void print(){
                                  cout<<"Number of delivery: "<<num<<"\n";
                                  // cout<<"Product:            "<<name<<"\n";
                                  // cout<<"Supplier:           "<<supplier<<"\n";
                                  // cout<<"Amount:             "<<count<<"\n";
                                  // cout<<"Date:               "<<date<<"\n";
                              }
                              void output(ostream &stream){
                                  stream<<num;
                              }
                              friend bool operator <(Product const a, Product const b){
                                  return a.num < b.num;
                              }
                          
                              friend bool operator ==(Product const a, Product const b){
                                  return a.num == b.num;
                              }
                          };
                          
                          class node
                          {
                          public:
                              Product data;
                              node *left,
                                   *right;
                          
                              void output(ostream &stream){
                                  if(this != NULL){
                                      left->output(stream);
                                      data.output(stream);
                                      stream<<" ";
                                      right->output(stream);
                                  }
                              }
                              node(Product var){
                                  data = var;
                                  left = right = NULL;
                              }
                              void remove(){
                                  if(this != NULL){
                                      left->remove();
                                      right->remove();
                                      delete left;
                                      delete right;
                                  }
                              }
                              ~node(){
                                  this->remove();
                              }
                              void output_sheets(){
                                  if(this != NULL){
                                      if(left == NULL && right == NULL){
                                          data.print();
                                      }else{
                                          left->output_sheets();
                                          right->output_sheets();
                                      }
                                  }
                              }
                          };
                          
                          class bintree
                          {
                          private:
                              node *root;
                          public:
                              bintree(){
                                  root = NULL;
                              }
                              bool add(){
                                  Product new_data;
                                  new_data.input();
                          
                                  if(root == NULL){
                                      root = new node(new_data);
                                      return true;
                                  }
                                  node *elem = root,
                                                  *parent = NULL;
                                  bool isLeft;
                                  while(elem != NULL){
                                      if(new_data == elem->data){
                                          return false;
                                      }
                                      isLeft = new_data < elem->data;
                                      parent = elem;
                                      if(isLeft){// если новый элемент меньше корня
                                          elem = elem->left;
                                      }else{
                                          elem = elem->right;
                                      }
                                  }
                                  if(isLeft){
                                      parent->left = new node(new_data);
                                  }else{
                                      parent->right = new node(new_data);
                                  }
                                  return true;
                              }
                          
                              void output(ostream &stream){
                                  root->output(stream);
                              }
                              void remove_max(){
                                  node *elem = root,
                                       *prev = NULL;
                                  while(elem->right != NULL){
                                      prev = elem;
                                      elem = elem->right;
                                  }
                                  if(elem == root){
                                      root = root->left;
                                  }else{
                                      prev->right = NULL;
                                  }
                                  delete elem;
                              }
                          
                              void output_sheets(){
                                  root->output_sheets();
                              }
                          
                              Product find(){
                                  Product key = Product::getkey();
                                  node *elem = root;
                                  while(elem != NULL){
                                      if(key == elem->data){
                                          return elem->data;
                                      }else if(key < elem->data){// если новый элемент меньше корня
                                          elem = elem->left;
                                      }else{
                                          elem = elem->right;
                                      }
                                  }
                                  return Product();
                              }
                          };
                          
                          void clear(){
                              cout<<"\n";
                              for(int i=0; i<30; i++) cout<<"-";
                              cout<<"\n";
                          }
                          void menu(){
                              clear();
                              cout<<"1) Add node\n";
                              cout<<"2) Find node by key\n";
                              cout<<"3) Output tree\n";
                              cout<<"4) Remove node with max key\n";
                              cout<<"5) Output tree's sheets\n";
                              cout<<"6) Output in file\n";
                              cout<<"0) Quit\n";
                              cout<<"Your choise: ";
                          }
                          int main()
                          {
                              bintree tree;
                              int ch;
                              while(true)
                              {
                                  menu();
                                  cin>>ch;
                                  switch(ch)
                                  {
                                      case 1:
                                          if(!tree.add()){
                                              cout<<"Element with this key exsist";
                                          }else{
                                              cout<<"Element succesfully added";
                                          }
                                          break;
                                      case 2:
                                          tree.find().print();
                                          break;
                                      case 3:
                                          tree.output(cout);
                                          break;
                                      case 4:
                                          tree.remove_max();
                                          break;
                                      case 5:
                                          tree.output_sheets();
                                          break;
                                      case 6:{
                                          char filename[50];
                                          cout<<"Filename: ";
                                          cin>>filename;
                                          ofstream filestr(filename);
                                          if (filestr.is_open()){
                                              tree.output(filestr);
                                              filestr.close();
                                          }else{
                                              cout << "Error opening file";
                                          }
                                      }
                                      break;
                                      case 0:
                                          return 0;
                                          break;
                                  }
                              }
                          
                          }
                          

                          • 0
                            Я не вижу где вы здесь использовали нетривиальный integral constant expression, равный нулю, в качестве null pointer constant.
                            • –1
                              Потому что я, что возможно в корне не верно — не стал перебирать весь список за добавлением нового элемента, а храню в объекте ссылки на последний и первый элемент, мне это не требуется. Реализация именно в функции добавления, что-то вроде
                              node *temp = root,
                                   *prev = 0;
                              while(temp!=0){
                                  prev = temp;
                                  temp = temp->next;
                              }
                              prev->next = new node();
                              
                              • 0
                                И? Вы же написали temp!=0, а не temp!=1-1.
                                • +1
                                  Да, я нигде не писал что кто-то придумывает выражения для проверок. Я написал что сравнение указателя и числа выглядит странно и не логично, как и присваивание.
                                  • 0
                                    Так, стоп. Было сказано следующее:

                                    gribozavr:
                                    > f(2 * 21 — 42); // OK

                                    На что вы ответили:
                                    > А используется такое в переборе указателей в списках, деревьях и тд
                                    • 0
                                      Мы с вами видимо запутались, это был ответ на это:
                                      gribozavr:
                                      > А как же тогда работает код у ваших студентов?
                                      PS Хотя судя по всему я просто не совсем правильно ответил, но относилось именно к этому.
                          • +1
                            > if(this != NULL){

                            Epic. Так не бывает.
                            • –2
                              Я долго сомневался прежде чем так писать. Однако лабу приняли, все ок. То есть компилятор сам не отправит пустой объект в функцию?
                              • 0
                                При чём тут компилятор? Вызывать функции-члены если у вас нет живого объекта — undefined behavior в момент вызова.
                            • –1
                              Вообщето такое возможно но мало вероятно.
                              SomeClass *lv_Obj = 0; lv_Obj->DoSmth(); // функция вызовется и если внутри нет обращения к полям то ещё и успешно выполнится.

                              но вообще так писать разуметься не хорошо, а если уж и пишешь то следует писать
                              if (!this) return;

                              ибо ненужные скоупы это тоже плохо.
                              • +2
                                Это UB в момент вызова.
                                • 0
                                  даже не поморщится
                                  liveworkspace.org/code/XjE1O$0
                                  Вызвать можно, но это вызовет ошибку в случае если есть обращение к полям, виртуальным методам, или если сам метод является виртуальным (последние не совсем так но проблемы могут возникнуть).
                                  • +6
                                    Разыменование нулевого указателя — неопределенное поведение. А то, что оно не вызвало ошибку — это один из вариантов этого поведения исключительно по воскресеньям.
                • 0
                  while (1) { // НЕТ!
                    :
                  }
                  

                  Вот, кстати пример из статьи. Все знают, что while работает только с булевыми значениями, а значит все остальное внутри скобок приводится к булевому значению. Также все знают, что 1 == true.
                  Так почему же зная то, что NULL = 0 — стоит писать 0, а в циклах такая запись не естественна?
                  • 0
                    См. ответ выше. Проблема в неинтуитивном определении null pointer constant в C++03.
                  • –2
                    В С++ нет bool это иллюзия есть лишь 0 и не 0 так что там ничего не приводится.
                    Так почему же зная то, что NULL = 0 — стоит писать 0

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

                    с циклами я кстати с автором тоже несогласен while (1) гораздо лучше
                    #define true 0 // happy debugging motherfucker!
                    • 0
                      И? Области видимости тоже можно переопределить, только зачем?
                      • –1
                        Что и? проблема в том что это можно сделать, и это может повлечь не очевидную проблему (злая шутка)
                        Поэтому писать 0 всё же лучше, а с точки зрения читаемости это ничего не поменяет.
                        • 0
                          Это нельзя делать. UB.
                          • –1
                            Ты о чём? писать 0 вместо NULL это UB? мда…
                            • 0
                              Нельзя делать #define NULL whatever в пользовательском коде.
                              • –1
                                даже не поморщится
                                http://liveworkspace.org/code/XjE1O$0
                                Вызвать можно, но это вызовет ошибку в случае если есть обращение к полям, виртуальным методам, или если сам метод является виртуальным (последние не совсем так но проблемы могут возникнуть).
                                • +2
                                  Вы не знаете что такое UB, а спорите.
                                  • –3
                                    в данном случае это не неопределённое поведение, и если бы вы знали как устроены методы в языке С++ вы бы не говорили этой глупости.
                                    • +4
                                      Методы в языке С++ никак не устроены. Внутреннее устройство методов не регламинтируется стандартом. Зато стандартом оговаривается, что разыменование 0-го указателя — UB.
                                    • –3
                                      Да про раз именовывание я как-то забыл, действительно, прошу прощения. Просто зациклился на том что метод это функция со скрытым параметром this
                              • –3
                                Ответил до редактирования.
                                Нельзя делать #define NULL whatever в пользовательском коде.

                                Можно, другое дело что это очень плохо, но тем не менее сделать это можно.
                                • +2
                                  Нельзя переопределять имена из стандартной библиотеки. Почитайте что такое UB, узнаете много нового.
                                  • –3
                                    Нельзя переопределять имена из стандартной библиотеки. Почитайте что такое UB, узнаете много нового.

                                    я не говорил что это не UB, несомненно это UB но это не значит что этого нельзя сделать
                                    http://liveworkspace.org/code/XjE1O$2
                                    • +4
                                      И какой в этом смысл? Ввести UB в свою программу для лулзов?
                                      • –4
                                        Ну в универе мы так часто прикалывались друг над другом, от туда и появилась устойчивая привычка использовать 0 вместо NULL.
                                        • +3
                                          Это действительно убедительная мотивация пункта рекомендаций стиля программирования от профессионала, не поспоришь.
                                          • –1
                                            Ну хз я стараюсь предвидеть любые ситуации даже такие незначительные, а что писать 0 вместо NULL это плохо? Честно говоря везде где я работал писали 0, и никаких проблем никогда не было. Может у вас есть история получше?
                                            • +1
                                              Это плохая мотивация, независимо от того, какой вариант на самом деле лучше. А вдруг кто-то (совершенно без UB) сделает #define print zzz, а у вас куча функций так называется? От этого тоже будете «защищаться»?

                                              Моё мнение выражено выше. NULL или 0 — разницы нет, так как *проблему* это не решает.

                                              habrahabr.ru/post/172091/#comment_6046361
                                • +3
                                  Нельзя делать #define NULL whatever в пользовательском коде.
                                  Можно, другое дело что это очень плохо, но тем не менее сделать это можно.

                                  Технически, это возможно, вы правы. Компиллятор даже возможно скомпиллирует вашу программу. Но никто не сможет предсказать, как она будет рабоать. Потому что, используяю такой define, вы нарушили правила игры. Теперь ваша программа — уже не валидная программа на языке С++.
                        • +1
                          Вопрос был из читабельности. 0 — число. NULL — пустота, для чисел является нулем.
                          Если кто-то может зло пошутить — это не повод писать не понятно.
                          • 0
                            А кому непонятно? Новичкам разве что, но гадлайны, делаются для разработок в компаниях куда не берут людей которые не знают языка. Обычно за
                            if(ptr != 0) по рукам бьют т.к. это равносильно if(SomeBool == true) конечно компилятор это разрулит, но обычно это заставляет обращать лишнее внимание на условие.
                            • +1
                              if(SomeBool == true) — подобное, как мне кажется, надо использовать только в языках с нестрогой типизацией и не ==, а ===, но это другая тема и тут я с вами согласен.
                              А за foo *bar = 0 (представим что у нас еще нет nullptr) по рукам не бьют?
                              • –1
                                А за foo *bar = 0 (представим что у нас еще нет nullptr) по рукам не бьют?
                                Нет в С++ это корректное выражение которое понимает каждый.
                                • 0
                                  Эх… Вот вырасту и буду бить. По мне — так это не логично. Уже 25 раз описал почему.
                    • +1
                      operator bool bool() const значит не существует?
            • 0
              Вопрос был «зачем использовать 0?»

              Где то читал, зачем и почему, но забылось уже, так что присоединюсь к вопросу.
          • +1
            0 — присутствие отсутствия, NULL — отсутствие присутствия ©
  • –7
    Была одна очень хорошая рекомендация по именованию локальных переменных vs членов классов.
    Локальные some_var, члены классов как somеVar. Тогда неажно насколько большое полотнище кода надо изучать/редактировать, всегда с первого взгляда понятно с чем работаем — локальная или член класса.
  • +11
    Если это не указано явно, C++ считает, что возвращаемое значение имеет тип int.

    А мне казалось, он как раз это запрещает (если не крутить некоторые флаги, по крайней мере).

    51. Символ указателя или ссылки в языке C++ следует ставить сразу после имени типа, а не с именем переменной.

    Мне такой способ тоже больше нравится, но он имеет одну проблему.
    int* ptr, not_ptr;
    int *ptr, not_ptr;
    

    Второй вариант более очевидный. Хотя оба плохие. :)
    • 0
      Угу. Мне тоже куда больше нравится, когда звёздочка в объявлении указателя «клеится» к типу, нежели к имени переменной.
      Однако, при этом я следую ещё одному правилу: каждая инициализация должна требовать отдельной строки для себя.
      Странно, что в статье про это ничего не говорится (если только я это пропустил).

      А в целом, статья хорошая.
      Хоть и не со всеми пунктами согласен — но лучше кодить по жёстко утверждённому стиль, чем вообще без оного.
    • 0
      Второй вариант однозначно более очевиден, особенно, когда объявляются несколько переменных через запятую.
      Глядя на первый вариант может показаться, что объявлено два указателя на переменные целого типа. Хотя с точки зрения компилятора C++ в обоих случаях указателем является только ptr, а not_ptr просто переменная целого типа.
      Если объявляем много указателей, например:

      Stream *Stream1, *Stream2, *Stream3;
      

      то второй вариант не просто предпочтительнее, а единственно логичный.

      А в таком примере уже не работает правило 12:

      void setStream(Stream *Stream1, Stream *Stream2, Stream *Stream3);
      
  • +14
    Рекоммендация #0: используйте любой, подходящий вам и команде стиль, но в рамках одного проекта других стилей быть не должно и придерживаться его надо обязательно.
  • +2
    Ещё хочу написать кое-что.

    1. Любой символ, заданный в тексте программы своим кодом, задокументировать в свободной форме.

    s += L'\u2212'     // 2212 = minus sign
    

    2. Специфично для Delphi/Builder. Любая ссылка на конкретный объект VCL, в тексте, в форме или в виде уникального события — дай ему говорящее имя. Для «массовых» событий, общих для нескольких компонентов с определением компонента по dynamic_cast<TButton*>(Sender)->Tag, это не требуется, однако само событие должно иметь говорящее имя.

    3. Если у метода есть аналог из известной библиотеки, подстраиваться под неё. Например: const char* Printf::c_str(), но не cStr().
    • +4
      Далее.

      4. Венгерская запись — не именование типа (в IDE уже 15 лет есть навигация по коду), а сокращение идентификаторов. Например, EPreprocess = PreprocessException, nUsers = UserCount.

      5. Различать id (суррогатный код), index (порядковый номер), count (кол-во объектов, которые компьютер различает), quantity (кол-во объектов, которые компьютер не различает) и number (что либо из реальной жизни, так называющееся). Например: carId — идентификационный код, отличающий машину от других, carIndex/iCar — номер машины из n шт., carCount/nCars — кол-во машин в гонке, carQty — кол-во машин в продаже, carNo — номер машины (государственный или стартовый).
  • +1
    Не без замечаний, но в целом годно.
    К п. 41: Опыт подсказывает, что в .cpp первым инклюдом удобно включать собственный .h-файл. Этим проверяется, что он самодостаточен, т. е. не зависим от предыдущих инклюдов в .cpp.
    • 0
      С этим согласен, сам постоянно так делаю.
      Гораздо хуже, когда твоя библиотека в другом проекте будет требовать библиотеки, которые не подключены.
  • +16
    Много уже неактуального, например про 80 колонок или символы табуляции, не говоря уже об игнорировании актуального C++11.

    Есть вредные советы. Из того, что еще не упоминались, например 8. Следует называть имена типов в шаблонах одной заглавной буквой. А кто угадает, что означает каждый аргумент:
    template<
        class K,
        class T,
        class H,
        class E,
        class A
    > class unordered_map;
    


    А вот так это выглядит в оригинале:
    template<
        class Key,
        class T,
        class Hash,
        class KeyEqual,
        class Allocator
    > class unordered_map;
    



    Отдельная история с именованием переменных. Предложенная здесь система похожа на использующуюся, например, в Qt. Microsoft используют немного другугю системму наименований, а вот standard C++ Library и boost вообще использует большинство имен в нижнем регистре с подчеркиванием. Тут может быть только один совет — именуйте так, как принто в той библиотеке, которую вы используете больше всего.

    И      еще,   я            не       могу 
    понять почему выравнивание улучшает читаемость
    
    • +1
      Если в проекте все строчки не длиннее 80 (или 100) колонок, удобно открывать 2 файла параллельно.

      С++ похоже один из не многих языков, где так и не смогли окончательно решить, как писать переменные :)

      Ваш пример выравнивания, конечно, читать неудобно. Чаще оно используется немного по-другому. Мне кажется, есть 2 типа людей: 1) те, кому субъективно нравится выравнивание, они часто его используют; 2) остальные выравнивание практически или совсем не используют. Все при этом руководствуются только улучшением читаемости.
      • +1
        Я сам стараюсь не вылазить за сотню, но 80 — все-таки уже перебор.

        А вот на счет выравнивания, тот пример, который приведен в статье, мне кажется не многим лучше. Ну да, я вижу паттерн, но у меня от него только в глазах рябит. Читаю же я все равно слева направо, и что там в других строках — мало интересует. В типографике наоборт, стараются избавляться от коридоров.
        • +2
          Текст на естественном языке читается по-порядку, всплошную, в то время как в исходном тексте структура имеет огромное значение.
        • –1
          Вообще не понимаю, кому в голову может придти использовать выравнивание в типичном проекте, как показывает практика выравнивание производится один раз, при написании кода, а со временем все это превращается в фарш после переименований классов и никем не поддерживается. А при каждом переименовании просматривать и переформатировать n кусков кода — слишком дорогое удовольствие. Правда автоматическое выравнивание инструментальными средствами наверное решает проблему, но сталкиваться не приходилось.
      • –1
        100-120 тоже нормально открываются на современных FullHD и больше мониторах. А то писать из-за 80 символов постоянно вот так:
        for (int tableNo = 0; tableNo < nTables;
             tableNo += tableStep) {
          ...
        }

        не очень удобно.
        • –2
          Если в коде много строк, которые не влезают 80 символов, возможно с кодом что-то не так. Поясните, к примеру, чем оправдано использование название tableNo вместо обычного i (j, k и т.д)?
          • +2
            Я скопировал это из статьи, совет 39. Без понятия, откуда у автора этот код.

            Например,
            animState->setTimePosition(std::min(anim->getLength(), animState->getTimePosition())); 

            86 символов + отступы.
            • +1
              ИМХО, в таких случаях лучше переносить. Более того, если что-то не вмещается в строку, лучше разбить его на логические составляющие.

              animState->setTimePosition(
                  std::min(
                      anim->getLength(),
                      animState->getTimePosition()
                  )
              );
              

              • 0
                Это стандартный капитанский совет людей, кто все вмещает в 80 символов. Только вот от этого читать ее нифига не стало лучше, а как по мне — наоборот.
                И это функция с одним параметром.

                Потом, самое неприятное — это переносить явно однострочные функции.
                deltaTransform.translate = srcBone->getInitialPosition() - dstBone->getInitialPosition();
                deltaTransform.rotate = dstBone->getInitialOrientation().Inverse() * srcBone->getInitialOrientation();
                deltaTransform.scale = srcBone->getInitialScale() / dstBone->getInitialScale();


                Замечу, что я не выступаю против переносов. Естественно, когда строка не вмещается в разумных границах монитора, программиста хочется ругать. Дело просто в том, что 80 строк правда далеко не всегда хватает, а городить перенос на каждом чихе — бесит. 100-120 столбцов — самое то. Лично у меня стоит ограничение на 110 символов, я спокойно отрываю два файла на одном мониторе FullHD.
                • 0
                  Читать стало лучше, потому что глаз видит больше. Газетные колонки по этой причине существуют. Еще в программировании опасны паттерны, потому что глаз замыливается и можно перепутать X с Y, например.
                  Еще это удобнее читать в редакторе, в котором открыто 6 вкладок, по ssh, на телефоне и нетбуке, на гитхабе. 80 символов — самое то.
                  • 0
                    Видите, Вам лучше, а мне нет. Очень все индивидуально.
                    Соглашусь, что в крупных открытых проектах лучше делать все по 80 символов, ибо мы не знаем, кто этот проект будет читать. Внутрикорпоративно же я приблизительно знаю, каким оборудованием владеет народ, и делать такие никому ненужные ограничения смысла не вижу.
                    • 0
                      Я бы не сказал, что все индивидуально. Поле зрения у глаза круглое, поэтому длинные строки нужно читать, а «квадрат» можно охватить взглядом сразу. Хотя насчет 80 я погорячился, пожалуй.
                      • 0
                        При беглом анализе — да, скорее, Вы правы.
                        А вот если нужно вчитываться в написанное, то при сильно скомканном коде (как в примере выше), приходится включать мозговой парсер. Строка же принимается также, как строка в обычной книжке.
                  • 0
                    И да, если приспичить сделать все по феншую, для VS есть плагин StyleManager, который можно настроить на любой стиль и длину строк. Есть и свободные аналоги, но названия не помню.
          • 0
            Вот по поводу последнего — вы погорячились.

            Видите вы строчку внутри вложенного цикла:
            var tratata = MumbleThis(i, j);
            

            Неужели она лучше чем:
            var tratata = MumbleThis(year, dayOfYear);
            


            Индексы i,j — допустимы только когда 100% кристально понятно, что они обозначают и невозможно разночтений или необходимости вникать.
            • +2
              Я бы сказал, что они допустимы только когда это абстрактные индексы, не имеющие воплощения в предметной области (или имеющие именно в таком виде, например в математической формуле именно i используется). Потому что-то, что кристально понятно сейчас, пока тело цикла из одной строки состоит, потом может уже не быть понятным вообще, особенно если десяток строк добавить. В эту же степь неявные преобразования «физических» номеров (начинаются с 1) в «абстрактные» индексы (с 0) типа a[month-1] = 0; Сиди и гадай потом, то ли это преобразование в индекс, то ли какая-то логика про предшествующий месяц.
              • 0
                Соглашусь, верное замечание.
    • +1
      Крайне неприятно, когда в исходном коде попадаются строки по 150-200 колонок, а то и больше, особенно когда кроме IDE открыто ещё что-нибудь, и нужно уменьшить ширину окна, или когда код просматриваешь не в IDE, а в текстовом редакторе.
      Что касается выравнивания, читаемость оно улучшает, но в довольно специфических случаях: при задании значений двумерным массивам, например.
    • +1
      Выравнивание улучшает читаемость при использовании случае выравнивания однотипных (в данном контексте) сущностей. Прекращение выравнивания означает, что пошли сущности другого типа.
      • 0
        Не проще ли для таких случаев использовать разделитель в виде пустой строки?
        • 0
          Обычно он используется для разделения логических блоков. Могут быть неоднозначности.
  • +28
    Вкратце: всё пишем будто на джаве.
  • +2
    На каком стиле кодирования основана статья? Их же бесчисленное множество: habrahabr.ru/post/116819/
  • +16
    А кто такие эти geosoft.no чтобы давать рекомендации по стилю C++? У них рекомендаций больше, чем написанных программ. Я вижу у них на сайте только ряд java-библиотек. Как объяснить программистам, что им теперь нужно писать по указке geosoft?

    Есть куча нормальных стандартов, написанных реальными разработчиками, а не Wannabe Styleguide Writer-ами.
    Google C++ Style Guide
    Mozilla Coding Style
    Linux kernel coding style
    Microsoft Design Guidelines for Class Library Developers
    Boost Guidelines
    Alexandrescu C++ Coding Standards
  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
    • –1
      Это все так, но добавление подчеркивания к имени членов класса позволяет легко отличить их от локальных переменных и позволяет избежать конфликтов имен. А кодирование типа данных в имени это действительно архаизм времен убогих IDE
  • +1
    48 Про глобальные функции не согласен.
    49 Не согласен, что всегда все члены класса следует объявлять как private и писать к ним get/set. Чаще — да, всегда — нет.
    58 Да ладно?
    70 Следовало бы и про nullptr сказать.
    51, 72-83 холиварные пункты. Считаю, лучше было бы их убрать, либо написать более демократично.
  • 0
    Добавил в избранное, хотя тут годных советов максимум процентов 30-40. Остальные — спорные, холиварные, вкус фломастера (на вкус и цвет фломастеры разные).
  • +3
    В статье больше спорных советов, чем полезных. В дополнение к критике выше:

    Все определения должны находиться в файлах исходного кода.

    class MyClass
    {
    public:
      int getValue () {return value_;}  // НЕЛЬЗЯ!
    


    Прощай, инлайнинг.
    • 0
      Это, может, и спорный совет, но назвать его однозначно вредным нельзя. В большинстве случаев оверхед на вызов функции пренебрежимо мал. В то же время, такие инлайн-определения имеют свойство разрастаться, теряя при этом всю привлекательность.
      • +1
        Инлайнинг полезен не только для того чтобы убрать оверхед на вызов функций (но это тоже очень важно для всяких геттеров), а ещё чтобы распространить информацию из места вызова в тело функции и убрать лишнее. Инлайнинг даёт большой положительный эффект по производительности для программ. Именно инлайнинг позволяет убирать оверхед абстракций C++, которые мы так любим.

        llvm.org/devmtg/2012-11/Carruth-OptimizingAbstractions.pdf
        llvm.org/devmtg/2012-11/videos/Carruth-OptimizingAbstractions.mp4
        • 0
          Все это становится полезным только для 20% кода, работающего под нагрузкой. Если же ваш геттер вызывается 3 с половиной раза в сутки, как это чаще всего и бывает, то никакого улучшения производительности вы не заметите. Зато заметите, что
          — теперь вам нужно перекомпилировать все зависимые модули при изменении одного геттера.
          — не может идти речь ни о какой бинарной совместимости. Вы просто не можете взять и поменять реализацию, не поменяв интерфейс.
          • +2
            Если вы пишете типичное энтерпрайз GUI приложение, пожирающее мегатонны RAM, в которое меланхоличный пользователь раз в минуту тыкает мышкой и затем размышляет «а что это оно мне написало», то ваш подход оправдан. Я работаю в несколько другой области.
            • –1
              Вы знаете, я просто смотрел код под профиллировщиком и видел, что тормозит. Всего один раз, после того как я уже устранил 3 или 4 основных затыка, у меня производительность уперлась в цепочку из 5 последовательных вызова виртуальных функций.
              • +4
                Вот вам простой эксперимент: я скомпилировал Clang в обычном режиме Release+Asserts, и в таком же режиме, но добавив -fno-inline -fno-inline-functions. Измерялось время исполнения тестов (CPU time).

                R+A: 12m40.520s
                R+A no-inline: 22m29.980s

                Выводы о пользе инлайнинга делайте сами.
                • –1
                  Внутри одного модуля трансляции встраивание будет работать даже в случае, если вы не пометили функцию как inline. Если включена межмодульная оптимизация, то встраивание, возможно, будет рабоать и в случае, когда объявление и реализация находятся в разных модулях трансляции. Все это не имеет прямого отношения к определению функции в заголовочном файле. С другой стороны, ваш тест не показывает, сколько процентов кода тормозит.
                  • 0
                    > Все это не имеет прямого отношения к определению функции в заголовочном файле.

                    КО: заголовочный файл становится частью многих единиц трансляции.

                    > С другой стороны, ваш тест не показывает, сколько процентов кода тормозит.

                    Без инлайнинга код начинает тормозить везде равномерно.
                    • 0
                      Да, но вы отключили встраивание глобально, там, где оно могло бы сработать, даже если бы определение функции было отделено от объявления, оно теперь не работатет. Точнее, только такие случаи вы и показали. Потому что там, где встраивание не могло срабоать раньше, оно раньше и не срабатывало.

                      Без инлайнинга код начинает тормозить везде равномерно.

                      Ну вот не факт. Какие-то части кода вызываются чаще, другие — реже. Мне вообще кажется, что основные проблемы были вызваны тем, что перестало срабатывать встраивание в каких-нибудь контейнерах. Или вроде того случая, который вы привели по ссылке в начале обсуждения.
                      • 0
                        > Да, но вы отключили встраивание глобально

                        Да. Что вы дальше хотели сказать я так и не понял. (Обычный R+A компилировался без LTO, если вы это имели ввиду.)

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

                        Основные — да. Но не все. Кроме того, мы же обсуждаем «правило» что инлайн функции в классах определять «НЕЛЬЗЯ». Поэтому считаю тест корректным.
                        • 0
                          Понимаете, вы выключили слишком много inline-ов. При простом переносе определения геттеров в файлы имплементации, выключится существенно меньше inline-ов. Поэтому тест не совсем корректен.

                          Кроме того, мы же обсуждаем «правило» что инлайн функции в классах определять «НЕЛЬЗЯ»

                          С оговоркой, что если очень хочется, то можно.

                          Фактически, я бы сказал, что по умолчанию вы делаете все функции классов невстраиваемыми, если нет явной причины сделать их встраиваемыми.
                          • +1
                            > При простом переносе определения геттеров в файлы имплементации, выключится существенно меньше inline-ов. Поэтому тест не совсем корректен.

                            Не согласен со словом «существенно». Зависит от приложения.

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

                            А вот с этим я согласен.
      • +1
        Ага, особенно для шаблонов. Впрочем, если в clang осилят нормальные модули, то быть может вообще получится уйти от разделения на h и cpp просто правильно подставляя экспорты и импорты.
        • 0
          Модули не подразумевают что у вас не будет .h. Они останутся. Модули говорят что у вас не будет #include. В Clang Douglas Gregor идёт дальше и предлагает оставить и #include, но автоматически преобразовывать его в импорт, если .h модульный.

          clang.llvm.org/docs/Modules.html
  • +20
    34, Первый раз слышу про файлы с расширением *.с++ (MyClass.c++), это где так принято именовать?
  • +4
    Правила явно писал Java кодер, некоторые вещи слишком избыточные, POD структуры вполне себе удобная сущность, да и в приватных классах можно не городить весь огород с сокрытием полей, только читаемость теряется.
    Приватные поля лучше записывать через
    m_name;  
    

    Так к ним быстрее из IDE добраться. В общем, Qtшный стандарт как-то ближе и теплее.
  • +1
    Удивительно прям, почти нет заминусованных комментариев. Обычно такие холиварные топики похожи на поле битвы.
    • +15
      Все дружно выступают против автора этих рекомендаций.
  • 0
    ИМХО тут очень, очень много вредных советов.
    1. Допускаются любые нарушения рекомендаций, если это улучшает читаемость.

    Незя, ибо читаемость за частую очень субъективно.
    2. Правила могут быть нарушены, если против них есть персональные возражения.

    XD тогда зачем вообще заводить гайдлайни по коду?
    Позволяет легко отличать переменные от типов, предотвращает потенциальные коллизии имён, например: Line line;

    Всегда думал что наилучший подход в данном случае использовать префиксы видимости (хоть они и не очень нравятся), они очень хорошо помогают в условиях современных IDE с интелесенсом, и при чтении, сравни:

      m_Field = lv_Field + _Offset;
    /*
    сразу при чтении понятно m_Field - поле класса lv_Field1 - локальная переменная метода _Offset - параметр метода.
    при этом нет никаких конфликтов имён, быстро читается + при вводе m_ интелесенс сразу выведет поля также и при lv_ и _
    /**/
      field = field1 + offset; // что от чего и где совсем непонятно.
    


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

    Зачем? при этом ведь возможен конфликт имён, и из-за этого тебе потом приходится ставить постфикс в приватных переменных, лучше с большой буквы т.к. это в любом случае будет выглядеть лучше
    SomeObj.Method() // гармония SomeObj.method() // дизгармония
    7. Названия пространств имён следует записывать в нижнем регистре.

    Логика тоже неясна. Зачем дробить понятие наименование на такие группы? когда конфликты в случае namespace, class name, func name. практически никогда не происходят, гораздо лучше задать им единый стиль.
    SomeNamespace::SomeObj.Method() // гармония some_namespace::SomeObj.method() // дизгармония
    8. Следует называть имена типов в шаблонах одной заглавной буквой.
    Это самый вредный совет который только можно было придумать, параметры шаблона, равносильны параметрам функции, тыже небудешь делать в функции такие параметры: void SomeFunc(int A, int C, int B, char* T)
    9. Аббревиатуры и сокращения в именах должны записываться в нижнем регистре.

    Не имеет смысла аббревиатуры на то и аббревиатуры что имеют определённый стиль написания, и менять его не следует.
    11. Членам класса с модификатором private следует присваивать суффикс-подчёркивание.

    Суффиксы самая бессмысленная вещь, либо префиксы либо без них. Если приватное имя, и имя параметра будут совпадать, то будет очень легко допустить ошибку при наборе + интелесенс поможет сделать ошибку, а при чтении суффикс _ вообще будет незаметен. Вредный совет.
    12. Настраиваемым переменным следует давать то же имя, что и у их типа.

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

    SomeFunc(string& stringPath, int CountInt, float OffsetFoloat)

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

    название пункта звучит очень гупо. Надо было сказать про стандартное допущение при наименовании итераторов в циклах.
    17. Слова get/set должны быть использованы везде, где осуществляется прямой доступ к атрибуту.

    Довольно спорный момент, get/set это так называемые свойства, которые в других языках реализованы обычным именем свойства без get/set, да и без get/set облегчается рефакторинг.
    18. Слово compute может быть использовано в методах, вычисляющих что-либо.

    Зачем?
    Дайте читающему сразу понять, что это времязатратная операция.

    В идеале читающий вообще не должен знать что происходит внутри, идёт там вычисление или значение из кэша достаётся.
    19. Слово find может быть использовано в методах, осуществляющих какой-либо поиск.

    Аналогично с предыдущем высказыванием.
    Следует избегать сокращения init.

    Почему? Чем оно вредно?
    21. Переменным, представляющим GUI, следует давать суффикс, соответствующий имени типа компонента.

    Спорно
    23. Префикс n следует использовать для представления числа объектов.

    Нет смысла, и не очевидно.
    28. Следует избегать сокращений в именах.

    Почему? Многие сокращения являются общепризнанными, например Avg, Min, Max, Sin, Cos, Cmd, Init и т.д. зачем перегружать названия и делать их длинными когда в некоторых случаях это не несёт смысла.
    37. Содержимое файлов не должно превышать 80 колонок.

    Спорно, экраны уже давно не такие маленькие.
    38. Нельзя использовать специальные символы (например, TAB) и разрывы страниц.

    Глупость! отступы следует делать только табами.
    Такие символы вызывают ряд проблем, связанных с редакторами, эмуляторами терминалов и отладчиками, используемыми в программах для совместной разработки и кроссплатформенных средах.

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

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

    Наоборот, т.к. символ указателя или ссылки в языке C++ относится к переменной а не типу.
    53. Следует избегать неявного сравнения булевых (логических) переменных и указателей с нулём.

    Глупость, в стандарте С++ и С нет особого типа bool который необходим для условий, соответственно не имеет смысла писать

    if (nLines != 0) // в С++ это равносильно if (nLines != false)

    58. Следует избегать использования break и continue в циклах.
    Такие выражения следует использовать только тогда, когда они повышают читаемость.

    мда… а в логике работы программ эти выражения не участвуют да?
    Если это не указано явно, C++ считает, что возвращаемое значение имеет тип int.

    Неужели может попробуешь скомпилировать код с функцией без типа возврата? Такое допускается, только с main и то выдаёт ошибку.
    71. Основной отступ следует делать в два пробела.

    Табами и только табами, т.к. табы можно настроить в большинстве IDE а пробелы нет.
    72. Блоки кода следует оформлять так, как показано в примере 1 (рекомендуется) или в примере 2, но ни в коем случае не так, как показано в примере 3. Оформление функций и классов должно следовать примеру 2.

    О да, 1 и 2 являются одним из вечных источников холиваров K&R или Олман в области стиля. И гайдлайн который вроде бы должен как раз такие моменты разруливать включает оба этих момента, круто!
    89. Используйте выравнивание везде, где это улучшает читаемость.

    ты же говорил что так

    if (a == lowValue) compueSomething(); else if (a == mediumValue) computeSomethingElse(); else if (a == highValue) computeSomethingElseYet();

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

    В общем очень, очень плохая статья. Многое писал КЭП, много вредных советов, много спорных моментов, много бессмысленных советов, много не освещённых моментов (например макросы, шаблоны, вложенные классы, датахолдеры, употребление explicit, перегрузка операторов, абстрактные классы(они же интерфейсы) ). Очень многое можно выкинуть и упростить.
    • 0
      про SomeObj.Method() и someObj.method(); категорически не согласен. Как тогда отличить SomeClassConstructor() от SomeStaticMethod(); и потом, допустим, должно быть SomeClass::Method(), но при этом someClass.method(); же, что еще больше путаницы создает.
      • 0
        Ничего не понял, в чём проблема? Отличить конструктор от метода? помоему это очевидно и с именем никак не пересекается.
        и потом, допустим, должно быть SomeClass::Method(), но при этом someClass.method(); же, что еще больше путаницы создает.

        в чём конкретно? что трудно различить SomeClass::Method() и SomeClassObj.Method()? А :: и. не помогут? К тому же я вообще ни разу не встречал ситуации когда статический метод и обычный были с одинаковым именем.
      • 0
        Можно различать их по цвету, современные IDE умеют подсвечивать разным цветом различные элементы (например статические методы — оранжевые, нестатические — лазурные).
        • 0
          Heroes of Might And Magic 3 научили меня, что не оранжевые, а ржавые.
  • 0
    Вот еще бы софтину какую посоветовали, чтобы автоматически за соответствием стандартам следить. У языков вроде питона или руби есть такие инструменты. А для С++ чаще всего больше чем реформаттер отступов и скобок найти очень сложно.
    • 0
      У Google, например, есть cpplint
      • 0
        У LLVM и Clang есть clang-format.
  • –1
    Есть одна хорошая книга Николас А. Солтер, Скотт Дж. Клепер. C++ для профессионалов (или второе издание с включением C++11, многопоточности и прочих интересных тем), которая содержит много полезных советов. В частности, по поводу именования переменных:

    gSettings — префикс выражает глобальный статус переменной;
    srcName, dstName — различие между объектами (например при копировании);
    mName — префикс выражает статус члена данных (или "_" вместо «m», по аналогии с Python).

    На странице 187, в данной книге есть таблица с префиксами и их значениями. На мой взгляд, префиксы куда удобнее для понимания и для работы в IDE, а также несут дополнительную информацию о назначении переменной (член данных, временная, статическая и так далее). К слову, именование констант с префиксом «kMaximumLength» выглядит куда лучше, чем «MAXIMUM_LENGTH».

    В гайде очень много спорных моментов и очевидных вещей. Помимо не освещённых моментах, о которых писали выше, хотелось бы увидеть рекомендации по оформлению кода реализации классов, что-то вроде:

    Foo::Foo( int arg1, int arg2 )
        : mArg1( arg1 )
        , mArg2( arg2 )
    {}
    • +1
      А это уже венгерка с сокращениями. Префикс _ нельзя, он за компиляторозависимыми вещами определен. Лучше уж m_, mMember несколько дико смотрится.
      • 0
        Ну не такая уж и злостная венгерка, как например «rgfpArray») Все вполне пристойно, если подходить без фанатизма.
        Тот же QtCreator генерирует имя поля для свойства в стиле «m_», что соответствует венгерской нотации. Ну а в Qt Coding Style ничего не написано по данному вопросу, если мне не изменяет память.
  • +2
    Переменные I и l надо запретить под страхом отката коммита. Причина, надеюсь, очевидна.
  • –3
    Я лично не понимаю на что расчитаны все соглашения насчет именования переменных, классов, функций, констант. Достаточно одной хорошей практики: «делайте методы короткими». И по барабану как что именуется.

    P.S. некоторые предложеные префиксы/суффиксы вообще идут в разрез с некоторыми другими пунктами. Например nLines — правильно согласно другому же пункту «number_of_lines»
    • 0
      class UniCorn {
          public:
      int get_x() {return X; };
      
          void setX(int salad) { X = salad; };
      
          int Y()
          {
              return _y;
          }
      
          void SetY(int JesusWillBeatTRex) {
              _y = JesusWillBeatTRex;
          }
      
      private:
          int    X;
          int    _y;
      };
      


      действительно, главное методы короткие
      • –1
        Во первых я про форматирование ничего не говорил (но это лечится автоидентом многих современных редакторов), а во вторых в таком тяжелом случае (я имеею ввиду автора такого кода) не помогут даже эти правила :). В одном случае будет называть переменную (согласно правилам) nLines, в другом случае — numberOfStrings. В одном случае функция будет членом и будет с префиксом m_, в другом будет без префикса m_ и тд и тп.

        А, так, благодаря коротким методам, этот код читается легко и непринуждённо, хоть он и плохо форматирован.
  • +1
    Не совсем понял, чем не угодило сокращение init (да и cmd, если уж на то пошло).

    Объяснение про while(1) тоже не уловил — «Проверка на единицу не является необходимой и бессмысленна.» — это что означает вообще?
    • 0
      Тут скорее всего имелась ввиду формальная логика: для человеческого восприятия условие «пока да» — у любого интерпретируется как «всегда да», а вот условие «пока 1» — немного сносит крышу.
      • +2
        Это еще что… Вот 1С куда более мозг сворачивает, например, «Для Каждого Стр Из Товары Цикл»
      • +2
        Словно формальная логика программиста имеет что-то общее с человеческим восприятием :)
    • 0
      Может заставить искать глубокий смысл в единице. Почему не двойка? Почему не минус единица и т. п.
      • 0
        Чтобы увидеть отсутствие глубокого смысла, вполне достаточно знать, как целочисленные типы приводятся к булевскому.
        • 0
          Это хорошо, если программируешь на C++ и только на нём. Но ведь не во всех языках это работает именно так. И когда используешь true и false, не приходится думать: а как же оно там приводится?
        • 0
          Зачем лишние сущности?
  • –4
    mainWindow, propertiesDialog, widthScale, loginText,
    leftScrollbar, mainForm, fileMenu, minLabel, exitButton, yesToggle и т. д.
    

    А почему не так, как все привыкли?
    wndMain, dlgProperties, txtLogin,
    scrlLeft, frmMain, mnuFile, lblMin, btnExit и т. д.
    
    • +13
      LPCWSTR, HRSRC, LGRPID, КРОВЬ, КИШКИ, WINAPI!
      • 0
        Я совсем о другом — не об использовании сокращений, а о большем акценте на назначении контрола, чем на его типе.
        Тип ведь в программе и так записан.
        • +1
          propertiesDialog — как раз и есть акцент на назначении. Начинаем набирать назначение, а остальное завершает автокомплит.
          dlgProperties — акцент на типе.
  • 0
    Странно, а почему в правиле 85 мы советуем использовать пробелы перед скобками, а в 86 их не используем.
  • 0
    Прочитал статью, и немного порадовался, что я почти всё делаю правильно.
    И всё-таки мне понятнее такая конструкция:
    while (true)
    {
       sendPacket("test");
       checkInput();
    }
    


    Нежели когда скобка стоит сразу после (true). Так логичнее, сначала цикл/условие, затем тело.
    • 0
      Вот тут немного подробней про стиль расстановки скобок: тот, что Вам не нравится, называется K&R. Не думаю, что это принципиально; всё зависит от тех соглашений, которых Вы придерживаетесь.
    • 0
      Мне тоже так удобней. По сути своей скобки — это ограничители, логические, функциональные, области видимости… и в коде вполне могут быть конструкции подобные приведённой ниже:
      int y = 0;
      {
         int x = 0;
         ...
      }
      

      Наглядный пример получается при использовании элементов синхронизации:
      int zzz;
      {
         Lock l(cs);
         ...
      }
      
      • +1
        С другой стороны, скобка в строке с ключевым словом показывает на неразрывную связь блока с этим словом.
        • 0
          И существенно экономит место, прилично увеличивая количество полезной информации, помещающейся на экране.
  • +1
    Годный вброс. Очень много спорных пунктов, не смотря на аргументацию (кстати местами довольно хилую).

    К примеру, на мой взгляд, строчное написание с подчеркиванием в качестве отступов читается_лучше, чем верблюжийШрифт.
    (Не надо холивара на эту тему, пожалуйста).

    Кстати, принятый в GNU формат отступов, тут назван неприемлемым.
    void
    some_function()
      {
        do_some_stuff();
      }
    
    • 0
      +1 за строчное написание. CamelCase читается хуже, особенно, когда используются longNamesTalkingForThemselves.
      Если открыть документ GNU Coding Style, половина там написанного тут попадает в список нерекомендуемого к использованию.
      А если открыть книжку Best Practices, то там и вовсе сказано, что главное — чтобы стиль был единым в пределах хотя бы файла или лучше модуля, и хороший программист без особого труда сможет переключаться между различными стилями.

      Так что приведенные рекомендации — всего лишь best practices + coding style одного конкретного проекта/конторы/разработчика. У текста одно достоинство: основные правила формализованы и подробно описаны.
      Это отдельный труд, мы в своем отделе уже месяца 2 собираемся формальный документ написать, все никак не соберемся — на code review полагаемся.
      • 0
        Знаю, сам как-то для мелкой команды пытался составить.

        Всё учесть не реально, к примеру во всей статье и в комментариях нет ни одного упоминания тернарного оператора. Хотя обычно тот, кто составляет такой документ прямо его зарещает (еще одна тема для холивара — не редко он читается легче, чем конструкция if/else).

        Читаемость кода — очень субъективная штука. Если мне доводится писать подобные рекомендации, то я пишу примерно следующее:
        • K&R стиль для скобок;
        • строчное написание везде, кроме имен типов, там — CamelCase;
        • #pragma once;
        • отступ — 4 пробела;
        • строго 80 колонок;
        • только блочные комментарии;
        • длинные самодокументирующиеся имена;
        • никакой венгерской нотации;
        • метки — на уровень левее кода, case, private, protected, public приравниваются к меткам;
        • использование auto и decltype;
        • использование static_assert;
        • там, где можно — циклы по коллекции, вместо итераторов;
        • обязательное использование const в параметрах методов;
        • использование final, override;
        • использование constexpr;
        • явное указание всех исключений, которые может выбросить метод.

        Остальное — на собственное усмотрение. Уже в перечисленном обычно находится пяток тем для споров, из которых уже выводятся окончательные требования к стилю в проекте.
        • +1
          обычно находится пяток тем для споров
          не удивительно:
          строчное написание везде, кроме имен типов, там — CamelCase

          It's so pythonic ._.______

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

          IMHO, очень спорно, ещё Майерс показал, что от throw деклараций вреда больше чем пользы. Даже вменяемые жависты люто ненавидят throws.

          там, где можно — циклы по коллекции, вместо итераторов

          Почему всё-таки не algorithm?
          • +1
            О нет… похоже, мы начинаем холивар.

            Одна из причин, по которой не люблю python (ой сейчас я огребу), именно _.__.___.
            Тут надо знать меру, IMHO больше одного символа "_" подряд — недопустимо.

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

            А по поводу algorithm:
            for(auto item : vect) {
                /* do some stuff */
            }
            

            Читается легче, чем
            std::for_each(vect.begin(), vect.end(), [] (some_type item) -> void {
                /* do some stuff */
            });
            

            или
            struct some_functor {
                void operator() (some_type item) {
                    /* do some stuff */
                }
            } my_functor;
            
            /* ... */
            
            std::for_each(vect.begin(), vect.end(), my_functor);
            


            Давайте закроем тему, все равно в итоге каждый останется при своем мнении.
            • +1
              _.__.___

              О, на вашего питона кто-то наступил. Я не намекал на магические методы, с ними как раз всё ок.

              for(auto item : vect)

              К сожалению, пока не могу использовать новый стандарт. Почти всегда ваш вариант наглядней. Но если нужно просто вызвать метод, я бы всё же написал std::for_each(v.begin(), v.end(), std::mem_fun(&cls::func));. Хотя, конечно, добавить ещё что-то будет сложнее.

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

              Можно забыть, когда его пишешь, и потом неожиданно попадёшь не в обработчик catch(std::exception) или catch (std::my_subsystem_exception), а в unexpected(), что довольно неприятно. Жить так, конечно, можно (если всегда оборачивать всё подряд в try {} catch (...) { throw my_exception(); }), но параною развивает. Дело ваше.

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

              Deprecated в C++11.

              www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3051.html

              As expressed in the national body comment above, exception specifications have not proven useful in practice. [...]

  • –1
    А кто-нибудь знакет обоснование, почему в stl и boost нарушаются описанные выше правила именования классов? А также почему включаемые файлы stl не имеют расширения ".h"?
    • 0
      Потому что эти «правила» — личное мнение автора.
      • +8
        причем его укусила java
    • +1
      Странные ребята минусуют… Вот, кстати, что удалось найти по этому поводу: stackoverflow.com/a/13882155/322708
      Underscores and lowercase was the style perferred by Bjarne Stroustrup in «The C++ Programming Language».

      Я считаю, что мнение автора языка о том, в каком стиле на нем писать, довольно важно. Это в некотором смысле первоисточник. Вот, Страуструп придерживается того же стиля, что в stl (вернее — stl написан в стиле, рекомендованном Страуструпом). Или еще похожий вопрос: как писать ключевые слова в Паскале — маленькими буквами? с большой буквы? большими буквами? Кто во что горазд, так и пишет. А между тем Никлаус Вирт явно дает понять, какой из вариантов он имел в виду — ведь в его последнем «паскалеподобном» языке, Обероне, ключевые слова пишутся только большими буквами; значит, с большой вероятностью так должно было быть и в Паскале.
    • +1
      Чтобы различать достандартный вариант, где все было в глобальном неймспейсе и новый, в котором появился namespace std. Как только старый вариант окончательно стал deprecated, достандартный вариант убрали, хотя он до сих пор встречается в плохих книжках. То есть раньше можно было писать или так:
      #include <iostream.h>
      int main() {cout << «hello»;}
      Или так:
      #include int main() {std::cout << «hello»;}
      • 0
        Ну несколько странный вариант делать различия — убирать расширение .h из имен включаемых файлов. Если бы сделали .hpp вместо .h, я бы еще понял. Но совсем убирать…
  • +1
    Ничего не могу с собой поделать, мне очень нравятся stl/boost naming conventions в сочетении с 1TBS. Жалею, что на работе принят более java-подобный стиль именования с отступами в стиле C#.
  • НЛО прилетело и опубликовало эту надпись здесь
  • +1
    Очень спорный документ. Я лично вообще против настолько развернутых style-guide-ов, от них холиваров больше, чем пользы. Да и пройдет в лучшем случае месяц, а то и несколько, full-time работы прежде, чем это все хоть как-то запомнится.

    И, может, я просто не заметил, но видимо главное правило все-таки упустили: при правке существующего кода использовать принятый в нем code-style.
    • 0
      это тоже спорно — например по кодстайлу можно определить в каком именно месте поправил что то другой программист, да и если ужасный кодстаил предшественника — с какой стати я должен говнокодить как он?
      • 0
        Ну я в первую очередь имел в виду, что делая правки в чужом проекте надо в первую очередь уважать существующий код-стайл. Да и нет ничего хуже, чем читать потом мешанину из табов и пробелов в качестве отступов. Разные правила расстановки фигурных скобок тоже раздражают. Главное — кода есть 2 вида расстановки скобок, вероятность того, что найдется чел, который напишет и третий, повышается.

        С различиями в «мелких» пунктах вроде расстановки пробелов внутри скобок/между операторов еще жить можно.

        Ну а вообще… код-стайл — это скорее зона ответственности project manager-а/тимлида/техдиректора проекта (в зав-ти от формата разработки). Но если уж выбрали — лучше придерживаться. Лучшее, что можно придумать во избежание бардака — автоматизироватьпроверку на соответствие или даже форматирование в некоторых случаях.

        Для определения где что, кто и когда поправил — есть git/mercrurial/svn. Если кодстайл реально ужасен, то проще скрепя сердце, переформатировать/поправить весь код, хотя бы в основных пунктах, предварительно согласовав это все с коллегами и тимлидом.
    • 0
      Я лично вообще против настолько развернутых style-guide-ов, от них холиваров больше, чем пользы.

      удобны для составления автоматических правил проверки/форматирования.
  • +1
    Для коллекции не хватает пунктов:
    — С какой стороны от типа ставить const при объявлении параметров;
    — Стоит ли использовать using namespace std.
  • 0
    "Все определения должны находиться в файлах исходного кода."
    Он шутит? А как же оптимизация? Он не знает, чем отличается реализация в заголовке (объявлении)?
    Особенно смешно совместно с рекомендацией использовать функции вместо констант.
    Сей господин отдаёт себе отчёт, что это приведёт к явному вызову функции при практически каждом использовании константы?
    Это может вылиться в замедление работы программы в сотни раз.
    Теоретик какой-то. он сам-то пытался так писать? И как, работало?
    • 0
      Да ещё и "Не следует объявлять переменные класса как public."
      Нет, он точно клоун.
      • 0
        Java головного мозга
    • 0
      > использовать функции вместо констант
      Все константы действительно глупо заменять функциями, хотя пример с MAX_ITERATIONS выглядит вполне разумно.
      А вот заменять все глобальные переменные функциями get/set — вполне здравая идея, ещё Макконнелл об этом писал. Но лучше, конечно, вообще не использовать глобальные переменные.
      • 0
        И выносить их в CPP-файлы? Вы уверены, что хорошо представляете себе, как оно работает?
        • +1
          Я прекрасно понимаю, как оно работает. Можете объявить функции досупа к глобальным переменным в заголовочном файле с ключевым словом inline, если очень хочется. Это просто неплохая, на мой взгляд, практика, я не утверждаю, что надо всегда и везде так делать.
          • +1
            Он пишет «Все определения должны находиться в файлах исходного кода.»
            Бред какой-то.
            Особого смысла выносить константы в функции в общем случае нет. Если есть смысл — никто не запрещает вынести. А иначе, это какая-то очередная «война против оператора goto».
            • +1
              Разумеется, бред. Хотелось бы посмотреть, как он шаблоны в cpp-файлы вынесет. Явное инстанциирование работает только в очень ограниченном количестве случаев. А без шаблонов писать на довольно c++ грустно.
              • +1
                локально используемые шаблоны на ура и в cppшнике пишутся.
    • 0
      Особенно смешно совместно с рекомендацией использовать функции вместо констант.
      Сей господин отдаёт себе отчёт, что это приведёт к явному вызову функции при практически каждом использовании константы?

      В новом стандарте С++ для решения этой проблемы есть ключевое слово constexpr:
      constexpr int someIntConstant() { return 25; }
      

      Удивительно, что автор про это не вспомнил.
      • 0
        В новом стандарте отменили независимую компиляцию файлов (я правда не знаю)? Никакой constexpr не спасёт, если тело метода определено вне заголовка — при компиляции остальных файлов доступен только заголовок и компилятор просто не знает значения константы. Всё равно будут генерироваться вызовы функции для каждого использования этой константы. Разве что ясно указывается, что значение константы можно кэшировать (для циклов for и тому подобного. Иначе вообще на каждую итерацию будет вызов осуществляться — как это будут делать компиляторы обычного C++).
        • –1
          Давно умеют делать дополнительную оптимизацию после линковки
          • +2
            lto? Это редко используется и требует огромных ресурсов. Как вы себе в общем случае представляете замену вызова константой после компиляции? Это может потребовать полной перекомпиляции. Что тогда вообще делать на этапе компиляции.
      • 0
        А разве constexpr функция не должна быть явно видна вместе со своим содержимым во всех единицах трансляции?
  • 0
    insert/delete

    А я всё время думал, что insert/erase
  • 0
    image
  • +1
    Кажется, что эти рекомендации процентов на 30 состоят из защиты от дурака, которая от сколько-либо изобретательного дурака не защитит, зато читаемость кода сильно ухудшает.
  • 0
    51-й — ну очень спорный. Где ставить * в объявлении переменной-указателя — повод для большого холивара.
    При чтении кода, взгляду проще «распарсить»
    type *p_var, var;
    чем
    type* p_var, var;
    По-православному вообще надо объявлять каждую переменную в новой строке, но не весь код «православный», да и в локальных мелких функциях/методах не всегда принято страдать «многострочием».
    Кстати, рекомендации «одно объявление переменной — одна строка» я не увидел.
  • 0
    Я бы еще добавил правило использования в циклах преинкремента и предекремента вместо постинкремента и постдекремента (++i и --i вместо i++ и i--).
    Prefer preincrement to postincrement whenever possible. Preincrement is potentially faster than postincrement. Just think about the obvious implementations of pre/post-increment. This rule applies to decrement too:

    Источник: QtCreator Coding Rules
    • 0
      А можете пояснить, какой в этом смысл? ++i выглядит явно уродливее, чем i++ (например, если писать в столбик, то строчка выпадает из общего вертикального столбца), а по скорости работы это то же самое в любом компиляторе, выпущенному менее 25 лет назад.
      • 0
        Для тривиальных типов (например int) смысла в этом нет (например gcc генерирует одинаковый код ассемблера), но если используется итератор, то реализуя оператор ++ будет необходимо написать примерно следующее:
          T old( *this ); // запомнить начальное значение
          ++*this;         //инкрементировать        
          return old;     //вернуть старое значение
        

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

        P.S. 25 лет назад кстати GCC только и появился: в 1989 году.
        • 0
          Понятно. Да, если нетривиальные самописные итераторы, написанные неэффективно, то конечно. Интересно, хотя бы один итератор в STL генерирует разный ассемблерный код для ++i и i++ (при условии, что возвращаемое значение не используется)?
          • 0
            Для такого кода
            #include <vector>
            #include <string>
            using namespace std;
            int main()
            {
            	vector<string> vec (1000);
            	typedef vector<string>::const_iterator vi;
            	for ( vi i = vec.begin() ; i != vec.end(); i++) //соответственно во втором файле меняем на преинкремент
            	{
            		const string& str = *i;
            		string tmp = str;
            	}
            	return 0;
            }
            

            у меня ассемблерный код получается разный:

            При этом размер файла с ассемблерным кодом составляет:
            для преинкремента 37 449 байт и 1622 строк
            для постинкремента 38 380 байт и 1655 строк

            Если закомментировать код внутри цикла, то файлы также отличаются (при этом у постинкремента опять же больше инструкций ASM):


            К сожалению я не могу найти конкретные строки в ASM так как не силен в нем.
            В тему: Стандарт кодирования Google также говорит, что для итераторов следует использовать преинкремент.
            Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.
          • 0
            Небольшое дополнение ))
            с ключом оптимизации -O4 ASM файлы в обоих случаях получаются идентичные.
      • 0
        Из этого же ряда вопрос конкатенации строк, которую можно выполнить двумя способами:
        string s1 = 'a';
        s1 = s1 + 'b'; //способ 1
        s1 += 'b';   //способ 2
        

        Б. Страуструп в книге «Язык программирования С++» пишет:
        Оба способа добавления к концу строки семантически эквивалентны, но я предпочитаю второй, потому что он более точно отражает смысл операции и, скорее всего, реализован более эффективно.

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