На новой работе (С++) пришлось сменить привычный стиль кода, открывающие угловые скобки { надо было ставить на той же строке, что и начало блоков if/for etc. Поначалу было неприятно отказываться от старых привычек, но за неделю новый подход мне понравился. Тогда подумал — а вдруг существуют и другие аспекты стиля, которые надо изменить несмотря на то, что они пока еще кажутся непривычными? И несмотря на то, что большинство программистов по инерции их не использует. Оказалось, что так и есть. За пару месяцев я сильно переработал стиль, результаты ниже.
Важное замечание
На изменение каждого аспекта стиля лучше давать не меньше недели на привыкание.
Обоснование
Дело не в «что красивее».
Вертикальные размеры
Новая функция и отступы
Между разными функциями обычно ставят пустую строку. Но для
|
|
|
|
Можно подумать о коде на Питоне — там как раз нету закрывающих скобок для блоков кода, и поэтому там ставят только одну пустую строку между функциями. С этим подходом, визуально ваш код на С-подобном языке будет похож на код на Питоне — если принимать, что закрывающая скобка } на новой строке равна пустой строке на Питоне (а она визуально почти равна — одна еле заметная палочка). Тогда количество строк кода для аналогичной программы на Питоне сравняется. Бойтесь питонщиков, которые выигрывают за счет краткости своего кода!
Автозамена регулярными выражениями: }nn -> }n.
Вначале было непривычно. Но за неделю подсознание привыкло, и пошел дальше. А программируя на Питоне, теперь я бы убрал пустую строку после блоков совсем — ведь достаточно отступов!
Комментарии
/*многострочный комментарий
неправильно
*/
/* многострочный комментарий
правильно */
Код читается намного чаще, чем пишется. Поэтому удобство написания (не следить за */) менее важно, чем удобство чтения. Чтобы понять, что комментарий кончился,
Это небольшое изменение стиля, поэтому на него можно не тратить неделю.
Include guard
Все современные компиляторы С++ поддерживают pragma once, поэтому include guard на основе ifndef/define/endif надо удалить и заменить. Пропадает опасность коллизии имен символов препроцессора, код становится короче на 2-4 строки (учитывая пустые строки), и быстрее компилируется (препроцессору не надо просматривать файл до конца в случае срабатывания guard). Далее, pragma once, если он есть, должен идти без лишних пустых строк в начале и конце. Подсознание привыкнет, что первая строка кода в файле (после комментариев начала файла) всегда pragma once, и отделит его от комментария по цвету. А даже если вы дальтоник, то подсознание отделит этот блок по набору // у комментариев и # у pragma once.
Это небольшое изменение, поэтому на него можно не тратить целую неделю. Автозамена:
#pragma oncenn -> #pragma oncen
nn#pragma once -> n#pragma once
Пустая строка в начале функции
Начало функции или другого блока итак хорошо видно — хотя бы по уровню отступа. И строки внутри функции имеют дополнительный отступ. Поэтому (после переобучения) пустая строка после начала функции не нужна.
|
|
|
Напротив, пустая строка сбивает с толку: зачем ее оставили? Почему такое отступление от общих правил? Может и после if ставить пустые строки? Кому-то не достаточно увеличения отступа? Зачем было обучаться такому исключению из правил? Кому это выгодно?
Автозамена: {nn -> {n
Через неделю использования нового подхода он становится привычным, и можно двигаться дальше.
#include
#include (если они есть) в начале файла должны идти сразу же, без лишних пустых строк (сразу после комментария начала файла или pragma once). После блока с include обычно ставлю forward declarations, тоже без пустой строки — ведь у них общее назначение, позволить программе собраться без ошибок (с точки зрения java-программиста, который не должен подключать лишние include и forward declarations). Forward declarations легко отличаются от #include по цвету и отсутствию #. Но после блока include/forward declarations я ставлю пустую строку — чтобы отделять например создание нового класса от forward declarations, потому что у них одинаковые отступы, и надо уметь быстро замечать, где начало основного содержимого файла.
Комментарии начала файла
В первом комментарии обычно ставится имя файла, имя создателя и лицензия. По возможности, имя файла и имя создателя переводится на первую строку, и лицензия ужимается до одной строки вместо копирования целого абзаца.
//file.cpp by AuthorName
//This file is licensed under GPL, www.gnu.org/copyleft/gpl.html
Горизонтальные размеры.
Унификация отступов внутри класса
В классах, метки области видимости типа public/private должны идти под новыми отступами, и код под этими метками — под еще одним отступом.
|
|
|
Зачем новый отступ? Да все затем же — чтобы быстро находить, где начало блока («а это какая область видимости?») и не сбиваться с толку — не путать начало класса и «public:» как область видимости, если у них одинаковые отступы («а где начинается этот класс? Не пропустить бы во время прокрутки...»). Почему принято иначе? Подозреваю, что дело идет от ширины экрана и меток для goto: «public:» похоже на создание новой метки для goto, и древние текстовые редакторы могли автоматически его выравнивать, как и метку. И древние программисты на ассемблере привыкли, что метки идут с самого слева, а не по уровням вложенности кода. Низкое разрешение древних ЭЛТ-мониторов способствовало закреплению такого подхода (на лишнюю табуляцию мало места). Но если разрешение вашего экрана больше, чем 320*240, то можно смело отказываться от этого атавизма, и с ухмылкой исправлять предложения текстового редактора превратить иерархические блоки внутри класса в аналог одноуровневого goto.
Эта унификация упрощает жизнь: ведь все блоки идут с новым отступом, почему для меток области видимости должно быть иначе?
Пустые функции
Пустые виртуальные деструкторы чаще всего так и остаются пустыми. Их пустое тело лучше прописывать прямо в определении класса, чтобы читатель кода не думал — «а вдруг там что-то есть? Надо посмотреть».
Длинные имена классов
Если лень писать такое:
VeryLongClassName* veryLongClassName = new VeryLongClassName(params);
то можно писать так:
auto veryLongClassName = new VeryLongClassName(params);
и даже так:
New<VeryLongClassName> shortName(params);
что возможно, если применять шаблонный класс New (умный указатель).
template<class Class>
class New {
private:
Class*d;
public:
New() {
d = new Class();
}
template<typename T0>
New(T0 t0) {
d = new Class(t0);
}
template<typename T0, typename T1>
New(T0 t0, T1 t1) {
d = new Class(t0, t1);
}
template<typename T0, typename T1, typename T2>
New(T0 t0, T1 t1, T2 t2) {
d = new Class(t0, t1, t2);
}
template<typename T0, typename T1, typename T2, typename T3>
New(T0 t0, T1 t1, T2 t2, T3 t3) {
d = new Class(t0, t1, t2, t3);
}
virtual ~New() {}
Class* operator->() { return d; }
const Class* operator->()const { return d; }
operator Class* () { return d; }
operator const Class* ()const { return d; }
};
В последнем случае, если конструктор без параметров, такие переменные можно ставить в определение класса, и тогда не надо будет следить за созданием объекта. И это все — не дожидаясь нововведений С++11 в VS2012.
Имена
Как принято в Java/Qt: ClassName, functionName. Плохо: function_name — ведь дольше набирать, тянуть палец к далеко расположенной клавише подчеркивания, дольше читать,
Tabs
Табуляция, не пробелы! habrahabr.ru/post/118208/
Запятые перед именами
В списках инициализации или списках переменных функций, такие конструкции:
|
|
надо отменить, и перенести запятые в конец строки. Зачем вообще запятые ставить в начало? Раньше я их ставил, чтобы 1) не следить за последней запятой — если добавляется новая строка, то не надо добавлять запятую в конец прошлой, достаточно написать в начало текущей, аналогично и при удалении, переносе строк между классами 2) чтобы визуально подчеркнуть блок однотипного кода, да и приятно смотреть на выровненные символы. По поводу 1 — код читается намного чаще, чем пишется, поэтому один символ — слабый аргумент. Зато такой код хуже воспринимается: чтение происходит с левой стороны, программист итак знает, что это список инициализации или список параметров функции, и ему важнее видеть имена переменных, а не сначала пропускать запятые и уж затем начинать читать переменные. Таким образом, второстепенная информация и ненужные украшательства ставятся на первое место, а более важная информация — на второе. Такой подход словно говорит нам: «Обратите внимание, переменные разделяются запятыми! Это не инструкции с точками с запятой, это запятые! Это очень важно! Ведь вы могли забыть, что списки инициализации разделяются запятыми — а вдруг вы новичок, и не знаете? Без запятой всему конец — а ее так легко пропустить! И компилятор не напомнит, что запятая пропущена — и что же тогда делать? А вдруг вам захочется скопировать запятую, а она уже тут как тут — в самом начале строки! А вы замечали, что дело можно развернуть и так, что запятые могут быть важнее переменных? Это ведь смотря на что обращать внимание, ведь все относительно! Как приятно видеть знакомую запятую в начале строки, вместо незнакомых имен переменных!». Долой украшательства, да здравствует удобство чтения более важной информации.
Заключение
Плавное изменение стиля кода для С-подобных языков может повысить конкурентоспособность этих языков и программистов их использующих, чуть приблизить их к другим языкам по краткости (например к Питону). Для С++ это особенно важно — учитывая, насколько программы на других языках бывают короче (взять тот же linq для C#). Экономьте электроэнергию и ресурс клавиатуры, всего хорошего!
Автор: neurocod