Во время написания кода начинающие программисты зачастую даже не задумываются над тем, как именно следует его оформлять. "Да и зачем? — думают они. — Программа же и так прекрасно работает!". Для очень маленьких учебных задач и проектов, возможно, это допустимо, однако не просто же так крупные компании, как например, 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);
