Гайд з оформлення коду на С++ від Стенфордського університету


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

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

Пропуски і відступи

Відділяйте пробілами фігурні дужки:

// Погана практика int x = 3, y = 7; double z = 4.25; x++;if (a == b) { foo (); }// Гарна практика int x = 3;int y = 7;double z = 4.25;x++;if (a == b) { foo ();}

Ставте пробіли між операторами і операндами:

int x =  (a + b)  * c / d + foo ();

Коли рядок стає довшим за 100 символів, поділіть його на два, перевівши на новий рядок після оператора, і продовжуйте писати:

int result = reallyLongFunctionOne () + reallyLongFunctionTwo () + reallyLongFunctionThree () + reallyLongFunctionFour ();int result2 = reallyLongFunction (parameterOne, parameterTwo, parameterThree, parameterFour, parameterFive, parameterSix);

Залишайте порожні лінії між функціями і між групами виразів:

void foo (){ ...} // порожня лініяvoid bar (){ ...}

Назви і змінні

Давайте змінним описові імена, такі як firstName чи homeworkScore. Уникайте одинолітерних назв типу x чи c, за винятком ітераторів типу i.

Називайте змінні і функції, використовуючи верблюжийРегістр. Називайте класи ПаскальнимРегістром, а константи – у ВЕРХНЬОМУ_РЕГІСТРІ. Дізнатися більше про верблюжий регістр Ви можете в цій статті.

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

Вибирайте відповідний тип даних для Ваших змінних. Якщо змінна містить лише цілі числа, то визначайте її як int, а не double.

Використайте текстовий рядок, стандартний для C++, а не С. С++ плутає теми, що має два види текстових рядків: клас string із С++ і старий char* (масив символів) із С:

// Погана практика: текстовий рядок у стилі Cиchar* str = "Hello there";// Гарна практика: текстовий рядок у стилі C++string str = "Hello there";

Якщо певна константа часто використовується у Вашому коді, то позначте її як const і завжди посилайтеся на цю константу, а не на її значення:

const int VOTING_AGE = 18;

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

// Погана практика int count; // Глобальна змінна void func1 (){ count = 42;}void func2 (){ count++;}int main (){ func1 (); func2 ();}// Хороша практика!int func1 (){ return 42;}void func2 (int& count) { count++;}int main (){ int count = func1 (); func2 (count);}

Базові вирази С++

С++ ґрунтується на С, тому завжди є варіант розв’язати задачу “шляхом С++” і “шляхом С”. Наприклад, коли Ви бажаєте вивести що-небудь на системну консоль, то можете зробити це “шляхом С++”, використавши оператор виведення cout, тоді як “шляхом С” Ви б використали глобальну функцію типу printf:

// Погана практика printf ("Hello, world!n");// Гарна практика cout << "Hello, world"! << endl;

Часто зазнаєте труднощів із вибором між for і while? Використайте цикл for, якщо Ви знаєте кількість повторень, а цикл while, коли кількість повторень Вам невідома:

// Повторює 'size' разів for (int i = 0; i < size; i++) { ...}// Повторює, поки більше не буде рядків string str; while (input >> str) { ...}

Коли використовуєте оператори управління типу if / else, for, while, завжди використовуйте {} і відповідні відступи, навіть якщо тіло всього оператора керування складається лише з одного рядка:

// Погана практика if (size == 0) return;else for (int i = 0; i < 10; i++) cout << "ok" << endl;// Гарна практика if (size == 0) { return;} else { for (int i = 0; i < 10; i++) { cout << "ok" << endl; }}

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

У C++ є функція exit, яка негайно завершує програму. Настійно не рекомендується використовувати цю функцію. Програма завжди повинна закінчуватися природно, досягаючи оператора return функції main.

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

// Погана практика if (grade >= 90) { cout << "You got an A"!;}if (grade >= 80 && grade < 90) { cout << "You got a B"!;}if (grade >= 70 && grade < 80) { cout << "You got a C"!;}// Хороша практикаif (grade >= 90) { cout << "You got an A"!;} else if (grade >= 80) {
cout << "You got a B"!;} else if (grade >= 70) { cout << "You got a C"!;}...

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

// Погана практика if (score1 == score2) { return true;} else { return false;}// Гарна практика return score1 == score2;

Ніколи не перевіряйте значення логічного типу, використовуючи == або!= с true або false:

// Погана практика if (x == true) { ...} else if (x != true) { ...}// Гарна практика if (x) { ...} else { ...}

Надмірність

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

// Погана практика foo ();x = 10;y++;...foo ();x = 15;y++;// Гарна практика helper (10);helper (15);...void helper (int newX) { foo (); x = newX; y++;}

Перемістіть загальний код із виразу if / else, щоб він не повторювався:

// Погана практика if (x < y) { foo (); x++; cout << "Вітаю!";} else { foo (); y++; cout << "Вітаю!";}// Гарна практика foo ();if (x < y) { x++;} else { y++;}
cout << "Вітаю!";

Коментарі

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

Заголовок функції / конструктора. Розмістіть заголовний коментар на кожному конструкторі й функції Вашого файлу. Заголовок повинен описувати поведінку та / або мету функції.

Параметри / повернення. Якщо Ваша функція приймає параметри, то стисло опишіть їх мету і значення. Якщо Ваша функція повертає значення – стисло опишіть, що вона повертає.

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

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

TODO. Слід видалити всі // TODO коментарі, до того як завершувати і здавати програму.

Ефективність

Викликаючи велику функцію і використовуючи результат кілька разів, збережіть результат у змінній замість того, щоб постійно викликати цю функцію:

// Погана практика if (reallySlowSearchForIndex ("abc")  >= 0) { remove (reallySlowSearchForIndex ("abc"));}// Гарна практика int index = reallySlowSearchForIndex ("abc");if (index >= 0) { remove (index);}

Функції та процедурне проектування

Якісно спроектована функція має такі характеристики:

  • повністю виконує чітко поставлене завдання;
  • не бере собі надто багато роботи;
  • не пов’язана з іншими функціями безцільно;
  • зберігає дані максимально стисло;
  • допомагає розпізнати і розділити структуру програми;
  • допомагає позбавитися від надлишків, які інакше були б у програмі.

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

/* * Розв'язує квадратне рівняння ax^2 + bx + c = 0, * вносячи результати в root1 і root2. * Передбачається, що ці рівняння мають два корені. */void quadratic (double a, double b, double c
double& root1, double& root2){ double d = sqrt (b * b - 4 * a * c); root1 =  (- b + d)  /  (2 * a); root2 =  (- b - d)  /  (2 * a);}

Коли необхідно повернути значення з функції, використайте значення return:

// Погана практика void max (int a, int b, int& result) { if (a > b) { result = a; } else { result = b; }}// Гарна практика int max (int a, int b) { if (a > b) { return a; } else { return b; }}

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

Використайте посилальні змінні, а не покажчики. Одна з причин – посилальні змінні, на відміну від покажчиків, не можуть набувати значення NUL

// Погана практика // Приймає покажчик void process (BankAccount* account) { ...}// Гарна практика // Приймає адресне посилання void process (BankAccount& account) { ...}

Якщо Ви передаєте об’єкт у функцію і код не змінить вид  у об’єкта, то передайте його як const-посилання:

// Погана практика// Приймає покажчик void display (BankAccount account) { ...}// Гарна практика// Приймає константне посилання void display (const BankAccount& account) { ...}

Уникайте “ланцюгових” викликів, коли багато функцій викликають одна одну по ланцюжку, не повертаючи значення в main. Переконайтеся, що main є стислим описом усієї програми:

// Погана практикаmain|+-- function1 | +-- function2 | +-- function3 | +-- function4 | +-- function5 | +-- function6// Гарна практика main|+-- function1|+-- function2| || +-- function3| || +-- function4|+-- function5| || +-- function6

Проектування класів

Інкапсуляція. Відділяйте Ваші об’єкти, роблячи всі поля даних у Вашому класі private:

class Student {private: int homeworkScore; ...

.h vs .cpp. Завжди розміщуйте оголошення класів та їх частин у власні файли, ClassName.h. Завжди згортайте файли оголошення класів .h у блок препроцесорів #ifndef / define / endif, щоб уникнути множинних оголошень одного класу :

// Point.h#ifndef_point_h#define_point_hclass Point {public: Point (int x, int y); int getX () const; int getY () const; void translate (int dx, int dy);private: int m_x; int m_y;};#endif// Point.cpp#include "Point.h"Point::Point(int x, int y){ m_x = x; m_y = y;}void Point::translate(int dx, int dy){ m_x += dx; m_y += dy;}...

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

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

За матеріалами “Stanford C++ Style Guide”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *