Бобра!
Что ж, мы плавно выходим на старт второго потока группы «Разработчик С++» и разбираем интересные материалы, которые накопились у преподавателя в свободное от работы и преподавания время. Сегодня рассмотрим (а потом и продолжим) серию материалов, где разбираются отдельные пункты С++ Core Guidelines.
Поехали.
В C++ Core Guidelines много правил, посвященных выражениям и операторам. Если быть точным, то более 50 правил посвящено объявлениям, выражениям, операторам и арифметическим выражениям.

*перевод
Информативные названия
Оптимальная длина переменных
Слишком длинные:
Слишком короткие:
В самый раз:

Существует два правила, являющихся общими:
Правило 1: Отдайте предпочтение стандартным библиотекам перед прочими библиотеками и “самописным” кодом
Нет смысла писать сырой цикл для суммирования вектора чисел:
Просто используйте
Это правило напомнило мне слова Шона Парент (Sean Parent) с CppCon 2013: “Если вы хотите улучшить качество кода в организации, замените все принципы кодинга одной целью: никаких сырых циклов!”.
Дословно: если вы пишете сырой цикл, скорее всего вы просто не знаете алгоритмов STL.
Правило 2: Отдайте предпочтение подходящим абстракциям перед непосредственным использованием языковых особенностей
Следующее дежавю. На одном из последних семинаров по C++ я долго обсуждал и еще дольше проводил детальный анализ нескольких замысловатых самодельных функций для чтения и записи strstream’ов. Участники были должны поддерживать эти функции, но спустя неделю так и не смогли в них разобраться.
Понять функционал мешали неправильные абстракции, на которых он был построен.
К примеру, посмотрим на самодельную функцию для чтения
И, в сравнении, насколько проще воспринимается следующая функция:
Правильная абстракция часто означает, что вам не придется думать о владении, как это происходит в функции read1. И это работает в read2. Вызывающий read1 владеет result и должен удалить его.
Объявление вводит имя в область видимости. Но, если честно, я предвзят. С одной стороны, эти правила могут быть скучными, потому что во многом очевидны. С другой стороны, я видел достаточно кода, нарушающего эти правила. Например, однажды я разговаривал с бывшим программистом Fortran, который считал, что каждая переменная должна состоять ровно из трех символов.
В любом случае, я продолжу объяснять правила, потому что хорошие названия помогают делать код более легким для чтения и понимания, поддержки и расширения.
Вот первые шесть правил.
(нумерация идёт как в статье. Автор пропустил пункты 3 и 4 т.к. они не соответствуют тематике)
Правило 5: Придерживайтесь небольшой области видимости
Код будет занимать не больше экрана и хватит одного взгляда, чтобы понять, как он работает. Если область видимости слишком большая, структурируйте код и разделите его на функции и объекты с методами. Определите логические сущности и используйте очевидные названия в процессе рефакторинга. Благодаря этому ваш код станет гораздо проще.
Правило 6: Объявляйте имена в инициализаторах и условиях for-оператора, чтобы ограничить область видимости
Мы могли объявлять переменную в операторе
Переменная
Правило 7: Общие и локальные имена должны быть короче, чем редкие и нелокальные
Правило может показаться странным, но мы уже привыкли. Присваивая переменным имена
Для этого правила есть мета-правило. Имя должно было очевидным. Если контекст небольшой, можно быстро понять, что делает переменная. Но в длинном контексте понять сложнее, поэтому нужно использовать названия длиннее.
Правило 8: Избегайте похожих имен
А вам удастся прочитать этот пример без замешательства?
Если честно, я часто с трудом различаю цифру 0 и заглавную букву O. Из-за шрифта они могут выглядеть почти одинаково. Два года назад мне потребовалось очень много времени, чтобы залогиниться на сервер. Просто потому что автоматически сгенерированный пароль содержал символ O.
Правило 9: Не используйте имена, написанные ПОЛНОСТЬЮ_КАПСОМ
Если вы пишете названия ПОЛНОСТЬЮ_КАПСОМ, то будьте готовы столкнуться с заменой на макросы — именно в них он часто используется. В части программы, представленной ниже, есть небольшой сюрприз:
Правило 10: Объявляйте (только) одно имя за раз
Приведу два примера. Заметили обе проблемы?
В C++17 для нас есть одно исключение из этого правила: структурированное связывание.
Теперь я могу сделать
THE END (to be continued)
Если есть вопросы, замечания, то ждём их тут или у нас на Дне открытых дверей.
Что ж, мы плавно выходим на старт второго потока группы «Разработчик С++» и разбираем интересные материалы, которые накопились у преподавателя в свободное от работы и преподавания время. Сегодня рассмотрим (а потом и продолжим) серию материалов, где разбираются отдельные пункты С++ 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)
Если есть вопросы, замечания, то ждём их тут или у нас на Дне открытых дверей.

