[Перевод] Основные принципы C++: Правила выражений и операторов
Бобра!
Что ж, мы плавно выходим на старт второго потока группы «Разработчик С++» и разбираем интересные материалы, которые накопились у преподавателя в свободное от работы и преподавания время. Сегодня рассмотрим (а потом и продолжим) серию материалов, где разбираются отдельные пункты С++ Core Guidelines.
Поехали.
В C++ Core Guidelines много правил, посвященных выражениям и операторам. Если быть точным, то более 50 правил посвящено объявлениям, выражениям, операторам и арифметическим выражениям.
*перевод
Информативные названия
Оптимальная длина переменных
- Не должны быть слишком длинными (maximimNumberOfPointsInModernOlympics.) или слишком короткими (например, x, x1)
- Длинные названия сложно печатать, короткие названия недостаточно информативны…
- Дебажить программы с названиями от 8 до 20 символов гораздо проще
- Гайдлайны не заставляют вас срочно менять названия переменных на имена из 9–15 или 10–16 символов. Но если вы найдете в своем коде более короткие названия, убедитесь, что они достаточно информативны.
Слишком длинные: numberOfPeopleOnTheUsOlympicTeam; numberOfSeatsInTheStadium; maximumNumberOfPointsInModernOlympics
Слишком короткие: n; np; ntmn; ns; nslsd; m; mp; max; points
В самый раз: numTeamMembers, teamMembersCount
Существует два правила, являющихся общими:
Правило 1: Отдайте предпочтение стандартным библиотекам перед прочими библиотеками и «самописным» кодом
Нет смысла писать сырой цикл для суммирования вектора чисел:
int max = v.size(); // плохо: пространно, цель не ясна
double sum = 0.0;
for (int i = 0; i < max; ++i)
sum = sum + v[i];
Просто используйте алгоритм std::accumulate
из STL.
auto sum = std::accumulate(begin(a), end(a), 0.0); // хорошо
Это правило напомнило мне слова Шона Парент (Sean Parent) с CppCon 2013: «Если вы хотите улучшить качество кода в организации, замените все принципы кодинга одной целью: никаких сырых циклов!».
Дословно: если вы пишете сырой цикл, скорее всего вы просто не знаете алгоритмов STL.
Правило 2: Отдайте предпочтение подходящим абстракциям перед непосредственным использованием языковых особенностей
Следующее дежавю. На одном из последних семинаров по C++ я долго обсуждал и еще дольше проводил детальный анализ нескольких замысловатых самодельных функций для чтения и записи strstream«ов. Участники были должны поддерживать эти функции, но спустя неделю так и не смогли в них разобраться.
Понять функционал мешали неправильные абстракции, на которых он был построен.
К примеру, посмотрим на самодельную функцию для чтения std::istream
:
char** read1(istream& is, int maxelem, int maxstring, int* nread) // плохо: пространно и разрозненно
{
auto res = new char*[maxelem];
int elemcount = 0;
while (is && elemcount < maxelem) {
auto s = new char[maxstring];
is.read(s, maxstring);
res[elemcount++] = s;
}
nread = &elemcount;
return res;
}
И, в сравнении, насколько проще воспринимается следующая функция:
vector read2(istream& is) // хорошо
{
vector res;
for (string s; is >> s;)
res.push_back(s);
return res;
}
Правильная абстракция часто означает, что вам не придется думать о владении, как это происходит в функции read1. И это работает в read2. Вызывающий read1 владеет result и должен удалить его.
Объявление вводит имя в область видимости. Но, если честно, я предвзят. С одной стороны, эти правила могут быть скучными, потому что во многом очевидны. С другой стороны, я видел достаточно кода, нарушающего эти правила. Например, однажды я разговаривал с бывшим программистом Fortran, который считал, что каждая переменная должна состоять ровно из трех символов.
В любом случае, я продолжу объяснять правила, потому что хорошие названия помогают делать код более легким для чтения и понимания, поддержки и расширения.
Вот первые шесть правил.
(нумерация идёт как в статье. Автор пропустил пункты 3 и 4 т.к. они не соответствуют тематике)
Правило 5: Придерживайтесь небольшой области видимости
Код будет занимать не больше экрана и хватит одного взгляда, чтобы понять, как он работает. Если область видимости слишком большая, структурируйте код и разделите его на функции и объекты с методами. Определите логические сущности и используйте очевидные названия в процессе рефакторинга. Благодаря этому ваш код станет гораздо проще.
Правило 6: Объявляйте имена в инициализаторах и условиях for-оператора, чтобы ограничить область видимости
Мы могли объявлять переменную в операторе for
еще со времен первого С++ стандарта. А в C++17 мы можем объявлять переменные и в операторах if
и switch
.
std::map myMap;
if (auto result = myMap.insert(value); result.second){ // (1)
useResult(result.first);
// ...
}
else{
// ...
} // результат автоматически уничтожается // (2)
Переменная result
(1) действительна только внутри веток if
и else
оператора if
. Поэтому result
не будет засорять внешнюю область видимости и автоматически уничтожится (2). Такая особенность есть только в C++17, раньше result
нужно было бы объявить во внешней области видимости (3).
std::map myMap;
auto result = myMap.insert(value) // (3)
if (result.second){
useResult(result.first);
// ...
}
else{
// ...
}
Правило 7: Общие и локальные имена должны быть короче, чем редкие и нелокальные
Правило может показаться странным, но мы уже привыкли. Присваивая переменным имена i
, j
и Т
, мы сразу даем понять, что i
и j
— это индексы, а T
— тип параметра шаблона.
template // good
void print(ostream& os, const vector& v)
{
for (int i = 0; i < v.size(); ++i)
os << v[i] << '\n';
}
Для этого правила есть мета-правило. Имя должно было очевидным. Если контекст небольшой, можно быстро понять, что делает переменная. Но в длинном контексте понять сложнее, поэтому нужно использовать названия длиннее.
Правило 8: Избегайте похожих имен
А вам удастся прочитать этот пример без замешательства?
if (readable(i1 + l1 + ol + o1 + o0 + ol + o1 + I0 + l0)) surprise();
Если честно, я часто с трудом различаю цифру 0 и заглавную букву O. Из-за шрифта они могут выглядеть почти одинаково. Два года назад мне потребовалось очень много времени, чтобы залогиниться на сервер. Просто потому что автоматически сгенерированный пароль содержал символ O.
Правило 9: Не используйте имена, написанные ПОЛНОСТЬЮ_КАПСОМ
Если вы пишете названия ПОЛНОСТЬЮ_КАПСОМ, то будьте готовы столкнуться с заменой на макросы — именно в них он часто используется. В части программы, представленной ниже, есть небольшой сюрприз:
// где-нибудь в заголовке:
#define NE !=
// где-нибудь в другом заголовке:
enum Coord { N, NE, NW, S, SE, SW, E, W };
// где-нибудь еще в .cpp грустного программиста:
switch (direction) {
case N:
// ...
case NE:
// ...
// ...
}
Правило 10: Объявляйте (только) одно имя за раз
Приведу два примера. Заметили обе проблемы?
char* p, p2;
char a = 'a';
p = &a;
p2 = a; // (1)
int a = 7, b = 9, c, d = 10, e = 3; // (2)
p2
— просто char
(1) и c
не инициализирован (2).
В C++17 для нас есть одно исключение из этого правила: структурированное связывание.
Теперь я могу сделать if
выражение с инициализатором из правила 6 еще более удобным для чтения.
std::map myMap;
if (auto [iter, succeeded] = myMap.insert(value); succedded){ // (1)
useResult(iter);
// ...
}
else{
// ...
} // iter и succeeded автоматически уничтожаются // (2)
THE END (to be continued)
Если есть вопросы, замечания, то ждём их тут или у нас на Дне открытых дверей.