Основные принципы 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<string> read2(istream& is)   // хорошо {     vector<string> 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<int,std::string> 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<int,std::string> myMap; auto result = myMap.insert(value)   // (3) if (result.second){       useResult(result.first);       // ... }  else{     // ... } 

Правило 7: Общие и локальные имена должны быть короче, чем редкие и нелокальные

Правило может показаться странным, но мы уже привыкли. Присваивая переменным имена i, j и Т, мы сразу даем понять, что i и j — это индексы, а T — тип параметра шаблона.

template<typename T>    // good void print(ostream& os, const vector<T>& 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<int,std::string> myMap;  if (auto [iter, succeeded] = myMap.insert(value); succedded){  // (1)     useResult(iter);       // ... }  else{     // ... } // iter и succeeded автоматически уничтожаются           // (2) 

THE END (to be continued)

Если есть вопросы, замечания, то ждём их тут или у нас на Дне открытых дверей.

FavoriteLoadingДобавить в избранное

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *