
Во время написания кода начинающие программисты зачастую даже не задумываются над тем, как именно следует его оформлять. "Да и зачем? — думают они. — Программа же и так прекрасно работает!". Для очень маленьких учебных задач и проектов, возможно, это допустимо, однако не просто же так крупные компании, как например, Google, создают огромные гиды (code style guide) с правилами написания кода внутри компании. Если вы действительно хотите развиваться как программист, превращаясь из начинающего кодера в крутого профессионального разработчика, то правил написания кода избежать попросту не удастся. Да и зачем избегать, если умение корректно оформить код является ценным навыком, поскольку в разы облегчает работу тех программистов, которым придётся с вами работать! В нашей статье речь будет идти в основном про языки C и C++, однако большую часть из них легко перенести на любой другой язык программирования.
Отступы и пробелы в коде
Отступы являются, пожалуй, самым главным критерием хорошей читаемости кода. А когда код читается без всяких затруднений, разбираться в нём становится гораздо легче.
Отделяйте пробелами фигурные скобки.
Переносите объявления нескольких переменных на новые строки.
Отделяйте пустой строкой объявления переменных и действия с ними (кроме присваивания):
// так делать не стоит: int x = 5, y; long v = -1; x++; double pi = 3.1415926; if (value == x) { func(); } // лучше написать так: int x = 5; int y; long v = -1; double pi = 3.1415926; x++; if (value == x) { func(); }
Разделяйте пробелами операторы и операнды:
// так делать не стоит: int expr = (x+y)*z/sqrt(x*x+y*y+z*z)-func(); // лучше написать так: int expr = (x + y) * z / sqrt(x * x + y * y + z * z) - func();
Если строка стала слишком длинной, разделите её на несколько, сделав перевод на новую строку после оператора:
int value1 = veryVeryLongFunctionNameOne() + veryVeryLongFunctionTwo() + veryVeryLongFunctionNameThree() + 456; int value2 = veryVeryLongFunction(longParameterOne, longParameterTwo, longParameterThree, longLongLongParameterFour, longParameterFive);
Разделяйте пустой строкой реализации функций:
// так делать не стоит: void func1() { // ... реализация функции 1 } void func2() { // ... реализация функции 2 } // лучше написать так: void func1() { // ... реализация функции 1 } void func2() { // ... реализация функции 2 }
Пропускайте пробел перед открывающей фигурной скобкой, а также перед открывающей круглой скобкой после операторов if
, for
, while
, switch
и тд:
// так делать не стоит: for(int i = 0; i < 10; i++){ if(data[i] % 2){ count++; } } // лучше написать так: for (int i = 0; i < 10; i++) { if (data[i] % 2) { count++; } }
Скобки и пустые строки
Не добавляйте пустую строку после открывающей ({
) и перед закрывающей (}
) фигурными скобками:
// так делать не стоит: if (a > b) { /* лишняя пустая строка */ c = a - b; while (c > (a + b) / 2) { c /= 2; /* лишняя пустая строка */ } } // лучше написать так: if (a > b) { c = a - b; while (c > (a + b) / 2) { c /= 2; } }
Добавляйте после закрывающей фигурной скобки (}
) пустую строку, но только если за ней что-то есть и это не ещё одна закрывающая скобка:
// так делать не стоит: for (int i = 0; i < 10; i++) { if (a == i) { func(); } /* лишняя пустая строка */ } /* не хватает пустой строки */ func2(); // лучше написать так: for (int i = 0; i < 10; i++) { if (a == i) { func(); } } func2();
Разделяйте логически связанные группы выражений, добавляя пустую строку между ними:
// так делать не стоит: ... vector<Point> points = getPoints(f); cout << "Readed points: " << endl; printPoints(points); vector<Point> grahamPoints = grahamHull(points); cout << "GrahamHull points: " << endl; printPoints(grahamPoints); vector<Point> jarvisPoints = jarvisHull(points); cout << "JarvisHull points: " << endl; printPoints(jarvisPoints); ... // лучше написать так: ... vector<Point> points = getPoints(f); // получение точек // вывод считанных данных cout << "Readed points: " << endl; printPoints(points); // получение данных с помощью одного метода и вывод результатов vector<Point> grahamPoints = grahamHull(points); cout << "GrahamHull points: " << endl; printPoints(grahamPoints); // получение данных с помощью второго метода и вывод результатов vector<Point> jarvisPoints = jarvisHull(points); cout << "JarvisHull points: " << endl; printPoints(jarvisPoints); ...
Переменные, функции, константы и их названия
"Как вы яхту назовёте, так она и поплывёт!" — пелось в песенке капитана Врунгеля. Вот и в программировании примерно так: как вы переменную назовёте, так она и должна использоваться! Опытные программисты даже могут не один час провести в раздумьях всего лишь из-за поиска подходящего названия!
Давайте переменным имена, по которым сразу будет понятно, для чего они используются: если нужно посчитать сумму положительных элементов массива, логичнее будет назвать переменную sumOfPositiveElement
, нежели просто s
или sum
. Или если требуется найти среднее из элементов, назовите переменную average
или avg
. Старайтесь избегать однобуквенных названий вроде x
или c
(к этому совету не относятся переменные циклов вроде i
, j
и тд.).
В зависимости от используемого языка слова в именах переменных разделяются либо с помощью символа нижнего подчёркивания, либо через верблюжийРегистр
(каждое новое слово записывается с заглавной буквы). Если же таковые правила отсутствуют, выберите наиболее удобный вам и всегда придерживайтесь его в дальнейшем. Классы обычно называются в ПаскальномРегистре
, а константы или макросы в ВЕРХНЕМ_РЕГИСТРЕ
.
Если переменная используется внутри определённого if
или while
блока, то делайте её локальной, объявляя в том же блоке кода, а не глобальной:
// так делать не стоит: int status; int value = getValue(word, dictionary); if (value > 0) { ... status = checkValue(value, correctValues); ... return status; } // лучше написать так: int value = getValue(word, dictionary); if (value > 0) { ... int status = checkValue(value, correctValues); ... return status; }
Избегайте "магических" констант в коде (простые, ничего не значащие цифры). Обозначьте их как const
и ссылайтесь на константы, а не на значения:
// так делать не стоит: for (int i = 0; i < 3; i++) { for (int j = 0; j < 7; j++) { if (field[i, j] == 0) showError(451); } } // лучше написать так: const int FIELD_HEIGHT = 3; // высота поля const int FIELD_WIDTH = 7; // ширина поля const int ZERO_FIELD_CODE = 451; // код ошибки в случае нулевой ячейки поля ... for (int i = 0; i < FIELD_HEIGHT; i++) { for (int j = 0; j < FIELD_WIDTH; j++) { if (field[i, j] == 0) showError(ZERO_FIELD_CODE); } }
Циклы и условные операторы
Циклы позволяют выполнять один код несколько раз, однако существуют различные виды циклов. Для каждой ситуации один цикл может подойти лучше, а другой хуже.
Используйте цикл for
, когда количество повторений заранее известно, а цикл while
, если число итераций заранее посчитать невозможно:
// повторять 'size' раз for (int i = 0; i < size; i++) { ... } // повторять, пока есть что считывать Point p; while (std::cin >> p.x >> p.y) { ... }
При использовании операторов цикла (for / while / do-while
) или условных операторов(if / else
) всегда ставьте фигурные скобки и соответствующие отступы, даже если тело всего оператора состоит всего из одной строки:
// так делать не стоит: if (isWin(gameField)) return; else for (int i = 0; i < freeCells.size(); i++) freeCells[i].calcValue(); // лучше написать так: if (isWin(gameField)) { return; } else { for (int i = 0; i < freeCells.size(); i++) { freeCells[i].calcValue(); } }
Старайтесь свести практику использования операторов break
и continue
к минимуму, а лучше вовсе откажитесь от неё. Используйте их только в случае крайней необходимости.
Используя оператор if / else
, избегайте проверки лишних условий внутри операторов:
// так делать не стоит: if (max >= 100) { func1(); } if (max >= 50 && max < 100) { func2(); } if (max >= 25 && max < 50) { func3(); } // лучше написать так: if (max >= 100) { func1(); } else if (max >= 50) { func2(); } else if (max >= 25) { func3(); }
Если вам требуется вернуть логическое выражение, записанное внутри if / else
, возвращайте это выражение:
// так делать не стоит: if (gameField.getFreeCellsCount() == 0) { return true; } else { return false; } // лучше написать так: return gameField.getFreeCellsCount() == 0;
Повторение частей кода
Если вы используете один и тот же код более одного раза, найдите способ удалить лишнее или перенесите его в отдельную функцию и вызывайте её вместо старых блоков. Если повторяющийся код похож лишь частично, то постарайтесь вынести различающиеся части в параметры вспомогательной функции:
// так делать не стоит: Move move = makeMove(human); gameField[move].player = human; player = ai; steps++; Move move = makeMove(ai); gameField[move].player = ai; player = human; steps++; // лучше написать так: void gameMove(Player currentPlayer) { Move move = makeMove(currentPlayer); gameField[move].player = currentPlayer; player = currentPlayer == human ? ai : human; steps++; } gameMove(human); gameMove(ai);
Если какой-то код повторяется во всех частях if / else
блока, то вынесите этот код за пределы условного оператора:
// так делать не стоит: if (a > b) { value = a + b; a = b; func(); } else { value = a + b; b = a; func(); } // лучше написать так: value = a + b; if (a > b) { a = b; } else { b = a; } func();
Эффективность
Вызывая большую функцию и используя результат несколько раз, сохраните результат в переменной вместо того, чтобы постоянно вызывать данную функцию:
// так делать не стоит: if (indexOfElement(list, element) > -1) { removeElementAtIndex(list, indexOfElement(list, element)); /* второй раз вычисляется значение функции */ } // лучше написать так: int index = indexOfElement(list, element); if (index > -1) removeElementAtIndex(list, index);