Правила оформления качественного кода. Как правильно оформлять код.

Превью к статье о разработке длвинной вещественной арифметики

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