четверг, 1 августа 2024 г.

[prog.c++.wtf] Еще немного про самый странный паттерн в коде

Продолжение вот этой темы. За прошедшие месяцы регулярно сталкивался с этим паттерном в разных кодовых базах, написанных разными разработчиками. Такое ощущение, что выросло новое поколение программистов, смотрящих на код совсем по-другому.

Вроде бы понял, в чем смысл. С обычными return-ами часто пишут так:

int some_func(some_arg & arg) {
  if(!is_valid(arg))
    return -1;

  if(!approriate_state(arg))
    return -1;

  if(!some_another_condition(arg))
    return -1;

  ... // Ну а здесь уже какие-то действия.
}

Похоже, этот подход пытаются переложить на тело цикла, только вместо преждевременных return-ов используют continue:

for(auto & item : collection) {
  if(!is_valid(item))
    continue;

  if(!appropriate_state(arg))
    continue;

  if(!some_another_condition(arg))
    continue;

  ... // Ну а здесь уже какие-то действия.
}

Понять-то я понял, а вот принять не получается 🙁

Во-первых, дело в том, что в реальном коде все это выглядит не так опрятно и понятно, как в моих псевдопримерах. Между if-ами, зачастую, еще какие-то действия выполняются, а сами if-ы могут быть и вложенными, и кроме continue еще встречаются и break, и даже return...

В общем, когда пытаешься вчитаться в незнакомый код вот с такими вот continue, то приходится в голове таблицу переходов вручную выстраивать. Невольно вспоминаются времена программирования на Basic-е с нумерацией строк и ассемблере 😁

Во-вторых, недавно довелось несколько циклов преобразовать в лямбды, которые передаются во что-то типа `std::ranges::for_each`, т.е. было:

for(const auto & item : detect_collection_for_processing()) {
  ... // Тут обработка с continue/break-ами.
}

а стало:

for_all_ready_to_processing_items(token, [&](const auto & item) {
  ... // Тут обработка, но уже без continue/break.
});

Так вот, оказалось, что тела тех циклов, которые были написаны по-человечески, без continue и break, трансформируются в лямбды легко и непринужденно. Ну вот вообще не припомню, чтобы нужно было делать что-то кроме замены for-а на вызов функции с лямбдой.

Тогда как тела циклов с continue -- это вот прям минное поле 😔
Хорошо хоть, что компилятор бьет по рукам когда видит continue вне цикла. Несколько раз это меня выручало, т.к. в сложных if-ах я continue просто не замечал.

Посему в очередной раз приходится делать вывод о том, что куски кода, оформленные в виде "одна точка входа, одна точка выхода", оказываются наиболее простыми и в изучении, и в сопровождении. Деды, которые учили меня структурному программированию 35 лет назад, были таки правы.


Не хочу создать впечатление, что в моем коде только правило "единственного return-а" и ничего больше.

Это далеко не так.

И break-и использую, и несколько return-ов из функций. В том числе, бывает, сочетаю в теле цикла и break, и return.

Но стараюсь, чтобы таких вещей было по минимуму. Ибо недостаточно умен, чтобы писать и поддерживать сложный код. Так что тут, как говорится, не было счастья (т.е. ума), да несчастье (т.е. низкий IQ) помогло.

А вот continue в моем коде, действительно, найти тяжело. Практически не использую. Чего и вам желаю 😎

Комментариев нет: