Hatred's Log Place

DON'T PANIC!

c_c_coding_style

Mar 29, 2016 - 9 minute read -

C/C++ Coding Style

Версия 2.0-draft

29 марта 2016г.

Изменения

Версия 2.0 [29 марта 2016]: * Изменение в именовании свободных функций (underscore) * TBD

Версия 1.2 [13 ноября 2010]: * Изменение в именовании приватных переменных класса * Рекомендации по поводу стиля при использовании библиотеки Qt * Дополнения для логических переменных: помимо префикса is использовать аналогичные (has. can & etc) * Дополнения о порядке директив include

Версия 1.1 [8 августа 2010]: * Для функций, в которых производятся действия по инициализации допускается использовать префикс init (сокращенный вариант initialize) * Убрана жесткая рекомендация по использованию 0 вместо NULL в C++ * Добавлена рекомендация использовать calloc вместо malloc в C * Добавлена рекомендация добавлять пробел после ключевого слова языка и открывающей скобкой * Рекомендация не использовать одиночные блоки if, while, for и т.п. * Добавлена рекомендация по использованию astyle с учетом стандартов перечисленных здесь.

Версия 1.0 [2 февраля 2010]: * Первая оформленная версия

Нотации

Pascal - слова пишутся слитно, каждое слово с большой буквы: NewClass

camelCase - слова пишутся слитно, первое слово с маленькой буквы, остальные с большой: newClass

Underscore - слова пишутся меленькими буквами, разделение знаком подчерка “_”

Введение

Многие элементы стиля сформировались годами, сгруппировать и оформить помогло: * http://geosoft.no/development/cppstyle.html * http://geosoft.no/development/cpppractice.html Но не со всеми аспектами, приведенными там я согласен, поэтому здесь вы найдете достаточно много различий, но в целом, данное описание стиля соответствует моему видению, кроме того, те моменты, в которых у меня не было однозначности, взяты из вышеуказанных источников, некоторые аспекты просто закреплены, дабы по ходу пьессы не возникало вопросов.

Имена

  1. Типы данных, классы пишутся в нотации Pascal:CLine, NewClass
  2. Для интерфейсов (чисто-виртуальные классы) или абстрактных классов (которые содержат, как минимум, один, но не все) не использовать никаких префиксов и суффиксов типа I (IFunction), A (AFunction) или Abstract (AbstractFile)```c++ class Function { public: virtual ~Function() = default; virtual void call() = 0; }; class LongExecutionFunction : public Function { public: virtual void call() override { … } };

    1. Имена переменных пишутся в нотации camelCase:```Cline, newValue```
    2. Имена методов пишутся в нотации camelCase:```Cline(), setLine(line)```
    3. Имена свободных функцию пишутся в нотации underscore: ```Csome_free_function()```
    4. Именованные константы (define, static const, элементы enum  (но не `enum class`)) записываются большими буквами, разделение знаком подчерка "_":```CMAX_COUNT```
    1. Элементы `enum class` (C++11) пишутся в нотации Pascal:```c++
    enum class Foo
    {
    SomeItem,
    OtherValue
    };
    ...
    auto v = Foo::SomeItem;
  3. Области имен записываются меленькими буквами:Cio::iomanager

  4. Типы в шаблонах задаются одной большой буквой:Ctemplate<class T> ...

  5. В общепринятых сокращениях (HTML, DPI, etc) в нотациях Pascal и camelCase, только первая буква большая:CviewHtml(), htmlSource(), HttpServer

  6. Глобальные переменные задаются префиксами (крайне рекомендуется сводить количество таких переменных к минимому):

    • C: g_
    • C++: ::Cg_mainWindow, ::mainWindow
  7. Приватные и защищенные переменные в классах начинаются с префикса m_:```C class Foo { private: int m_length; };

    1. Публичный переменные классов и структур пишутся без префиксов, как обычные переменные:```C
    class Foo
    {
    public:
    int length;
    };
  8. Переменные в прототипах функций должны иметь осмысленные названия, не использовать (по возможности) сокращений.

  9. В качестве счетчиков циклов использовать переменные i, j, k, l, m, n; для временных символьных и десятичных величин можно использовать переменные c, d.

  10. Имена - английские слова.

  11. Для переменных, описываюих количество каких-то элементов или значений, используйте суффикс Count:```c++ size_t itemsCount;

    1. FIXME (не очень хорошее правило) Для булевых (bool) методов и переменных используйте префикс `is` и вариации типа `has`, `can`, `should` и т.п.:```CisSet(), isProcessInProgress```
    2. Для методов, призванных манипулировать значениями переменных класса:
    1. для геттеров - не использовать никакие префиксы (допускается префикс is/has/can/etc для булёвых проверок):```c++
    value();
    line();
    1. для сеттеров - использовать префикс set:```c++ setValue(value)

      3. Для инициализации каких-то значений, используйте в имени метода используйте префикс `initialize` (допускается `init`):```CinitializeGraphicsView(); initSpeller();```
      4. По возможности используйте другие парные префиксы:```C
      add/remove, create/destroy,
      start/stop, insert/delete, increment/decrement,
      old/new, begin/end, first/last, up/down,
      min/max, next/previous, open/close, show/hide,
      suspend/resume, ...
  12. По возможности в именовании булевых переменных используйте прямую логику:isError, но isNoError

  13. Перечесления должны содержать общую для типа часть:```C enum Color { COLOR_RED, COLOR_GREEN COLOR_BLUE };

    1. не распространяется на C++11 `enum class` - по именованию - выше.
    
    ## Файлы исходных текстов 
    
    3. Имена исходников меленькими буквами
    4. Каждый класс в своей паре cpp/h, имя файла соответствует имени класса: mycustomevent.h, mycustomevent.cpp
    5. Не использовать реализации метода в заголовочном файле (кроме коротких, которые рекомендуется заинлайнить)
    6. Использовать максимальную длину строки 80-100 символов
    7. Базовый отступ 4 пробела
    8. Не стесняться в использовании пробелов при составлении выражений, особенно касается математических формул.
    9. При разрыве строки, делать отступ под начала выражения:```C
    total = a + b + 
        c + d;
    
    function(param1,
         param2);
  14. Заголовочные файлы должны включать include-защиту. Предпочитать:```c++ #pragma once

    #ifndef HEADER_FILE_NAME_H
    #define HEADER_FILE_NAME_H
    ...
    #endif /* HEADER_FILE_NAME_H */

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

  15. Директивы #include группировать по смыслу, в частности более низкоуровневые располагать выше остальных:```C #include #include

#include #include

#include “projectheader1.h” #include “projectheader2.h”

    1. Исключение: директивы STL размещать выше директив STDC: они являются "родными" для языка.
    2. В C++ коде предпочитать использовать C++-врапперы инклудов, типа **cerrno** вместо **errno.h**
  2. Директивы препроцессора (кроме условных) стараться располагать в начале файла



## Определения 

### Типы 

  1. Стараться уменьшать области видимости типов данных
  2. Директивы `public`, `protected`, `private` располагать в порядке уменьшения их публичности
  3. Для методов и переменных класса использовать разные секции `public`, `protected`, `private`
  4. Конструктор и деструктор класса располагать первыми в `public` секции (если не предполагается их закрытие в `private`/`protected`)
  5. Использовать явное приведение типов:
    * C: ```CfloatValue = (float)intValue;```
    * C++: ```C++floatValue = static_cast<float>(intValue);```

### Переменные 

  1. Инициализируем переменные там же, где их определяем
  2. Переменные должны называться так, что бы не вызывать неоднозначности в их трактовке
  3. Как выше писал, старайтесь уходить от глобальных переменных
  4. Опять таки, не делайте публичными переменные класса, используйте для манипуляции с ними геттеры/сеттеры
  5. FIXME (спорный пункт) Опускать сравнение с нулем можно только для типа bool:```C
if (linesCount != 0) // НЕЛЬЗЯ: if(linesCount)
if (isSet) // выше: bool isSet;
  1. Аналогично типам данных, старайтесь уменьшать область видимости переменных

Циклы

  1. В цикле for внутри делайте инициализацию только тех переменных, которые осуществляют управление циклом:```C sum = 0; for (i = 0, j = 0; i < 10 && j < 2; i++) { sum += getValue(i, j); j = getLatency(i, sum); }

    1. Управляющие переменные для цикла *while* инициализируйте перед входом в цикл, дабы не потерять.
    2. Старайтесь использовать `for-each` циклы C++11 для обхода коллекций.
    ### Разное 
    
    1. Не используйте магических чисел (кроме 0), используйте именованные константы
    2. При инициализации, работе с вещественными числами, всегда используйте десятичную точку:```C
    double value = 0.0;
    double step  = 3.0e8;
    ...
    step = value1 * step / 100.0;
  2. При работе с вещественными числами, не опускайте не значащих нулей:Cdouble value = 0.5; // а не .5

  3. Старайтесь обходиться без goto

  4. Для инициализации указателя нулём используйте:

    1. C: NULL, определён в stddef.h
    2. C++98/03: 0
    3. C++11 и новее: nullptr
  5. Не используйте нескольких выражений в одной строке (разделенных ;)

  6. Следовать правилу: одна строка - одно объявление переменной:```C int x; int y; // а не: int x, y;

    1. В C++ программах используйте `new`/`delete` вместо `malloc`
    2. В Си программах, по возможности, используйте `calloc` вместо `malloc`, кроме случаев, когда память всё равно будет проинициализирована после вызова.
    3. При переопределении виртуальных методов используйте `override`.
    4. Если наследование от класса или переопределение метода не планируется, определяйте методы или классы с ключевым словом `final`.
    5. Используйте константность везде, где только можно (const-correctness).
    6. Если не предполагается, что метод или функция могут бросить исключения, объявляйте их как `noexcept` (C++11) или `throw()` (C++98/03).
    7. Ни в коем случае не используйте явное указание возможных к выбросу исключений (при помощи `throw(except, ...)`), это не говорит ни о чём: может быть выброшено и любое другое исключение тоже.
    
    ## Оформление блоков кода 
    
    
    ### Блоки 
    
    
    1. Как говорилось выше, базовый отступ - 4 пробела
    2. Если условие содержит одну строку: не используйте фигурные скобки
    3. Если блок какой-то из блоков (`if`, `else`, `else if`) состоит из нескольких выражений (используются фигурные скобки), то остальные блоки тоже нужно обрамить в фигурные скобки, даже если они состоят из одного выражения.
    4. Фигурные скобки с новой строки:```C
    if (...)
    {
    ...
    }
  7. Декларация класса:```C class ClassFoo : public ClassBar { public: …

protected: …

private: … }

  1. Реализация метода/функции:```C
void function()
{
    ...
}

или```C void function() { … }

  1. Блок `if-else`:```C
if (...)
{

}
else
{

}
...
if (...)
{

}
else if (...)
{

}

// Одиночный if
if (...)
   ...;
else if (...)
   ...;
else
   ...;
   
  1. Блок цикла for:```C for (initialize; condition; update) { … }

// Empty for for (initialize; condition; update);

// Одиночный for for (initialize; condition; update) …;

// for-each for (const auto &ptr : items) { … }

  1. Блок циклов `while` и `do-while`:```C
while (condition)
{
    ...
}

// Одиночный while
while (condition)
     ...;

do
{
   ...
}
while (condition);
  1. Блок switch:```C switch (value) { case ABC: { … break; }

    case DEF: { … // fallthrough }

    default: { … break; } }

    1. Блок `try-catch`:```C++
    try
    {
    ...
    }
    catch (Exception& exception)
    {
    ...
    }

Пробелы и другие пробельные символы

  1. Ставить пробелы:
    • вокруг знаков математических и логических операций (в том числе операций присваивания и сравнения)
    • после запятых, точки с запятой
    • рекомендуется ставить один пробел после ключевого слова языка и открывающей скобкой (с if, while, for, switch и т.п.)
  2. Разделять пустой строкой логически связанные блоки кода:```C if (isEnd) return;

    m_basePoints << p; std::cout << “Count of points: “ << m_basePoints.size() << std::endl;

    if (m_isPathAndBasePointsSame) m_path_points << p;

    1. Выравнивать объявление переменных:```C
    FILE* fp;
    int   line_count;
    char  c;
  3. Вообще использовать выравнивание пробелами для улучшения наглядности кода.

Исключения и дополнения

Qt

  1. Имена объектов элементов GUI (кнопки, панели, метки, текстовые окна и т.д.) задавать как обычные, не приватные переменные, и снабжать суффиксом, описывающим их принадлежность:Cmain_form, exit_button, exit_label, info_text
  2. Автоматические сгенерированные обработчики сигналов не смешивать с вручную написанным кодом и располагать в конце файла с имплементацией (точнее свой код писать выше). В заголовочном файле в сециях public signals:, prinvate signals свои обработчики объявлять выше автоматически сгенерированных.
  3. Не смешивать реализацию обработчиков сигналов и прочих функций, рекомендованная структура файла:C // 1. Глобальный комментарий с информацией о лицензии и кратким описанием файла // 2. Директивы #include // 3. присваивание констант // 4. static-функции, если есть // 5. конструктор(ы) класса // 6. деструктор класса // 7. функции, реализующие логику класса // 8. созданные вручную обработчики сигналов // 9. созданные автоматически обработчики сигналов (обычно добавляются автоматом в конец, имеют префикс `on_`, тело, соответствующее имени объекта и суффикс `_signalName` отражающий какой сигнал обрабатывается, такие обработчики автоматически связываются moc-компилятором)

cioner c_rules

comments powered by Disqus