Настанови зі стилю програмування C++

Ця стаття є перекладом C++ Programming Style Guidelines, поширюється з дозволу автора.


Вступ

ред.

Цей документ містить поради стосовно програмування на C++, поширені в спільноті розробників C++.

Поради базуються на розроблених стандартах з багатьох джерел, власного досвіду, місцевих вимог і потреб, як і з припущень з [1] [2] [3] [4].

Є кілька причин для створення нових настанов замість посилання на наведені вище. Основна причина - що ті настанови надто загальні і потрібно запровадити більш конкретні правила (особливо стосовно іменування). Крім того, ці настанови мають коментовану форму, що значно полегшує їхнє застосування при переглядах коду, ніж в інших настановах. На додачу, поради до програмування часто змішують поради до стилю з технічними особливостями, що заплутує. Цей документ не містить жодних технічних порад і сфокусований виключно на стилі програмування; за такими порадами звертайтеся до Настанов з практичного програмування на C++.

Хоча середовища розробки (IDE) можуть покращувати читаність коду, показуючи властивості доступу, підсвіткою коду, автоматичним форматуванням і т.ін., програмісту не слід покладатися на такі інструменти. Сирцевий код завжи слід розглядати як щось більше за IDE, де його розробили, і його слід писати так, щоб максимізувати його читаність незалежно від IDE.

Як влаштовані ці поради.

ред.

Поради згруповані за темами і кожна порада пронумерована, що робить легшим посилання на неї при переглядах.

Поради влаштовані так:


n. Загальний опис поради.

Приклад, якщо можливо

Мотивація, походження і додаткова інформація.


Мотивація також важлива. Стандарти і поради щодо програмування мають властивість розпочинати "холівари", і важливо зазначити їхнє походження.

Ступінь важливості порад.

ред.

Поради зґруповані за темами і кожна порада пронумерована для полегшення посилань на них.

В цих настановах слова треба, слід та може мають особливе значення. Треба означає, що поради треба дотримуватися, слід - наполеглива рекомендація, а може - загальна настанова.

Загальні поради

ред.
  1. Можна не дотримуватися будь-якої з цих настанов, якщо це покращує читаність тексту програми. Головна мета цих рекомендацій - підвищення читаності, а отже, зрозумілості, підтримуваності і загальної якості коду. Загальні настанови не можуть охопити всі можливі випадки, і програміст має гнучко їх застосовувати.
  2. Настанов можна не дотримуватися, якщо програміст має серйозні заперечення проти них. Це - тільки поради, а не примус всіх програмістів до спільного стилю. Досвідчені програмісти зазвичай все одно намагаються запровадити стиль на кшталт цього, і опис такого стилю, особливо якщо всі матимуть ознайомитися з ним, змусить людей почати роздумувати про стиль програмування і виробляти власні звички в цій галузі. З іншого боку, для новачків і недосвідчених програмістів ці настанови дають можливість легше засвоїти програмістський жаргон.

Домовленості про йменування

ред.

Загальні домовленості про йменування

ред.
  1. Імена типів треба писати в змішаному регістрі, починаючи з великої літери.
    Line, SavingsAccount
    
    Це загальна практика у спільноті розробників на C++.
  2. Імена змінних треба писати в змішаному регістрі, починаючи з маленької літери.
    line, savingsAccount
    

    Це загальна практика у спільноті розробників на C++. Це дозволяє легко відрізнити змінні від типів, і вирішує потенційні проблеми колізій імен, як у

    Line line;
    

  3. Іменовані константи (включно з переліками) треба писати в верхньому регістрі, з знаком підкреслення між словами.
    MAX_ITERATIONS, COLOR_RED, PI
    

    Це загальна практика у спільноті розробників на C++. В цілому, краще мінімізувати використання таких констант. В багатьох випадках найкращим вибором реалізовувати константу як метод:

      int getMaxIterations() // А не: MAX_ITERATIONS = 25
      {
        return 25;
      }
    
    Ця форма легша для читання і забезпечує єдиний інтерфейс доступу до змінних класу.
  4. Методи і функції треба називати дієсловами, записаними в змішаному регістрі, починаючи з маленької літери.
    getName(), computeTotalWidth()
    
    Це загальна практика у спільноті розробників на C++. Домовленість така сама, як і зі змінними, але функції на C++ легко відрізнити за формою виклику.
  5. Простори імен слід писати в нижньому регістрі.
    model::analyzer, io::iomanager, common::math::geometry
    
    Це загальна практика у спільноті розробників на C++.
  6. Типи в шаблонах слід називати однією великою літерою.
    template<class T> ...
    template<class C, class D> ...
    
    Це загальна практика у спільноті розробників на C++. Такі назви явно вирізняють тип шаблону серед інших використаних імен.
  7. Скорочення в іменах не треба записувати великими літерами [4].
    exportHtmlSource(); // А не: exportHTMLSource();
    openDvdPlayer();    // А не: openDVDPlayer();
    
    Використання суцільного верхнього регістру для базового імені конфліктує із поданими вище домовленостям. Змінна такого типу не може називатися dVD, hTML і т.ін., вони очевидно є погано читаними. Назви вище вказують ще на одну проблему: коли імена сполучаються з іншими, читаність значно знижується, бо наступне за скороченням слово виглядає не так, як слід.
  8. Глобальні змінні завжди слід вказувати з оператором ::.
    ::mainWindow.open(), ::applicationContext.getName()
    
    В цілому слід уникати глобальних змінних. Розгляньте можливість заміни їх одинаками.
  9. В приватні змінні класів слід додавати підкреслення наприкінці.
    class SomeClass {
      private:
        int length_;
    }
    

    Після імені і типу, зона видимості змінної - її найважливіша якість. Позначення зони видимості класу за допомогою підкреслення надає можливість легко розрізняти змінні класу і локальні змінні. Це важливо, бо змінні класу вважаються важливішими за змінні методу, і програміст має уважніше до них ставитися.

    Побічним ефектом такої домовленості є вирішення проблеми придумування адекватних імен змінним в сеттерх і конструкторах:

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

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

    Слід зазначити, що ідентифікація зони видимості змінних певний час була предметом суперечок. Тим не менш, схоже, що така практики здобуває визнання і стає все більш схваленою як домовленість про йменування в спільноті професійних розробників.
  10. Змінні загального призначення слід називати за назвою їхнього типу.
    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;
    

  11. Всі назви слід робити англійською.
    fileName;   // А не: imyaFaylu
    
    Англійська мова - переважна в міжнародній розробці.
  12. Змінним з великої зони видимості слід давати довші імена, з маленької зони можуть бути короткими[1]. Змінні, використовувані для тимчасового зберігання, та індекси краще лишати короткими. Треба, щоб програміст, який бачить таку змінну, був спроможний припустити, що її значення не буде використане за межами кількох рядків коду. Зазвичай таким цілим змінним дають імена i, j, k, m, n, символам - c та d.
  13. Ім'я об'єкту неявне, і його слід уникати в назві методу.
    line.getLength();   // А не: line.getLineLength();
    
    Другий варіант виглядає більш природним у визначенні класу, але виявляється надмірним у використанні, як показано в прикладі.

Конкретні домовленості про йменування

ред.
  1. Треба використовувати терміни get та set, коли забезпечується безпосередній доступ до атрибуту.
    employee.getName();
    employee.setName(name);
    
    matrix.getElement(2, 4);
    matrix.setElement(2, 4, value);
    
    Це загальна практика у спільноті розробників на C++. В Java ця домовленість стає більш-менш стандартною.
  2. Термін compute може використовуватися в методах, що щось обчислюють.
    valueSet->computeAverage();
    matrix->computeInverse();
    

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


  3. Термін find може використовуватися там, де щось шукають.
    vertex.findNearestVertex();
    matrix.findMinElement();
    

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

  4. Термін initialize може використовуватися там, де створюється об'єкт чи концепт.
    printer.initializeFontSet();
    

    Слід надавати перевагу американському initialize перед англійським initialise. Слід уникати скорочення init.

  5. Змінні, що представляють компоненти графічного інтерфейсу, слід закінчувати назвою типу компонента.
    mainWindow, propertiesDialog, widthScale, loginText,
    leftScrollbar, mainForm, fileMenu, minLabel, exitButton, yesToggle і т.д..
    

    Покращує читаність, явно вказуючи читачеві на тип змінної і, таким чином, на ресурси об'єкта.

  6. В назвах колекцій об'єктів слід використовувати множину.
    vector<Point>  points;
    int            values[];
    

    Покращує читаність, явно вказуючи читачеві на тип змінної і дії, які можна застосувати до її елементів.


  7. В змінних, що представляють кількість об'єктів, слід використовувати префікс n.
    nPoints, nLines
    

    Такий спосіб запису запозичений з математики, де це є узвичаєною домовленістю для позначення кількості об'єктів.


  8. В змінних, що представляють номер сутності, слід використовувати суфік No.
    tableNo, employeeNo
    

    Такий спосіб запису запозичений з математики, де це є узвичаєною домовленість для позначення номеру сутності.

    Елегантною альтернативою є додавати до таких змінних префікс i:
    iTable, iEmployee
    
    . Це явно робить їх названими ітераторами.


  9. Змінні-ітератори слід називати 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 і т.д. слід використовувати тільки у вкладених циклах.


  10. Для булівських змінних і методів слід використовувати префікс is.
    isSet, isVisible, isFinished, isFound, isOpen
    

    Це загальна практика у спільноті розробників на C++, також це рекомендовано в Java.

    Використання префіксу is вирішує розповсюджену проблему невдалого вибору булівських імен на кшталт стану прапорця. isStatus чи isFlag просто не підходять, і програмісту доводиться обирати більш значущу назву.

    Є кілька альтернатив префіксу is, які стають в нагоді в певних ситуаціях, а саме has, can та should:

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


  11. Антонімічні назви треба використовувати для протилежних операцій[1].
    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, etc.
    

    Це спрощує програму за допомогою симертрії.


  12. Слід уникати скорочень в назвах.
    computeAverage();   // А не: compAvg();
    

    Слід сказати про два різновиди слід. Перший - це звичайні слова, які є в словнику. Їх ніколи не слід скорочувати. Не пишіть:

    cmd   замість   command
    cp    замість   copy
    pt    замість   point
    comp  замість   compute
    init  замість   initialize
    

    і т.ін.

    З іншого боку, є специфічні для області знань фрази, які зазвичай упізнаються за скороченнями. Їх слід залишати скороченими. Не пишіть:

    HypertextMarkupLanguage  замість   html
    CentralProcessingUnit    замість   cpu
    PriceEarningRatio        замість   pe
    

    і т.ін.


  13. Слід уникати спеціальних імен для вказівників.
    Line* line; // А не: Line* pLine;
                // А не: LIne* linePtr;
    

    Багато змінних в середовищі C/C++ - вказівники, і таку домовленість майже неможливо виконувати. Крім того, об'єкти в C++ часто э непрямими типами, де конкретна реалізація має ігноруватися програмістом. Тільки коли конкретний тип об'єкта має особливе значення, ім'я має підкреслювати цей тип.


  14. Треба уникати зворотніх булівських імен.
    bool isError; // А не: isNoError
    bool isFound; // А не: isNotFound
    

    Проблема виникає, коли таке ім'я застосовується з операцією логічного заперечення, бо це призводить до подвійного заперечення. Не одразу можна зрозуміти, що означає !isNotFound.


  15. Константи в переліках можуть мати префікс - загальне ім'я типу.
    enum Color {
      COLOR_RED,
      COLOR_GREEN,
      COLOR_BLUE
    };
    

    Це дає додаткову інформацію, де шукати оголошення, які константи стосуються одного і того самого, і який концепт вони представляють.

    Альтернативний підхід полягає в тому, щоб завжди звертатися до констант через спільне ім'я:
    Color::RED, Airline::AIR_FRANCE
    
    і т.ін. Зауважте, що назва самого переліку зазвичай має бути одниною, на кшталт
    enum Color {...}
    
    . Множини як імена виглядають непогано, коли оголошується тип, але в коді виглядають пришелепкувато.


  16. Класи виключних ситуацій слід закінчувати на Exception.
    class AccessException
    {
      :
    }
    

    Класи виключних ситуацій, насправді, не є частиною основного задуму програми, і такі назви виокремлюють їх серед решти класів.


  17. Функції (методи, що щось повертають) слід називати згідно того, що вони повертають, а процедури (методи типу void) - згідно того, що вони роблять. Це покращує читаність, робить очевидним, що підпрограма робить і особливо - чого не робить. Це, знову таки, полегшує утримання коду без побічних ефектів.

Файли

ред.

Сирцеві файли

ред.
  1. Файлам заголовків слід давати розширння .h (бажано) чи .hpp. Сирцевим файлам слід давати розширення .c++(рекомендовано), .C, .cc чи .cpp.
    MyClass.c++, MyClass.h
    

    Це загальноприйнятний стандарт для розширень файлів на C++.


  2. Клас слід проголошувати в файлі заголовків і визначати в вихідному файлі, причому імена файлів мають збігатися з іменем класу.
    MyClass.h, MyClass.c++
    

    Це значно полегшує пошук файлів, пов'язаних із певним класом. Очевидний виняток - класи шаблонів мають бути і оголошені, і визначені в файлі .h.

  3. Реалізації слід повністю розміщувати в сирцевих файлах.
    class MyClass
    {
    public:
      int getValue () {return value_;}  // Не так!
      ...
    
    private:
      int value_;
    }
    

    Файл заголовків має проголошувати інтерфейс, сирцевий файл має його реалізовувати. Програміст, коли йому знадобиться реалізація, має бути певен, що її можна знайти в сирцевому файлі.


  4. Вміст файлу треба вміщати в 80 стовпчиків. 80 стовпчиків - поширений розмір для редакторів, емуляторів терміналів, принтерів і зневаджувачів, і файли, якими користується кілька людей, мають бути витримані в цих межах. Уникання ненавмисних розривів рядків при передачі файла іншому розробнику покращує читаність.
  5. Треба уникати особливих символів, таких, як TAB та розрив сторінки. Ці символи можуть викликати проблеми у редакторів, емуляторів терміналів, принтерів і зневаджувачів, застосовані серед багатьох розробників в багатоплатформеному середовищі.
  6. Незакінченість розірваного рядка треба робити очевидною[1].
    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 стовпчиків. Важко визначити жорсткі правила, як треба ділити рядки, але приклади вище мають надати певний натяк.

    В цілому:

    • Розривайте після коми.
    • Розривайте після оператора.
    • Вирівнюйте новий рядок за початком виразу в попередньому рядку.

Файли для включення і вирази включення

ред.
  1. До файлу заголовків треба додати захист включення.
    #ifndef COM_COMPANY_MODULE_CLASSNAME_H
    #define COM_COMPANY_MODULE_CLASSNAME_H
      :
    #endif // COM_COMPANY_MODULE_CLASSNAME_H
    

    Ця конструкція запобігає помилкам компіляції. Домовленість про назви відтворює місцезнаходження файла всередині дерева сирців і запобігає конфлікту імен.


  2. Вирази включення слід відсортувати і згуртувати. Сортувати слід за позицією в ієрархії в системі, файли нижчого рівня - першими. Залишайте пустий рядок між групами виразів включення.
    #include <fstream>
    #include <iomanip>
    
    #include <qt/qbutton.h>
    #include <qt/qtextfield.h>
    
    #include "com/company/ui/PropertiesDialog.h"
    #include "com/company/ui/MainWindow.h"
    

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

    Шляхи до файлів, що включаються, не можна робити абсолютними. Натомість треба директиви компілятора вказати кореневі каталоги для включених файлів.


  3. Вирази включення треба розташовувати тільки на початку файла. Це загальна практика. Уникає небажаних побічних ефектів компіляції від "схованих" виразів включення десь в нетрях сирцевого файлу.

Вирази

ред.

Типи

ред.
  1. Типи, вживані тільки в одному файлі, можуть оголошуватися всередині цього файла. Це сприяє приховуванню інформації.
  2. Розділи класу public, protected та private треба сортувати[2][3]. Всі розділі слід проголошувати явним чином. Впорядкування має йти від "найбільш публічних" елементів, щоб читачам не треба було обов'язково читати приватний та захищений розділ.
  3. Всі перетворення типів треба виконувати явно. Ніколи не покладайтеся на неявне перетворення типів.
    floatValue = static_cast<float>(intValue); // А не: floatValue = intValue;
    

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

Змінні

ред.
  1. Змінні слід ініціалізувати при оголошенні. Це гарантує, що змінні будуть будь-коли коректні. Іноді просто неможливо ініціалізувати змінну коректним значенням в момент оголошення:
      int x, y, z;
      getCenter(&x, &y, &z);
    

    В таких випадках краще лишити її неініціалізованою, ніж ініціалізувати значенням зі стелі.


  2. У змінних треба не робити двох сенсів. Це покращує читаність гарантуванням, що всі концепти представлені унікально. Зменшує ймовірність помилки від побічних ефектів.
  3. Слід мінімізувати використання глобальних змінних. В C++ немає жодного сенсу використовувати глобальні змінні. Те саме стосується глобальних функцій та статичних (static) змінних, зона видимості яких - файл. 49. Змінні класу ніколи не слід проголошувати публічними. Концепція приховування інформації та інкапсуляції в C++ порушується публічними змінними. Використовуйте замість них приватні змінні і функції доступу. Єдиний виняток з цього правила - коли клас є насправді структурою даних без поведінки (еквівалентий структурі в C). В цьому випадку доцільно робити змінні екземпляра класу публічними[2]. Зверніть увагу, що структури залишені в C++ тільки для сумісності з С, і уникання їх покращує читаність коду завдяки зменшенню використаних конструкцій. Використовуйте класи.
  4. При оголошені вказівників і посилань знак посилання слід ставити поруч із назвою типа, а не ім'ям змінної.
    float* x; // А не: float *x; 
    int& y;   // А не: int &y;
    

    Вказівник чи посилання є властивістю типу змінної, а не її імені. Програмісти на C часто використовують зворотний підхід.


  5. Неявну перевірку на 0 слід вживати тільки з булівськими змінними і вказівниками.
    if (nLines != 0)  // А не: if (nLines)
    if (value != 0.0) // А не: if (value)
    

    В стандарті C++ не вказано, що int та float зі значенням 0 мають реалізацію в вигляді двійкового нуля. Крім того, використання явної перевірки дає пряму підказку на вживаний тип. Звідси можна зробити припущення, що також не варто неявно перевіряти вказівники на 0, тобто треба вживати if (line == 0) замість if (line). Втім, другий варіант настільки поширений в C/C++, що його можна використовувати.


  6. Змінні слід проголошувати в найменшій можливій зоні видимості. Проведення дій зі змінною в невеликій зоні полегшує контроль на ефектами і побічними ефектами змінної.

Цикли

ред.

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, оскільки умова в do-while знаходиться в кінці циклу. Читач має проглянути весь цикл, щоб зрозуміти обсяг роботи циклу. На додачу, цикли do-while не необхідні. Будь-який цикл do-while можна легко переписати у вигляді while або for. Зменшення кількості конструкцій покращує читаність.


58. Слід уникати break та continue.

Ці вирази треба використовувати, тільки якщо вони покращують читаність порівняно із структурованими замінниками.


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

while (true) {
  :
}

for (;;) {  // Не так!
  :
}

while (1) { // Не так!
  :
}

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

Умовні вирази

ред.

61. Треба уникати складних умовних виразів. Натомість вводьте тимчасові булівські змінні[1].

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

// А не:
if ((elementNo < 0) || (elementNo > maxElement)||
     elementNo == lastElement) {
  :
}

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


62. Типовий випадок слід розміщувати в частині if, а винятковий - в частині else[1].

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"))) {
  :
}

Виконувані вирази в умовах важко читати, особливо новачкам в C/C++.

Різне

ред.

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;

Це підкреслює різну природу цілих чисел і чисел з рухомою комою - дві абсолютно різні і несумісні математичні моделі. Крім того, в останньому прикладі, це підкреслює тип змінної, який присвоюється значення, в такому місці в коді, де це може бути не очевидно.


67. Константи з рухомою комою слід завше писати з цифрою перед крапкою.

double total = 0.5;  // А не:  double total = .5;

Математичні вирази в C++ позичені з математики, тому бажано дотримуватися математичного синтаксису, де це можливо. Крім того, 0.5 значно легше прочитати, ніж .5; його неможливо сплутати з цілим числом 5.


68. Треба завжди позначати тип, що його повертає функція.

int getValue()   // А не: getValue()
{
  :
}

Якщо не зазначено тип функції, C++ припускає, що вона повертає int. Це може заплутати програмістів, які не знають про таку особливість.


69. Ніколи не слід застосовувати goto. Вирази з goto порушують принципи структурованого програмування. Тільки в деяких дуже нечастих випадках (наприклад, вихід з дуже глибоко розміщеної структури) можна подумати про використання goto, і тільки якщо структурна альтернатива явно буде менш читаною.


70. Не слід використовувати NULL, слід міняти його на 0. NULL - частина стандартної бібліотеки C, застаріла в C++.

Примітка перекладача: з появою стандарту C++11 введене нове ключове слово - nullptr, яке і слід застосовувати замість NULL чи 0 в операціях з вказівниками.

Розмітка і коментарі

ред.

Розмітка

ред.

71. Типово слід робити відступи в 2 пробіли.

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

Відступ в 1 пробіл надто малий, щоб підкреслити логічну розмітку коду. Відступ більше 4 пробілів робить глибоко вкладений код складним для прочитання і збільшує потребу розривати рядки. Серед значень 2, 3 і 4 найвживаніші варіанти 2 і 4, і 2 обрано, бо зменшує потребу розривати рядки.


72. Розмітку блоків слід робити такою, як в прикладі 1 (рекомендовано) чи прикладі 2, і треба уникати такої, як в прикладі 3[4]. Блоки класів та функції треба робити як в прикладі 2.

//Приклад 1:
while (!done) { 
  doSomething();
  done = moreToDo();
}

//Приклад 2:
while (!done) 
{
  doSomething();
  done = moreToDo();
}

//Приклад 3 (не робіть так!):
while (!done)
  {
    doSomething();
    done = moreToDo();
  }

Приклад 3 додає зайвий рівень відступів, який не підкреслує логічну структуру коду так ясно, як в прикладах 1 і 2.


73. Оголошення класів слід писати такими:

class SomeClass : public BaseClass
{
  public:
  ...
  protected:
  ...
  private:
  ...
}

Це випливає з загального правила про блоки, поданого вище.


74. Реалізацію методів слід писати так:

void someMethod()
{ 
  ...
}

Це випливає з загального правила про блоки, поданого вище.


75. Вирази if-else слід робити так:

if (умова) {
  тіло;
}

if (умова) {
  тіло; 
}
else {
  тіло;
}

if (умова) {
  тіло;
} 
else if (умова) {
  тіло;
}
else {
  тіло;
}

Це випливає з загального правила про блоки, поданого вище. Втім, дискусійним лишається питання, чи не слід розміщувати else в тому ж рядку, що й дужка, що закриває попередній блок if або else:

if (умова) {
  тіло; 
} else {
  тіло;
}

Обраний підхід вважається кращим, оскільки кожна частина виразу if-else записано в окремому рядку, що полегшує зміни, наприклад переміщення блоку else.


76. Вираз for слід писати так:

for (ініціалізація; умова; перехід) { 
  тіло; 
}

Це випливає з загального правила про блоки, поданого вище.


77. Пустий цикл for слід писати так:

for (ініціалізація; умова; перехід)
  ;

Це підкреслює, що цикл for пустий і робить очевидним для читача, що це зроблено свідомо. Втім, слід утримуватися від пустих циклів.


78. Цикл while слід писати так:

while (умова) {
  тіло;
}

Це випливає з загального правила про блоки, поданого вище.


79. Цикл do while слід писати так:

do {
  тіло;
} while (умова);

Це випливає з загального правила про блоки, поданого вище.


80. Перемикач switch слід писати так:

switch (умова) {
  case ABC :
    тіло;
    // Fallthrough
  case DEF :
    тіло; 
    break;
  case XYZ :
    тіло;
    break;
  default :
    тіло;
    break;
}

Зверніть увагу, що кожне слово case зроблено з відступом відносно виразу switch. Це виокремлює загальний перемикач switch. Також зверніть увагу на додатковий пробіл перед знаком :. Явний коментар Прохід слід додавати, якщо є вираз case без виразу break. Забути break - типова помилка, і треба зробити ясним, що його відсутність умисна.


81. Вираз try-catch слід писати так:

try {
  тіло;
}
catch (Exception& exception) {
  тіло; 
}

Це випливає з загального правила про блоки, поданого вище. Дискусія про дужки в if-else також поширюється і на try-catch.


82. Вирази if-else, for' та while з одною командою можуть писатися без дужок.

if (умова)
  вираз;

while (умова)
  вираз;

for (ініціалізація; умова; перехід)
  вираз;

Є загальна порада, що слід завжди застосовувати дужки в цих випадках. Втім, дужки - це конструкція мови, що групує кілька виразів. Дужки за визначенням надмірні для одного виразу. Поширений аргумент проти - що код зламається, якщо додати ще один рядок, не додавши дужки. Тим не менш, код ніколи не слід писати задля того, щоб задовільнити майбутні потреби в зміні.

Примітка перекладача: слід також враховувати можливість використання багаторядкових #define-макросів. Звісно, в C++, особливо в C++11, немає особливої потреби в таких макросах, а поширені бібліотеки, які використовують такі макроси, намагаються писати з урахуванням можливості неправильного застосування - але це не знімає проблеми в цілому.


83. Тип функції можна розмістити в лівому стовпчику одразу над іменем функції.

void 
MyClass::myMethod(void) 
{
  :
}

Це полегшує пошук імен функцій, бо вони всі розміщені в першому стовпчику.

Проміжки

ред.

84.

  • Стандартні оператори слід виокремлювати {{tooltip|пробілами|space charachter} з обох боків.
  • Після зарезервованих слів 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++) { // NOT: 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;
}

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

Коментарі

ред.

90. Хитрий код слід не коментувати, а переписувати[1]! В цілому, коментарів слід робити мінімум, а код слід робити самодокументованим відповідним вибором назв і явною логічною структурою.


91. Всі коментарі слід робити англійською[2]. Англійська мова - переважна в міжнародній розробці.


92. Використовуйте // для всіх коментарів, навіть багаторядкових.

// Коментар, що займає
// більше одного рядка.

Оскільки багаторівневі коментарі не підтримуються, використання коментарів з // забезпечить можливість закоментувати цілі секції файлу за допомогою /* */ для зневадження і т.ін.

Між // та початком коментаря завжди слід робити відступ, і коментарі завжди слід починати з великої літери і закінчувати крапкою.


93. Коментарі завжди слід додавати у відповідності до їхнього місця в коді[1].

while (true) { 
  // Do something
  something();
}

// А не: 
while (true) {
// Do something 
   something(); 
}

Так коментарі не зламають логічну структуру програми.


94. Заголовкові коментарі класів і методів мають відповідати домовленостям JavaDoc. По стандартизації документації класів і методів спільнота розробників Java більш зріла, ніж C/C++. Це - наслідок стандартного автоматичного інструменту Javadoc, що є частиною набору розробника і допомогає виробляти високоякісну гіпертекстову документацію з цих коментарів.

Існують аналогічні інструменти для C++. Вони використовують той самий теговий синтаксис, що й Javadoc. Наприклад, Doc++ чи Doxygen.

Посилання

ред.
  1. 1,0 1,1 1,2 1,3 1,4 1,5 1,6 1,7 Code Complete, Steve McConnell - Microsoft Press
  2. 2,0 2,1 2,2 2,3 Programming in C++, Rules and Recommendations, M Henricson, e. Nyquist, Ellemtel (Swedish telecom)
  3. 3,0 3,1 Wildfire C++ Programming Style, Keith Gabryelski, Wildfire Communications Inc.
  4. 4,0 4,1 4,2 C++ Coding Standard, Todd Hoff

Подяки

ред.

Дякую Robert P.J. Day за цінні внески.