Почему ранний возврат из функций так важен?

Привет, Хабр! Представляю вашему вниманию перевод статьи «Why should you return early?» автора Szymon Krajewski

image

В начале моего приключения в роли программиста мой код зачастую напоминал вермишель. В любых условных выражениях я только и делал, что сразу переходил к описанию верного исхода, оставляя на конец остальное. «Это работает, вот и все», — говорил я себе, а код продолжал расти, как на дрожжах. Тысячи написанных методов в итоге заставили меня задуматься, а не стоит ли поменять их внутреннюю логику, возвращая отрицательные результаты как можно раннее. Таким образом, я пришел к тому, что теперь называю правилом «неотложного провала».

Очевидно, что существует несколько подходов написания одной и той же функции. Например, как можно начать выполнение основной части сразу после положительного исхода условного оператора, так и можно сначала пробежаться по всем отрицательным исходам, возвращая ошибки из функции, а уже только потом перейти к основной логике. Иными словами, я открыл для себя разные стили написания условных конструкций.

Два подхода проверки требований


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

function sendEmail(string $email, string $message)
{
    if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
        if ($message !== '') {
            return Mailer::send($email, $message);
        } else {
            throw new InvalidArgumentException('Cannot send an empty message.');
        }
    } else {
        throw new InvalidArgumentException('Email is not valid.');
    }
}

Предыдущий отрывок кода является именно тем, что было описано раннее. Работает ли это? Абсолютно. Читаемо ли? Не совсем.

В чем проблема написания требований в самом коде?


Существует две стилистические проблемы в написанной функции sendMail():

  1. Присутствует более одного уровня отступов в теле функции;
  2. Невозможно сразу определить путь успешности функции без ее полного изучения;

Так как первый пункт касается понятия «чистого кода», позвольте мне сфокусироваться на втором. Наша функция имеет несколько ветвей, а оператор return недостаточно хорошо обозреваем. Конечно, приведенный пример очень прост и короток, но на практике в функции может наблюдаться огромное множество условий и возвратов значений. В этот момент и вступает в дело герой-спаситель «обратное условие».

function sendEmail(string $email, string $message)
{
    if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('Email is not valid.');
    }

    if ($message === '') {
        throw new InvalidArgumentException('Cannot send an empty message.');
    }

    return Mailer::send($email, $message);
}

Я понимаю, что избавился от одного else после второго условного оператора, но, субъективно говоря, новый образец кода выглядит заметно чище и эстетичней. Явно, что «обратное условие» заслужило печеньку за помощь (прим. переводчика, тоже печеньку ему).

Чего я достигну, используя «обратные условия»?


Во первых, максимальный уровень отступов в основном теле функции уменьшился до одного, а также мы избавились от вложенных условий. Код становится более читаемым и легким для понимания, а финальная инструкция в конце метода – доступней.

Наверное, сейчас вы недоумеваете, а что именно я назвал подходом «неотложного провала». К сожалению, Jim Shore и Martin Fowler дали определение этому термину еще задолго до моего “Hello world”. Конечно, хотя и цели обоих подходов одинаковые, я решил переименовать мое видение в «ранний возврат». Термин описывает сам себя, так что я закрепил это название «на вывеске».

Концепт «раннего возврата»


Для начала требуется формально определить данный термин.
«Ранний возврат» — это концепт написания функций таким образом, что ожидаемый положительный результат возвращается в конце, когда остальной код в случае расхождения с целью функции должен завершить ее выполнение настолько раньше, насколько возможно.
Вы не поверите, насколько долго я думал над данным определением, но в любом случае концепт является обобщенным. Что такое «ожидаемый положительный результат», «завершение выполнения» и «цель функции»? Постараюсь описать это в дальнейшем.

Следуйте «пути к счастью»


«Горшочек с золотом в конце радуги», помните ли вы эту историю? А может вы уже нашли такой горшок? Это именно то, чем можно описать подход «раннего возврата». Ожидаемый приз, успех нашей функции находится именно в конце пути. Глядя на новую версию sendMail, можно уверенно сказать, что целью функции была отправка сообщения на указанный почтовый адрес. Это и есть «ожидаемый положительный результат», иными словами «путь к счастью».
image
Основной путь в функции четко обозреваем

Расположив финальную операцию в самом конце, мы четко понимаем как выглядит путь к финальной цели. Один взгляд на код, и все становится на свои места.

Избавьтесь от отрицательных случаев как можно ранее


Не всегда исполнение функции приводит к ожидаемому результату. Порой пользователь может ввести неверный email адрес в нашем примере, а порой и пустую строку в качестве сообщения. В обоих случаях требуется завершить работу функции немедленно. Завершением функции может быть или возврат отрицательного значение, или возврат ошибки. Данный выбор индивидуален. Некоторых людей может смутить использование нескольких операторов возврата, но это вполне нормальная практика. К тому же данный подход зачастую делает функцию более понятной для человеческого взора.

«Шаблон вышибалы» — это раннее описанная методология, которая завершает выполнение функции в случаях ее неверного состояния. Взгляните на следующий пример:

function matrixAdd(array $mA, array $mB)
{
    if (! isMatrix($mA)) {
        throw new InvalidArgumentException("First argument is not a valid matrix.");
    }

    if (! isMatrix($mB)) {
        throw new InvalidArgumentException("Second argument is not a valid matrix.");
    }

    if (! hasSameSize($mA, $mB)) {
        throw new InvalidArgumentException("Arrays have not equal size.");
    }

    return array_map(function ($cA, $cB) {
        return array_map(function ($vA, $vB) {
            return $vA + $vB;
        }, $cA, $cB);
    }, $mA, $mB);
}

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

Возвращайте пораньше из действий контроллеров


Действия контроллеров – это идеальный кандидат для использования вышеописанного подхода. Действия зачастую включают в себя огромное количество проверок прежде, чем они вернут ожидаемый результат. Давайте рассмотрим пример с updatePostAction в контроллере PostController:

/* PostController.php */

public function updatePostAction(Request $request, $postId)
{
    $error = false;

    if ($this->isGranded('POST_EDIT')) {
        $post = $this->repository->get($postId);

        if ($post) {
            $form = $this->createPostUpdateForm();
            $form->handleRequest($post, $request);

            if ($form->isValid()) {
                $this->manager->persist($post);
                $this->manager->flush();

                $message = "Post has been updated.";
            } else {
                $message = "Post validation error.";
                $error = true;
            }
        } else {
            $message = "Post doesn't exist.";
            $error = true;
        }
    } else {
        $message = "Insufficient permissions.";
        $error = true;
    }

    $this->addFlash($message);

    if ($error) {
        $response = new Response("post.update", ['id' => $postId]);
    } else {
        $response = new RedirectResponse("post.index");
    }

    return $response;
}

Можно заметить, что кусок кода является достаточно объемным, включая множество вложенных условий. Этот же отрывок можно переписать, использую метод «раннего возврата».

/* PostController.php */

public function updatePostAction(Request $request, $postId)
{
    $failResponse = new Response("post.update", ['id' => $postId]);

    if (! $this->isGranded('POST_EDIT')) {
        $this->addFlash("Insufficient permissions.");
        return $failResponse;
    }

    $post = $this->repository->get($postId);

    if (! $post) {
        $this->addFlash("Post doesn't exist.");
        return $failResponse;
    }

    $form = $this->createPostUpdateForm();
    $form->handleRequest($post, $request);

    if (! $form->isValid()) {
        $this->addFlash("Post validation error.");
        return $failResponse;
    }

    $this->manager->persist($post);
    $this->manager->flush();

    return new RedirectResponse("post.index");
}

Теперь же любой программист может четко увидеть «путь к счастью». Все очень просто: ожидаете, что что-то может пойти не так, — проверьте это и верните отрицательный результат пораньше. Вышеуказанный код также имеет один уровень отступов и присущую большинству подобных функций читаемость. Опять-таки, больше не нужно использовать else условия.

Возвращайте пораньше из рекурсивных функций


Рекурсивные функции также должны прерываться как можно ранее.

function reverse($string, $acc = '')
{
    if (! $string) {
        return $acc;
    }

    return reverse(substr($string, 1), $string[0] . $acc);
}

Недостатки подхода «раннего возврата»


Возникает вопрос, а является ли описанный концепт панацеей? В чем же заключены недостатки?

Проблема 1. Стилистика кода – понятие субъективное


Мы, программисты, зачастую тратим больше времени на чтение кода, нежели его написание. Это довольно-таки известная правда. Таким образом, требуется написание как можно более простого и изящного кода, насколько возможно. Я настаиваю на следовании концепта «раннего возврата» именно потому, что основной механизм функции заключен именно в ее конце.

Вот только выбор стилистики кода подвержен субъективному мнению. Кому-то удобней единое использование оператора возврата, а кому-то нет. Выбор, в конечном итоге, остается за каждым программистом.

image
Если представить две абстрактные функции, какая из них будет казаться проще для чтения и понимания?

Проблема 2. Иногда «ранний возврат» — лишнее усложнение


Можно привести множество примеров, когда «ранний возврат» негативно влияет на конечный код. Типичным примером можно привести функции-сеттеры, в которых параметры зачастую отличаются от false:

public function setUrl($url)
{
    if (! $url) {
        return;
    }

    $this->url = $url;
}

Это не самое значительное улучшение читабельности кода. Ситуативность использования подхода остается заботой самого программиста, ведь, используя стандартный подход, можно добиться лучшего результата:

public function setUrl($url)
{
    if ($url) {
        $this->url = $url;
    }
}

Проблема 3. Все это выглядит как оператор break


Так как в функции может быть расположено большое количество операторов return, не всегда удобно понять, откуда был получен результат. Но до тех пор, пока вы используете данный подход для завершения некорректных состояний функции, все будет в порядке. Ах, да, пишете ли вы тесты?

Проблема 4. Иногда лучше использовать одну переменную вместо множества return операторов


Существуют структуры кода, в которых подход «раннего возврата» ничего не изменяет. Только взгляните на следующие примеры кода:

function nextState($currentState, $neighbours)
{
    $nextState = 0;

    if ($currentState === 0 && $neighbours === 3) {
        $nextState = 1;
    } elseif ($currentState === 1 && $neighbours >= 2 && $neighbours <= 3) {
        $nextState = 1;
    }

    return $nextState;
}

Эта же функция с вышеуказанным концептом выглядит так:

function nextState($currentState, $neighbours)
{
    if ($currentState === 0 && $neighbours === 3) {
        return 1;
    }

    if ($currentState === 1 && $neighbours >= 2 && $neighbours <= 3) {
        return 1;
    }

    return 0;
}

Очевидно, что больших преимуществ у второго примера над первым нет.

Заключение


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

Концепт «раннего возврата» вводит некоторые правила, необязательные для исполнения в 100% случаев, но которые зачастую помогают делать код чище и читабельней. Очень важно сохранять дисциплину при написании кода, ведь следование единой стилистике кода важней, чем ее выбор.

Лично я использую метод «раннего возврата» настолько часто, насколько возможно, особенно когда неотложное завершение функций с некорректными состояниями улучшает эстетику кода. Я привык, что успешное завершение всегда находится в конце функций, ведь недаром одна из мантр гласит:
Следуйте пути к успеху и реагируйте в случае ошибок.


Оригинал статьи
Заметка: переводчик оставляет за собой право стилистически менять фразы из источника.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 175

    0
    Хм, могу ошибаться но дядюшка Боб как раз из за проблем 1 и 3 рекомендует делать только один возврат в конце.
      0
      Найдете ссылку на рекомендацию?
        0
        Постараюсь вечером найти, если не забуду.
          0
          Timmmm Хм, не нашел, хотя казалось бы точно читал. При этом у Макконела, книга которого мне как то больше понравилась, есть раздел «множественные возвраты и функции» в котором ранний выход при проверке условий как раз поощряется.
            0
            Спасибо, что не забыл.
            «поощряется»? На мой взгляд это единственно верное выполнение функции. Все перечисленные проблемы, это же смех, притянуты за уши. Хочется добраться до реальных проблем, если таковые есть.
              +1
              Там всё хитро. Надеюсь, изложу близко к тому, что написано у Макконнелла, но, возможно, на моё изложение наложились и собственные деформации и опыт.

              Тезис: функция должна соблюдать инварианты.
              Сначала надо выполнить проверки входных данных и не приступать к работе, если они не соблюдены.
              Дальше выполняется код, который должен иметь одну точку выхода.
                0
                Да, я примерно так же этот раздел понял. Но все таки странно что у меня в памяти это в другом виде отложилось… Ведь и сам в основном писал сначала проверки с возвратом, далее основной код, и в конце еще один возврат.
                  0
                  А что делать, если проверка входных данных — дорогостоящая операция? Обычно такое бывает когда проверка входных данных делается где-то в вызывающем коде один раз, а не каждый раз при вызове «вложенной функции».
                  Пример:
                  double div(a,b)
                  {
                    if(b==0.0) {
                        if(a==0.0) return NaN;
                      return sign(a)*Inf;
                    }
                    return a/b;
                  }
                  int main()
                  {
                    const double denom=3.14;
                    for (double num: {3.14, 2.71, 0.0}) {
                      cout<<div(num,denom)<<"\n";
                    }
                  }
                  

                  Получается, что будет 2 лишних проверки, которые в реальном коде компилятор не сможет соптимизировать, если проверка не тривиальна.
                    0

                    Можно делать булевый параметр с значением по умолчанию — нужно ли делать дорогие проверки.
                    Если упороться в край, можно сделать его шаблонным. Тогда проверки будут вырезаны еще на этапе компиляции))

                      0
                      Это уже есть, называется assert, проблемы не решает.
                        0

                        А почему, кстати?

                          0
                          Потому, что всё равно бывает необходимо отлавливать ошибки в рантайме (в production) и отлавливать их на нижнем уровне вложенности (assert) — медленно, а на верхнем — ненадёжно (легко ошибиться, пропустив недопустимое значение параметра дальше по стеку вызова).
                          Assert — это только для дебага, когда скорость не важна.
                          Тут возникает идея Design by contract.
                      0
                      Делать зависимые типы и у функций, которые не делают проверки, полагаясь на вызывающего, описывать это в виде необходимости передать им значение-доказательство типа-проверки.
                        0
                        Вам не трудно будет привести пример?
                          +1
                          С радостью! Тут mydiv нельзя вызвать, не проверив предварительно, что b не ноль, а checkedDiv делает все нужные проверки.
                          mydiv : (a : Double) -> (b : Double) -> {auto prf : b /= 0 = True} -> Double
                          mydiv a b = a / b
                          
                          data Sign = Plus | Minus
                          
                          sgn : (a : Double) -> {auto prf : a /= 0 = True} -> Sign
                          sgn a = if a > 0 then Plus else Minus
                          
                          data DivResult = NaN
                                         | Infty Sign
                                         | Fin Double
                          
                          checkedDiv : (a : Double) -> (b : Double) -> DivResult
                          checkedDiv a b = case decEq (b /= 0) True of
                                                Yes prf => Fin $ a `mydiv` b
                                                No _ => case decEq (a /= 0) True of
                                                             Yes prf => Infty $ sgn a
                                                             No _ => NaN
                          


                          На самом деле можно ещё изящнее, закодировав информацию об a в конструкторы Sign, но это уже лень, да и исходную цель это несколько размывает.

                          В общем, попробуем просто поделить:
                          *Scratch> mydiv 2 1
                          2.0 : Double
                          *Scratch> mydiv 1 2
                          0.5 : Double
                          

                          Всё норм.

                          *Scratch> mydiv 2 0
                          (input):1:5:When checking argument prf to function Main.mydiv:
                                  Can't find a value of type
                                          False = True
                          

                          Тайпчекер бьёт по рукам. Умница!

                          Попробуем checkedDiv:
                          *Scratch> checkedDiv 2 1
                          Fin 2.0 : DivResult
                          *Scratch> checkedDiv 2 0
                          Infty Plus : DivResult
                          *Scratch> checkedDiv 0 0
                          NaN : DivResult
                          

                          как и ожидалось.

                          Попробуем сломать checkedDiv, забыв там все проверки и написав просто:
                          checkedDiv a b = Fin $ a `mydiv` b
                          

                          Что получится?
                          Scratch.idr:23:27-32:When checking right hand side of checkedDiv with expected type
                                  DivResult
                          
                          When checking argument prf to function Main.mydiv:
                                  Can't find a value of type 
                                          not (intToBool (prim__eqFloat b 0.0)) = True
                          

                          Снова тайпчекер бьёт по рукам. В компил-тайме. Умница!

                          Ну, как-то так.
                            0
                            Это уже design by contract. Не придумал, как такое сделать на C++.
                              0
                              Да, это контракты, явно выраженные в типах и проверяемые тайпчекером.

                              В плюсах никак, там нет завтипов.
                                0
                                Как-то так наверное
                                #include <iostream>
                                
                                
                                enum class Infinity
                                {
                                    Positive,
                                    Negative
                                };
                                
                                std::ostream& operator << (std::ostream& stream, Infinity value)
                                {
                                    switch (value)
                                    {
                                    case Infinity::Positive:
                                        stream << "+Inf";
                                        break;
                                    case Infinity::Negative:
                                        stream << "-Inf";
                                    }
                                    return stream;
                                }
                                
                                struct NaN {};
                                
                                std::ostream& operator<< (std::ostream& stream, const NaN&)
                                {
                                    return stream << "NaN";
                                }
                                
                                template<size_t A, size_t B>
                                struct Div
                                {
                                    static constexpr size_t Value = A / B;
                                };
                                
                                template<size_t X>
                                struct Div<X, 0>
                                {
                                    static constexpr Infinity Value = (X > 0) ? Infinity::Positive : Infinity::Negative;
                                };
                                
                                template<>
                                struct Div<0, 0>
                                {
                                    static constexpr NaN Value = NaN{};
                                };
                                
                                #define CHECK(A, B) << "Div<" << #A << "," << #B << "> : " << Div<A,B>::Value << std::endl
                                
                                int main()
                                {
                                
                                    std::cout
                                        CHECK(5, 2)
                                        CHECK(5, -2)
                                        CHECK(-5, 2)
                                        CHECK(-5, -2)
                                        CHECK(5, 0)
                                        CHECK(-5, 0)
                                        CHECK(0, 1)
                                        CHECK(0, -1)
                                        CHECK(0, 0)
                                        ;
                                
                                    return 0;
                                }
                                


                                Правда компилятор позволяет в шаблонах использовать только исчислимые типы.
                                  0
                                  не сработает с рантайм переменными.
                                  int main()
                                  { 
                                    auto denom=rand()+3; // всегда >0
                                    for (int i=0; i<10; i++) {
                                      cout<<Div<rand(),denom><<" ";
                                    }
                                    
                                  }
                                  

                                  Хотелось бы что-то такое, что позволяет с одной стороны указать диапазон допустимых значений входящих параметров, а с другой — выкидывать эти проверки когда они и так выполнены уровнем выше по стеку вызова.
                                    0
                                    Значит я не понял идеи тремя комментариями выше.
                                    Если хочется рантайм с исключениями, можно использовать boost::variant / std::variant.
                                      0
                                      Что бы выкинуть исключение, нужно проверить условие, нет никакого выигрыша. Фактически я говорю о необходимости design by contract вместо обработки ошибок через ветвления или assert. Assert работает только в дебаге, а ветвления — всегда, даже когда не нужно. Design by contact как бы совмещает лучшее из обоих. Даже ещё лучше чем assert, который становится static_assert где можно и runtime ветвлением где нельзя.
                                        0
                                        Давайте сначала договоримся о терминах: какую задачу мы хотим решить и какие ограничения соблюсти?

                                        (несмотря на то, что это обсуждение уже далеко за пределами тематики исходной статьи)
                                          0
                                          С моей точки зрения общая задача стоит так: обеспечить надёжную и предсказуему работу программ. Для этого полезно автоматизировать контроль за соблюдением диапазонов входящих значений параметров функций. На сегодняшний день такой контроль реализуется чаще всего двумя способами:
                                          if (isInvalid(param)) return Failed;
                                          и
                                          assert(isValid(param));
                                          Первый способ связан с потерей производительности, которую вполне можно было бы избежать. Во втором случае проверки просто нет в Release, а если есть, то неудобно контролировать поток выполнения — это не return. Хотелось бы что-то вроде варианта с `if`, но так, что бы компилятор выкидывал проверку, когда это возможно. Также хотелось бы, что бы компилятор автоматически переносил эту проверку максимально высоко по стеку вызова. Лучшим решение этой проблемы, из тех что я знаю, является Design by Contract, которого в мейнстримных языках вроде нет, даже в Rust, если я правильно понимаю.
                                            0

                                            В Eiffel, единственном языке, который, как я знаю, был специально спроектирован под Design by Contract, проверки в runtime рекомендуется исключать в продакшен окружении. По сути, те же assert, только сбоку.

                                              0
                                              В Eiffel, единственном языке, который, как я знаю, был специально спроектирован под Design by Contract

                                              Смотря как вы определяете design by contract. Если это упомянутые рядом зависимые типы, которые вполне позволяют выражать контракты, то это далеко не единственный язык, и в runtime никакие проверки толком не попадают.
                                        0
                                        Сценарий 1. Данные известны в момент компиляции. Передаём в некоторое выражение, получаем результат. Валидация может быть проведена во время компиляции, во время выполнения проверка не требуется и ничего не сто'ит.
                                        Сценарий 2. Данные получены из внешнего источника во время выполнения, их надо передать дальше. Проверить можно только во время выполнения. Выполнять следует на том слое, который уверенно может обработать данные на своём уровне.

                                        Теперь контрольные вопросы:
                                        0. Когда известны входные данные?
                                        1. Применим ли наиболее быстрый вариант в вашем конкретном случае?
                                        Если да, то выбор принят.
                                        2. Если ответ на вопрос отрицательный, то можем ли мы локализовать место, где входные данные могут быть провалидированы?
                                        Если да, то выбор принят. На остальных уровнях делаем код прозрачным к для исключений и сохраняющим инварианты.
                                        3. Если ответ отрицательный, то можем ли мы спроектировать систему так, чтобы ответ на вопрос 2 стал утвердительным?
                                        Если да, то переходим к пункту 2 и повторяем.
                                        Если нет, то у вас проблемы с пониманием задачи.
                                          +1
                                          Тремя комментариями выше можно и рантайм, но проверка наличия проверки происходит во время компиляции.
                                            0
                                            То есть всего лишь делаем обёртку над типом только для того, чтобы гарантировать, что в эту обёртку программист должен влезть?
                                              0
                                              Не обёртку, отдельный тип, который, вообще говоря, может быть достаточно выразительным.

                                              Можно, наверное, и в плюсах передавать некоторый dummy-объект, в конструкторе проверяющий равенство нулю. Некоторую малую долю юзкейсов оно покроет.

                                              Как вы на уровне системы типов будете гарантировать, что такой dummy-объект действительно делает то, что он должен делать?
                                                0
                                                Если некоторая функция предназначена для математических расчётов (работа в некоторой предметной области), то возвращаемый тип хотелось бы иметь из той же предметной области, а не сколь угодно выразительные типы, с которыми надо много воевать, чтобы наконец выковырять то, что нужно.
                                                  0
                                                  А там и не надо воевать и выковыривать.
                                                    0
                                                    Может тогда стоит написать, что там с этим делать, например, если надо посчитать a/b + c/d, к примеру?
                                                      0
                                                      Единожды проверить b и d.

                                                      Или единожды проверить, что матрица невырожденная. Или что у двух матриц совместимые размеры, если вы их складывать-умножать собрались.

                                                      Не во всех задачах это нужно, конечно.
                                                        0
                                                        Хорошо. Проверять где надо? В этой функции или где-то выше по стеку вызовов?
                                                        Если откуда-то сверху этот аргумент приходит в виде, исключающем необходимость проверки, например, когда аргумент — промежуточный результат, полученный как 1+x^2?
                                                        А может быть есть другие проверки, которые гарантированно не дадут ноль в знаменателе, например, модуль аргумента строго больше единицы? Поймёт ли ваш чекер, что его проверка лишняя?

                                                        Далее, ну, допустим, проверили мы, можем сделать корректную обработку этой ситуации или для этого нужен контекст более высокий по стеку вызовов?

                                                        Могу ли я сделать вывод, что приведённый выше фрагмент не претендует на универсальность, а полезен лишь как пример, что так в некоторых ситуациях можно сделать, и закончим нашу дискуссию?
                                                          0
                                                          В этой функции или где-то выше по стеку вызовов?

                                                          Выше, чем в mydiv — в checkedDiv, например.

                                                          Если откуда-то сверху этот аргумент приходит в виде, исключающем необходимость проверки, например, когда аргумент — промежуточный результат, полученный как 1+x^2?

                                                          Тут кусок вопроса пропал, кажется. Подозреваю, вы имели в виду, нужен ли в таком случае этот тип. Да, нужен, соответствующее доказательство строится легко, впрочем.

                                                          А может быть есть другие проверки, которые гарантированно не дадут ноль в знаменателе, например, модуль аргумента строго больше единицы? Поймёт ли ваш чекер, что его проверка лишняя?

                                                          Проверка чекера не лишняя. Тут чекер-то только типов и только во время компиляции. Если вы во время компиляции ему предоставите пруф доказательство, что аргумент всегда не ноль, то он будет счастлив, и сотрёт этот терм при компиляции совсем (как, впрочем, и в случае динамической проверки).

                                                          Далее, ну, допустим, проверили мы, можем сделать корректную обработку этой ситуации или для этого нужен контекст более высокий по стеку вызовов?

                                                          А это решать не mydiv. Она получает доказательство проверки, а откуда оно взялось — дело вызывающего стека. Может, кто-то в самом начале программы это проверил и теперь это доказательство передаёт.

                                                          Могу ли я сделать вывод, что приведённый выше фрагмент не претендует на универсальность, а полезен лишь как пример, что так в некоторых ситуациях можно сделать, и закончим нашу дискуссию?

                                                          Ну, я бы не стал делать такой вывод. Это весьма универсальный механизм и вообще один из методов написания формально верифицированных программ и доказательства теорем.
                              0
                              Можно ввести новый тип, «уже проверенный». Делать проверки при работе с непроверенным типом и при конвертации, а при работе с проверенным типом проверки будут не нужны. Примером подобного типа будет gsl::not_null.
                  –6
                  Код ниже
                  if Ошибка1 {
                  // Обработка ошибки 1
                  result = 1;
                  } elsif Ошибка2 {
                  // Обработка ошибки 2
                  result = 2;
                  } elsif Ошибка3 {
                  // Обработка ошибки 3
                  result = 3;
                  } else {
                  // Все ок, работаем дальше.
                  result = 4;
                  }
                  return result;

                  лишен недостатков 1, 3 и соответствует идее, переданной в статье.
                  Личное мнение по недостатку 4 — ВСЕГДА лучше использовать промежуточную переменную вместо множества RETURN операторов.
                  В некоторых ЯП для этих целей даже неявно переменная объявляется.
                    0
                    Ага, проблема только в том, что проверочные условия могут выходить за рамки одной иерархии if-else. А также в том случае, когда возвращаемый результат не единообразен, как в случае с контроллером из статьи.
                      0
                      Если выходят за рамки одного уровня иерархии возможно стоит рассмотреть возможность вынести в отдельные функции?
                        +2
                        Вы проверку каждого входного параметра будете в отдельную функцию выносить или что?
                          0
                          Если у вас вложенные иерархии if-else — то это уже явно не проверка одного параметра, а проверка каких то комбинаций условий. Которые лучше в отдельные функции вынести для снижения цикломатической сложности конкретной функции, имхо.
                            –1
                            А я где-то говорил про вложенные иерархии if-else?
                              +1
                              проблема только в том, что проверочные условия могут выходить за рамки одной иерархии if-else
                              За рамками одной иерархии собственно как раз вложенные иерархии. Или я что то не так понял?
                        0
                        как в случае с контроллером из статьи.

                        Мне кажется пример с контроллером вообще не очень удачен в современном мире. Все, что в нем происходит можно "разбить" на более мелкие части и вынести в дргуие метса. Мне, например, очень нравиться как это реализовано в Laravel — валидация происходит вне контроллера, равно как и обработка ее ошибок и ACL.

                        +11
                        И чем же это хорошо? Оператор return сразу однозначно дает понять, что обработка этой ветви завершена. Присваивание result оставляет туман неизвестности — а что если дальше с этим результатом будет что-то происходить? Получается что-то типа switch с неполным набором брейков — иногда это может быть полезно, но в общем случае практика плохая.
                          –1
                          Return может затеряться (визуально) в куче кода и человек, который сопровождает код будет искренне удивлен, почему выполнение не доходит до конца метода (функции).
                          Чтобы все-таки понять почему — ему придется заходить в отладчике во все if'ы и проверять «а не в этом ли условии программа выходит на уровень вверх?».
                          В случае с if-elsif-elsif-else достаточно поставить точку останова на 1м if-e и увидеть куда зайдет программа в процессе выполнения.
                            0
                            Честно говоря, не очень понял аргумент с точкой останова. Ясно же, что и в случае с return-ами вариант «встать на первое условие внутри функции и проследить выполнение до момента выхода» тоже сработает. А если функция такая громоздкая, что проходить её пошагово сначала — нерациональная трата времени, так и в случае с цепочкой elseif-ов результат будет ровно такой же (не говоря уж о вопросе, рационально ли её делать в этом случае одной функцией).
                              0
                              Ответ комментом ниже.
                            –2
                            Т.е. if-elsif-elsif-else сразу дает человеку понять, что есть ситуации, когда не весь код выполнится.
                            А глядя на конструкции
                            if Условие1 {
                            return;
                            }

                            if Условие2 {
                            return;
                            }

                            if Условие3 {
                            return;
                            }

                            // все ок, работаем.


                            создается ложное впечатление, что строка «все ок, работаем» выполняется всегда.
                              0
                              … которое, опять же, разбивается за один проход отладчика. Который нужен, по Вашим же собственным словам, и в первом случае, если по каким-то причинам оказалось неясно, какая именно из веток в данных условиях отрабатывает. Я не спорю с тезисом (что код с if-elseif выглядит читабельнее), я пытаюсь понять обоснованность аргумента.
                                0
                                В данном случае, может быть, выглядит не убедительно т.к. внутри каждого условия просто стоит return.
                                Выше писали, что внутри этого условия могут быть еще условия.
                                Во вложенных условиях еще условия.
                                Тогда чтобы понять тут ли выходит программа мне нужно по всей этой лесенке пройтись. Потом по следующей лесенке. Потом по третьей по счету.

                                Да, можно вынести лесенку в отдельный метод.
                                И вариант оформления if-elseif с единственным return в конце принуждает это делать.
                                Иначе не написать так, чтобы вложенных условий не было.
                                  0
                                  Делайте не return, a throw.
                                0
                                Добавь в начале
                                bool ok = true;
                                а после всех проверок
                                if (ok)
                                {
                                }

                                Иногда так даже удобней, но обычно можно и проще
                                // If vse Ok
                                {
                                }
                                  0
                                  Этот вариант ни чем не лучше if-elsif-else.
                                  Тем более обязывает нас заводить очередную дополнительную переменную.
                                    0
                                    Я бы сказал, что хуже. Т.к. менее устойчив к рефакторингу и unit тестированию.
                            0
                            Даже с таким подходом корректная логика и некорректная начинают смешиваться. По моему, по возможности, проще валидации выделять в отдельные функции, а после всех проверок исполнять уже непосредственно основную логику.
                              –1
                              Выделять в отдельные функции стоит если эти проверки делаются более одного раза.
                              +1
                              4 пример
                              Очевидное преимущество второго способа — не надо поднимать глаза и искать что за там $nextState объявлен, код четко разбит на 3 случая, читать проще будет.
                                +13
                                Не знал, что Капитана Очевидность зовут Szymon Krajewski.

                                Неужели для кого-то это что-то новое? Удивляет, что такую мелочь можно раздуть до размеров статьи, которую начинают переводить и репостить.

                                Кстати, ни слова про декларативный, императивный, контрактный и т.д. подходы, когда часть таких проверок вообще выносится из тела метода.
                                  0

                                  Хотя эти вещи кажутся очевидными, в реальном мире, я не раз сталкивался с таким вот if/else hellом. А иногда и вообще без какой либо валидации.

                                  –4

                                  если честно подобный подход считаю очевидным, пришел к следующему правилам для себя:


                                  1. Только один return (не считая исключений), можно использовать переменную.
                                  2. return это последняя строка
                                  3. Исключения только для исключительных ситуаций.
                                  4. Не более одного уровня отступов, не считая отступов функции => рефакторить в приватный метод.

                                  все остальное это следствие из этих правил.

                                    +1
                                    Не более одного уровня отступов, не считая отступов функции => рефакторить в приватный метод.

                                    И как алгоритм Флойда должен выглядеть?))

                                      –2

                                      так как php используется в вебе то такое решение вполне сойдет


                                      <?php
                                      
                                      class Floid
                                      {
                                          private $w;
                                      
                                          public function __construct(array $w, int $n)
                                          {
                                              // если нужно проверяем данные
                                              $this->w = $w;
                                              $this->n = $n;
                                          }
                                      
                                          public function getResult()
                                          {
                                              for ($k = 1; $k <= $this->n; $k++) {
                                                  $this->forByI($k);
                                              }
                                              return $this->w;
                                          }
                                      
                                          private function forByI($k)
                                          {
                                              for ($i = 1; $i <= $this->n; $i++) {
                                                  $this->calc($k, $i);
                                              }
                                          }
                                      
                                          private function calc($k, $i)
                                          {
                                              for ($j = 1; $j <= $this->n; $j++) {
                                                  $this->w[$i][$j] = min(
                                                      $this->w[$i][$j],
                                                      $this->w[$i][$k] + $this->w[$k][$j]
                                                  );
                                              }
                                          }
                                      }
                                      

                                      если мы будем говорить об оптимизации производительности, потребление памяти, скорости и тд первое что стоит начать делать это взять компилируемый язык чтобы не терять время интерпретации, причем я ничего против пхп не имею в свое нише он дает достаточную производительность.


                                      ах да по поводу отсутуов внутри min забыл упомянуть что такое в функциях можно делать но в меру, могу убрать но не вижу смысла зачем от этого избавляться

                                        +2

                                        Вам не кажется, что исходный вариант (4 строки) выглядит проще?
                                        Ну т.е. понятно, что можно разбивать код до уровня "любая инструкция в отдельной ф-ции", а в С++ это еще и работать быстро будет. Но по-моему не имеет смысл резать на отдельные функции что-то цельное, например алгоритмы.

                                          0

                                          исходный вариант 4 строки которые в примере это на самом деле, + 1 строка на функцию + 2 строки на скобки этой функции(в psr открывающая с новой строки) + 1 строка закрывающая скобка для каждого цикла (итого 3) + 1 строка на return
                                          итого получаем 4 полезных строки размазанные по еще 7 строкам получаем 11 входных строк.


                                          но даже учитывая 11 строк можно считать что он выглядит не сложно, но это не значит что так как я написал делать нельзя.


                                          Но работа программиста это не писать максимально короткие программы, а писать софт который потом можно поддерживать потому что имея 11 строк я могу довольно много чего придумать так чтобы потом это было читать и понимать тяжело, поэтому мы не строками меримся, а пишем софт который потом поддерживать и большая часть времени читать.


                                          Для примера цитата про "алгоритм установки gentoo" http://bash.im/quote/394695 если вам нравиться подобное в коде, то у всех свои вкусы и каждый вправе иметь свои взгляды.


                                          по поводу разбивать на части то что цельное, вы когда запрос к бд посылаете вы ручками создаете сокет, подключаетесь к бд и посылаете запрос или все таки разделяете на уровень по работе с бд и потому что генерирует запросы?
                                          ото алгоритм работы с бд можно назвать и это:


                                          1. Создать подключение
                                          2. Аутедентификация + авторизация
                                          3. выбрать бд
                                          4. цикл по запросам для бд

                                          как бы тоже 4 строки, не надо все под одну гребенку нести.


                                          а то что я разбил исходный текст на 3 метода это потому что у алгоритма 3 уровня отступов

                                            +1

                                            При использовании готовых алгоритмов при таком подходе есть шанс усложнить поддержку. В одной функции алгоритм может быть узнаваем, а при разбитии на части — нет.

                                              0

                                              давайте я возьму очень популярный алгоритм которым большинство присутствующих пользуется и не задумывается (реализацию специально на пыхе найду) https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Crypt/RSA.php


                                              вот сразу как открываешь подобный файл и видишь родные $i, $j, $n прям сразу ясно становиться что за алгоритм


                                              прям каждый день сниться когда все это в одной функции будет не разделено на вспомогательные вещи, прям сразу ясно что это RSA, а не DSA


                                              теперь запишите его как вы говорили все в одну функцию, да да да decrypt, encrypt, sign, verify, createkey пусть там на входе массив будет и сказан тип операции что надо сделать и разные ключи с параметрами


                                              я вам даже начну


                                              <?php function rsa(array $params) { return ;/* тут надо его просто расчитать в зависимости от параметров и да я специально все в одну строку пишу, чтобы строки сэкономить это же так важно */}

                                              и теперь ответьте честно будет выглядеть проще?


                                              есть GodObject, но похоже можно еще создавать GodFunction или GodMethod, не знал о последних двух, спасибо что помогли их понять еще раз.

                                                0

                                                Ну, если я буду писать алгоритм RSA по описанию, то, скорее всего, выделю так как выделено в оригинальном описании, чтобы тот, кто знает это описание сразу его определил.


                                                P.S. А экономия строк тут непричём, дело в разрыве знакомого многим процесса на выбранные из моей головы куски с придуманными мною названиями.

                                                  –1

                                                  только что вот считать оригинальным описанием?
                                                  то что написано в rfc? RFC 2313, RFC 2437, RFC 3447 в каком именно?
                                                  или первая его оригинальная публикация в научных журналах ?


                                                  только вот в этих документах нет ничего конкретного по реализации (кроме математических формул) потому что парадигм программирования очень много:


                                                  1. Императивное программирование
                                                    1.1. ООП
                                                    1.2. Процедурное и тд
                                                  2. Декларативное программироваине
                                                    2.1. Функциональное и тд

                                                  Вы будете искать rfc описывающее rsa в рамках ООП или ФП? таких нет, потому что rfc это описание и оно не привязано к реализации (парадигме или языку программирования), а как вы будете делать реализацию и в рамках какой парадигмы, зависит от вас и возможно от языка программирования что будете использовать (глупо например используя java писать в фп парадигме или использовать haskell и писать в ооп)


                                                  Даже если вы выберете ООП то тут есть еще куча вопрос:


                                                  1. mutable или immutable ?
                                                  2. stateless или statefull ?

                                                  То о чем вы говорите (о разрыве знакомого многим и лишними абстракциями) называется "связанность", что код должен отражать предметную область с которой он работает.


                                                  Только вот проблема в том что на вики выложен пример реализации в виде псевдо кода или какой нибудь наиболее наглядный пример на популярном языке, например в ФП не принято использовать циклы и вместо циклов бы тут использовали array map и реализация была бы совсем другой (в вики нет примера алгоритма флойда в рамках ФП).


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

                                                    0

                                                    Я не силён в данной предметной области, чтобы аргументированно ответить на большинство ваших вопросов. Но в целом и не утверждаю, что есть одна и только одна каноническая реализация алгоритма. С другой стороны, чем меньше разбираешься в предметной области, тем меньше надо вносить отсебятины, особенно в такой области как криптография, где даже лишние вызовы функций могут быть использованы в качестве информации для взлома.

                                                0

                                                простите не вам про одну строку.

                                              0

                                              а как по вашему должен выглядеть алгоритм rsa со всеми основными вещами (decrypt, encrypt, sign, verify, createkey причем не разбивая на отдельные функции)
                                              а также работу с BigInt тоже не выносить отдельно это же цельное, там ниже задал вопрос, но не вам к сожалению, поэтому и тут продублировал, а и еще постарайтесь сделать поменьше строк, "это очень важно".

                                                0
                                                Вы путаете выделение отдельных действий и разбивание одного цельного действия на части. Изображение вам наверно проще целиком смотреть, чем по отдельному пикселю прокручивать.
                                                  0

                                                  так вопрос разделения действий очень относительный.


                                                  вот например взять тот же алгоритм флойда и пхп, там есть операция +, если я выйду за пределы int во время этой операции?


                                                  Для работы с большими числами есть bc math или gmp, но это же алгоритм флойда надо ли их использовать, мне кажется да (если можно за пределы int уйти) но вы говорите что алгоритм флойда это то неделимое и надо смотреть на всю картину в целом, давайте тогда и работу с большими числами реализуем внутри циклов, как тогда будет выглядеть алгоритм без вспомогательных функций (методов) для работы с большими числами ?


                                                  по поводу изображений пример не корректен, если было так как вы говорите то в редакторах не было бы zoom (ведь удобней смотреть на все изображение без приближений вы так написали), поэтому пример с изображениями как раз и говорит что иногда один элемент (изображение) состоит из множества разных элементов (пикселей и векторов, ведь внезапно бывает еще и векторная графика), но мы не задумываемся о этих деталях когда смотрим на изображение пока нет в этом необходимости.


                                                  к сожалению я так и не вижу реализацию rsa от вас

                                                    0

                                                    Сложение двух больших чисел — это одно отдельное действие. Если два вложенных цикла по одному массиву нужны для получения результата и без любого из них промежуточный результат не существует или не имеет смысла — это тоже одно отдельное действие.
                                                    Отдельные действия можно вынести в функции и использовать во многих других действиях, причем основываясь на интерфейсе и описании смысла результата. Части действий, вынесенные в функции, без знакомства с реализацией использовать нельзя.


                                                    не было бы zoom (ведь удобней смотреть на все изображение без приближений вы так написали)

                                                    Нет, я не писал, что приближения не нужны. Я писал, что в режиме большого приближения изображение целиком сложно воспринять. Так же как и воспринять реализацию алгоритма, наблюдая зависящие друг от друга куски реализации по отдельности на одном уровне.


                                                    к сожалению я так и не вижу реализацию rsa от вас

                                                    Реализация, разбитая на независимые отдельные действия меня устраивает. И писали про нее вы не мне.
                                                    И да, не вижу ни одной причины из обсуждения в этой ветке, почему кто-то должен всё объединять в одну функцию, так же как и почему кто-то должен разбивать логику на миллион функций, причем руководствуясь числом отступов, а не результатом анализа этой логики.

                                                      0
                                                      Я писал, что в режиме большого приближения изображение целиком сложно воспринять.

                                                      Понятие приближения зависит от изображения и от того что вы хотите увидеть, но обратите внимание вы сами начали говорить о


                                                      не имеет смысл резать на отдельные функции что-то цельное, например алгоритмы

                                                      Вы сами начали говорить о приватных методах, вместо того чтобы смотреть на картину, вы начали упоминать о пикселях из которых она состоит (приватных методов).


                                                      И я вам показал пример алгоритма где имеет смысл делить на отдельные методы, это rsa.


                                                      И да, не вижу ни одной причины из обсуждения в этой ветке, почему кто-то должен всё объединять в одну функцию

                                                      Вот я это уже цитировал выше повторю "не имеет смысл резать на отдельные функции что-то цельное, например алгоритмы"


                                                      теперь по поводу работы с большими числами это тоже алгоритмы, а значит исходя из ваших же слов не имеет смысла резать на отдельные функции, да алгоритм сложения больших чисел один, но есть куча алгоритмов умножения например.

                                                        0
                                                        и от того что вы хотите увидеть

                                                        Правильно. В данном случае речь о том, чтобы увидеть алгоритм целиком.


                                                        Вы сами начали говорить о приватных методах, вместо того чтобы смотреть на картину, вы начали упоминать о пикселях из которых она состоит (приватных методов).

                                                        Во-первых, это не я говорил.
                                                        Во-вторых, о том и речь, что нет места, где есть картина, есть только куча отдельных пикселей. В вашем примере нет места с двумя вложенными циклами, зато есть 3 места с одним циклом. Причем у одного из них название завязано на реализацию. А если мы for на foreach поменяем, или на map?


                                                        И я вам показал пример алгоритма где имеет смысл делить на отдельные методы, это rsa.

                                                        В этой ветке никто не говорил что делить на методы вообще не надо.


                                                        Вот я это уже цитировал выше повторю

                                                        В таком случае и я повторю: вы общались с другим человеком, обратите внимание на ники.


                                                        а значит исходя из ваших же слов не имеет смысла резать на отдельные функции

                                                        Исходя из моих слов (а именно "Отдельные действия можно вынести в функции и использовать во многих других действиях") это никак не следует. Потому что написано прямо противоположное. Сложение можно вынести в функцию и использовать в умножении. Отдельные независимые части, из которых состоит сложение тоже можно вынести в функции и использовать в сложении.


                                                        Вы подменяете понятия. Речь идет не о том, разбивать на функции или нет. Разбивать надо, это и так все знают. Речь о том, насколько подробно разбивать, и по каким границам. Если кто-то говорит, что не надо разбивать по отступам, это никак не означает, что разбивать не надо вообще. Потому что есть больше двух возможных вариантов.

                                                  0

                                                  Мой тезис — сложность восприятия кода нелинейно зависит от кол-ва функций. Например, в случае с алгоритмом Флойда, знающему его человеку ориентироваться будет проще, чем в длинном. Естественно это не значит, что все алгоритмы должны быть реализованы в одной функции.

                                                    0

                                                    Сложность зависит только от кол-ва функций? complexity(functionCount) или есть еще другие параметры ?


                                                    Например, в случае с алгоритмом Флойда, знающему его человеку ориентироваться будет проще, чем в длинном.

                                                    каким образом знание или незнание алгоритма зависит от его длинны? если я перепишу алгоритм флойда в одну строку вам будет проще в нем ориентироваться и он станет вам более знакомым?


                                                    кто вам сказал что псевдо код выложенный на сайте вики это единственная реализация?


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


                                                    попробуйте реализовать алгоритм флойда в рамках фп и посмотрите понадобится ли вам вспомогательные функции (в том числе и анонимные).

                                            –3
                                            Не знаю, за что минусят — вполне адекватный подход
                                              –3

                                              "умом Россию не понять ..."

                                            +2
                                            Рад, что появляются такие статьи. Часто чиню чужие вермишели с помощью переписывания на ранний возврат.
                                            Для меня главное, что значительно повышается скорость сопровождения кода, и сразу видны проблемы в чужой логике, которые люди не учли, когда вкладывали ифы.

                                            При правильной органиизации кода return не теряется, наоборот, обработчики ошибок сразу видны в том месте где она происходит, а не после основного рабочего кода.
                                              0
                                              Resharper / IntelliJ Idea имеют автоматический рефакторинг для этого.
                                              +1
                                              Хм. Всегда так писал. Всегда это казалось очевидным. Сначала проверить входные данные, а только потом их обрабатывать.
                                              Не верить же на слово тому, кто вызывает функцию.
                                                0
                                                Тут вопрос не «верить/не верить», а как проверять, если не верить.
                                                  0
                                                  проверки стоят тактов процессора/памяти.
                                                    0
                                                    Некорректные данные на входе функции стоят часов отладки.
                                                      –1
                                                      assert()
                                                        0
                                                        Ассерт на тестах помогает. А в продакшене нет. И почему-то именно в продакшене не корректные данные и приплывают в самых неожиданных местах.
                                                          0
                                                          Речь шла об отладке, в предположении что отлаживается Debug версия.
                                                  0
                                                  Во втором варианте контроллера уже намечается ситуация, из-за которой я иногда избегаю ранних возвратов — сложная логика в той же ветви, что и возврат. Тут ещё относительно легко сделать что-то вроде this->createWrongResponseWithFlash($mesaage) один раз и юзать его при фейлах. Если же логика разная, то часто не хватает условного return, типа return? value; которая возвращает если value приводится к fa
                                                    0
                                                    возвращает value, если приводится к fslse и игнорится, если к true. На C можно макрос написать, ав других есть языках подобное или я хочу чего-то странного?
                                                    –1
                                                    Рекомендую прочитать книгу «Elegant Objects» Егора Бугаенко — там приведен прекрасный пример отделения валидации от бизнес-логики. Если вкратце, то вот как это выглядит.
                                                    Необходимо выделить интерфейс из вашего класса (который в текущей версии содержит и валидацию, и бизнес-логику).
                                                    У интерфейса следует сделать две реализации: первая реализует бизнес-логику без какой-либо валидации входных параметров, вторая — содержит всю необходимую валидацию входных параметров. При этом вторая реализация инкапсулирует экземпляр исходного интерфейса.
                                                    После всех манипуляций вы можете использовать либо экземпляр первой реализации — при этом у вас не будет валидации (иногда бывает необходимо для тестов) — или же можете завернуть этот экземпляр в инстанс второй реализации — в этом случае добавляется валидация.
                                                    В общем, используйте декораторы :)
                                                      0
                                                      Звучит, конечно, круто. К сожалению, не всегда удобно. В том же Perl сей подход вызовет больше сложностей в связи с корявостью ООП
                                                        0

                                                        Вот именно в Perl такой подход проблем не вызовет из-за нормальной реализации ООП. Другой вопрос, что на валидации входных параметров свет клином не сошёлся.

                                                        0
                                                        Видел такое доведенное до идиотизма. Все из себя представляет комманды, вызывающие другие комманды (Command Pattern) и так 5 уровней вложенности для того, чтобы сделать выборку по ID из базы данных.
                                                        0
                                                        Используем одну переменную и один return. И потом надеемся, что компилятор скомпилирует так, что не заставит программу пробегать по всей цепочке условий, а выполнит безусловный переход на return… иначе пользователь бежит и покупает новый процессор т.к. этот тормозить чего-то начал :)
                                                          +1
                                                          А есть сведения о компиляторах, которые заставят проверять все ветки условия if-elsif-elsif-elsif-else если первое условие истинно?
                                                            0
                                                            В Паскале это была неявная переменная Result, а вместо возврата — псевдофункция Exit.
                                                            Не сказал бы, что писать в таком стиле доставляло удовольствие.
                                                            0
                                                            Если на вход функции данные поступают чаще правильные, чем неправильные, то в инструкциях для процессора эта концепция выглядит так.
                                                            Проверить условие → прыгнуть дальше → проверить условие → прыгнуть дальше → проверить условие → прыгнуть на выполнение полезного действия.

                                                            Вложенные условия избавляют от прыжков, теперь цепочка инструкций будет: проверить условие → проверить условие → проверить условие → выполнить полезное действие.

                                                            Что лучше: одноуровневые условия и лишние прыжки или вложенные условия и отсутствие прыжков? Умеет ли компилятор оптимизировать лишние прыжки?
                                                              0

                                                              Даже если не умеет (особенно если речь об интерпретаторе, а не компиляторе), то по умолчанию приоритет у читаемости кода, а не у скорости его работы или потребляемой памяти.

                                                                0

                                                                С интерпретатором все понятно, но вот в компилируемом языке скорость иногда важнее читаемости.

                                                                  0

                                                                  Оптимизации очень часто уменьшают читаемость. Читаемость и скорость редко ортогональные друг другу измерения, обычно сильная отрицательная корреляция.

                                                                0

                                                                Сейчас не найду точных цифр, но, помнится, что ошибочная ветка в if вместо else добавляет пару десятков тактов из-за предсказания ветвлений. Процессор идет по ветке обработки ошибки и потом, когда оказывается, что данные верны, вынужден откатиться назад. Хотя, когда все поставят патчи для Meltdown, эта проблема перестанет быть актуальной :)

                                                                  0
                                                                  Если ошибочные данные приходят редко (и проверочные ветки редко срабатывают), и кусок кода горячий, то бранч предиктор очень быстро научится всё делать как надо.
                                                                0
                                                                Низкая вложенность — это очень удобно. Только желательно логировать выходы из функций, в таком случае будет понятно где обрубается, по типу такого:
                                                                  +1
                                                                  Давно пишу в таком стиле. Как-то сам пришел к нему.
                                                                    0
                                                                    Вот в первые полгода работы, пока набирался опыта, писал в духе if else, потом это перестало нравиться, стал делать ранние выходы, аргументируя себе это тем, что функция раньше закончится, а, следовательно, выполнение программы быстрее выйдет.
                                                                    А сейчас больше претензий к читаемости, конечно, для ранних выходов чётко видно, когда и что произойдёт этапами. Хотя где-то встречалось мнение, что для некоторых программ или приложений должен быть строго 1 оператор return, поэтому сомнения иногда закрадываются.
                                                                      0
                                                                      Вот мнение это встречалось, но непонятно на чем основано. Может быть какая-то оптимизация для компиляторов времен DOS. Или что-нибудь связанное с функциональщиной и доказуемостью алгоритма. Помню только, что что-то древнее.
                                                                        +1
                                                                        Мотивация, с которой я знаком, идёт из языков без детерминированных деструкторов, сборки мусора, финализаторов и вот этого вот всего, соответственно в таких языках при наличии нескольких точек выхода необходимо дублировать работу по освобождению ресурсов в каждой точке.
                                                                          0
                                                                          Для ANSI C например, так? Звучит логично.
                                                                            0
                                                                            Да, для него, например.
                                                                    0
                                                                    Звучит разумно и выглядит красиво, но, к примеру, в стандарте написания высоконадежного кода MISRA C (используемого, к примеру, при разработке встраиваемого ПО для автомобилей) прямым текстом сказано:
                                                                    Rule 14.7 :(required) «A function shall have a single point of exit at the end of the function».
                                                                    Аналогичные пункты есть и в других подобных стандартах.
                                                                      +1

                                                                      Стандарты пишут люди. Люди могут ошибаться.

                                                                        +1
                                                                        Стандарты пишут люди далеко не глупые и с экспертизой в своей сфере, основываясь на логике и реальном опыте. Иными словами, если что-то написано, то скорее всего не просто так.
                                                                        А ещё стандарты развиваются, исправляются и дополняются. Иными словами, если какой-то пункт стандарта пережил уже множество редакций (учитывая, что его отмена никак не влияет на обратную совместимость), то он там тем более не просто так :)
                                                                          0
                                                                          А ещё стандарты развиваются, исправляются и дополняются
                                                                          Именно это я и хотел сказать. То, что это стандарт, и «так принято», еще не означает, что больше не существует других более выгодных вариантов написания кода.
                                                                            0
                                                                            Неглупые люди приняли рекоммендации по валидации паролей. Все эти перемешивания заглавных и строчных букв, цифр и спецсимволов.
                                                                              0
                                                                              Стандарт генерации паролей и стандарт написания кода, от корректности которого зависят жизни сотни людей — это какбэ немного разные вещи.
                                                                              Плюс я именно об этом говорил в предыдущем комментарии, те же NIST'овские рекомендации про пароли перевыпустили, а в стандартах написания высоконадежного кода обсуждаемый тезис используется уже не один десяток лет и пережил множество переизданий в разных формах, и ничего, самолёты не падают, АЭС не взрываются.
                                                                                0
                                                                                Вообще-то от програмных ошибок и ракеты взрывались. Но вот теперь уже — машины врезаются.

                                                                                И те же АЭС взрываются и самолеты падают — хотя у меня нет статистики по тому, какая часть из проблем связана с плохим кодом.

                                                                                Мне ваша аргументация кажется очень слабой.
                                                                                  +1
                                                                                  Я вообще немного не о том.
                                                                                  В авиации есть такое расхожее выражение: «правила безопасности пишутся кровью».
                                                                                  Как минимум, при разработке стандартов, при добавлении тезисов или выборе из альтернативных сущностей, выводы делаются не только на голой теории, но и с учетом анализа обнаруженных дефектов в уже существующих и существовавших изделиях и процессах, с учетом последствий, к которым они привели. Это к тому, что если что-то написано, то оно написано не «с потолка».
                                                                                  Касательно «пережил много изданий» и «самолеты не падают, АЭС не взрываются». Когда появляются какие-то новые данные или доводы, или происходит какой-то случай, который оказывается не покрытым правилами или же доказывает их ошибочность, стандарты оперативно обновляются и перевыпускаются. Иными словами, касательно конкретно обсуждаемого пункта, если пункт по прежнему присутствует в списке правил, значит за десятки лет не нашлось ни одного события или существенного аргумента, обоснованно говорящих о его некорректности.
                                                                                    0
                                                                                    > выводы делаются не только на голой теории, но и с учетом анализа
                                                                                    > обнаруженных дефектов в уже существующих и существовавших изделиях

                                                                                    Вы уверенны, что это имеет отношение к статье?

                                                                                    > Это к тому, что если что-то написано, то оно написано не «с потолка».

                                                                                    Мой пример с паролями — опровергает ваше утверждение.

                                                                                    > стандарты оперативно обновляются и перевыпускаются.

                                                                                    Вы в общем может быть правы, если исключить «оперативно».

                                                                                    > значит за десятки лет не нашлось ни одного события или существенного
                                                                                    > аргумента, обоснованно говорящих о его некорректности.

                                                                                    Утверждение никак не связано логически с тем, что вы написали до него. И в целом ложно.
                                                                                      0
                                                                                      Вы уверенны, что это имеет отношение к статье?

                                                                                      А что конкретно вас смущает?
                                                                                      В статье предлагается идея, противоречащая утвержденной и выверенной практике в отраслях, критичных к безопасности и корректности ПО.
                                                                                      Если вам не нравится терминология, то можете учесть, что «софт» является неотъемлемой частью «изделия», а термин «дефекты в ПО» так вообще довольно распротраненный с сфере безопасности и оценки качества программ.

                                                                                      Мой пример с паролями — опровергает ваше утверждение.

                                                                                      Я специально выше отметил, что это совершенно разные сферы с совершенно разными методами. Вероятность катастрофы с человеческими жертвами из-за слабого пароля на много порядков меньше чем из-за ошибки в программе управления двигателями авиалайнера. Следовательно, отличаются и сами процессы подготовки документов и требования к ним.
                                                                                      Тот же NIST'овский стандарт писался даже не комитетом экспертов, а одним-единственным рядовым менеджером базируясь на голой теории (причем еще ошибочно примененной) без анализа известных «неудачных случаев». В авиации и энергетике такое в принципе невозможно. Поэтому как пример его приводить бессмысленно.
                                                                                      И самое главное: когда появились обоснованные доводы ошибочности его пунктов, стандарт все-таки пересмотрели и перевыпустили.

                                                                                      Вы в общем может быть правы, если исключить «оперативно».

                                                                                      Не скажу про атомную энергетику, но в авиации — весьма оперативно.
                                                                                      Утверждение никак не связано логически с тем, что вы написали до него. И в целом ложно.

                                                                                      Обоснуйте.
                                                                                      Есть два тезиса:
                                                                                      1. «При обнаружении реальных фактов о некорректности пунктов стандарта, в стандарт должны вноситься (и по факту вносятся) изменения и он перевыпускается».
                                                                                      2. «За несколько десятков лет существования стандарта и нескольких переизданий его, обсуждаемый пункт не был убран или изменен».
                                                                                      Вывод очевиден: «за все это время не нашлось ни одного события или аргумента, весомо и объективно говорящего о его неправильности».
                                                                                        0
                                                                                        > В статье предлагается идея, противоречащая утвержденной и
                                                                                        > выверенной практике в отраслях, критичных к безопасности и
                                                                                        > корректности ПО.

                                                                                        Это факт или мнение?

                                                                                        > Я специально выше отметил, что это совершенно разные сферы с
                                                                                        > совершенно разными методами.

                                                                                        Безопасность и безопасность — это разные сферы. А безопасность и кодирование — это одно и то же. Я правильно вас не понимаю?

                                                                                        > В авиации и энергетике такое в принципе невозможно.

                                                                                        Это факт или мнение?

                                                                                        > И самое главное: когда появились обоснованные доводы
                                                                                        > ошибочности его пунктов, стандарт все-таки пересмотрели и
                                                                                        > перевыпустили.

                                                                                        И более ошибочные рекоммендации не используются, я так понимаю?

                                                                                        > Вывод очевиден: «за все это время не нашлось ни одного события
                                                                                        > или аргумента, весомо и объективно говорящего о его
                                                                                        > неправильности».

                                                                                        Вывод очевиден. Формальную логику в школах отменили зря.
                                                                                          +1
                                                                                          Безопасность и безопасность — это разные сферы.

                                                                                          Да )) Безопасность в области "постить котиков", личных и корпоративных финансов, медицины и АЭС — это разные безопасности, разная цена ошибки, следовательно разные ресурсы выделяются на их предотвращение.


                                                                                          Безопасность (в контексте безошибочности) и удобство обычно противоречат друг другу, и в отраслях, где цена ошибки относительно низка удобство побеждает. Вон, посмотрите в топике про malloc какие дебаты пошли из-за одного if, причём большинство (субъективно) склоняется к мнению, что можно доверять исторически сложившемуся поведению некоторых компиляторов и некоторых реализаций ОС с некоторыми настройками. Что какие-то частные случаи не стоят вставки if после каждого malloc для проверки на NULL, поскольку на подавляющем большинстве систем реальных пользователей проверка не гарантирует реального выделения памяти, а фактически сложившиеся традиции в реализации дадут аналогичный самой простой проверке результат. Что это неопределенное поведение, в любой момент могущее измениться, никого не парит.

                                                                                            0
                                                                                            О! А мы с вами уже дискутировали когда-то, насколько помню это в тот раз подзатянулось.

                                                                                            Я надеюсь, что статью вы читали и заметили, что автор не предлагал не проверять какие-то условия, что там кто ленится делать — это их ответственность, главное чтоб не вредили намеренно.

                                                                                            В статье речь шла о выделении логических блоков при написании функции. F0iL написал, что это противоречит безопасности и выверенной практике в отраслях. Что это вообще такое — выверенная практика в разработке ПО? Ну хорошо бывают стандарты — которые устаревают и пересматриваются, бывают рекоммендации, бывает то, что для кого-то работает в определенных условиях и они эти подходы масштабируют. Я привел пример того, что рекоммендации в индустрии бывают ошибочными, обновляются не достаточно быстро, мало того реализации обновленных рекоммендаций затягиваются на годы.
                                                                                            F0iL продолжил настаивать, воспользовавшись аргументом что это другая сфера. Что вы хотели сказать/доказать я вообще не понимаю.

                                                                                            Вообще статья мало нового постулирует. Схожие идеи развиваются гораздо более формально в рамках фреймворков типа .NET, где часть проверок уходит в аттрибуты, что это как не частный случай раннего возврата? В более широком смысле aspect oriented prorgramming. Точнее будет сказать, то что автор описывает в статье — это poor man's aspect oriented programming — переизобретение велосипеда.

                                                                                            В связи с этим дважды не понятно чего такое F0iL выдумывает, про стандарты, устоявшиеся практики и подобное.
                                                                                            0
                                                                                            Это факт или мнение?

                                                                                            Факт.
                                                                                            Безопасность и безопасность — это разные сферы. А безопасность и кодирование — это одно и то же. Я правильно вас не понимаю?

                                                                                            В соседнем комменте уже совершенно ответили за меня, причем совершенно правильно ответили.
                                                                                            Это факт или мнение?

                                                                                            Факт.
                                                                                            И более ошибочные рекоммендации не используются, я так понимаю?

                                                                                            В целом верно. Зависит от конкретной ситуации. В некоторых случаях стандарт вступает в силу непосредственно в момент его опубликования, в некоторых случаях — для новых проектов и производных документов.
                                                                                            Вывод очевиден. Формальную логику в школах отменили зря.

                                                                                            То есть обосновать вы не можете. Понятно.
                                                                                              0
                                                                                              > Факт.

                                                                                              ФАКТ, факта, муж. (лат. factum).
                                                                                              1. Действительное событие, явление, то, что произошло в действительности. Исторический факт. Смело смотреть фактам в лицо. Это — факт, а не выдумка.…
                                                                                              2. Данное, являющееся материалом для какого-нибудь заключения, вывода или служащее проверкой предположения, теории. Изложив факты, автор переходит к их толкованию. Исследование опирается на факты. Проверить факты. Опровергнуть что-нибудь с фактами в руках. Обильный фактами рассказ. Искажать факты.…

                                                                                              МНЕ́НИЕ, мнения, ср.
                                                                                              1. Взгляд на что-нибудь, суждение о чем-нибудь, выраженное в словах. Интересное мнение. Придерживаться какого-нибудь мнения по вопросу о чем-нибудь. По моему мнению. Разделять чье-нибудь мнение. Обменяться мнениями. Усвоить чужое мнение.
                                                                                              || Официальное заключение по какому-нибудь вопросу, требующему решения (офиц.). Мнение экспертизы. Запросить мнение комиссии. Особое мнение (см. особый).
                                                                                              2. только ед. Оценка, то или иное суждение о ценности чего-нибудь. Быть низкого мнения о чем-нибудь. Быть о себе высокого мнения. Я хорошего мнения о вашем друге.
                                                                                              ❖ Общественное мнение — суждение общества о чем-нибудь, отношение общества к чему-нибудь; само общество, как источник этих суждений. Считаться с общественным мнением. Благоприятное общественное мнение. Успокоить общественное мнение. Подготавливать общественное мнение.

                                                                                              ----

                                                                                              > В статье предлагается идея, противоречащая утвержденной и
                                                                                              > выверенной практике в отраслях, критичных к безопасности и
                                                                                              > корректности ПО.

                                                                                              > В авиации и энергетике такое в принципе невозможно.

                                                                                              Вы либо заблуждаетесь, либо нас обманываете.

                                                                                              > То есть обосновать вы не можете. Понятно.

                                                                                              Вам обосновать формальную логику? Вы образцово продемонстрировали логическую ошибку.
                                                                                                0
                                                                                                Может быть вы все-таки перестанете устраивать клоунаду и начнете оперировать аргументами и доводами?
                                                                                                  0
                                                                                                  Давайте просто закончим дискуссию. Договориться о даже базовой терминологии нам с вами не удается. Как можно хоть чем-то оперировать, если вы под этим будете понимать что-то совершенно иное?!
                                                                                                    0
                                                                                                    Ваше право, давайте закончим.
                                                                                        0
                                                                                        Насчёт новых данных.

                                                                                        Вот я в комментарии чуть выше описал возможную причину необходимости наличия единственной точки выхода. И пока все в автомобильно-самолётной индустрии пишут на С или, упаси Тойота, на ассемблере, это оправдано. Потом приходит смелая компания и начинает писать на плюсах, где есть деструкторы. Информация новая? Да. Опровергает старую рекомендацию? Нет, старая рекомендация от этого не приводит к написанию некорректного кода. Сохраняет ли она смысл? Нет, корректный код становится настолько же легко (если не легче) писать в новом стиле. Пересмотрят ли её с учётом этого? А хрен его знает.
                                                                            –1

                                                                            А производительность??? Правильные данные случаются намного чаще, чем неправильные. А при этом подходе, код будет выполнятся медленней всего именно когда данные правильные.


                                                                            То есть, вред конкретный и вещественный, а польза несколько виртуальная…

                                                                              0
                                                                              Это точно не преждевременная оптимизация некритичных фрагментов кода?
                                                                                –1

                                                                                Нет, не думаю. Это преждевременная, сознательная пессимизация всего кода. А когда придет время оптимизировать (ведь, "преждевременно" не значит "никогда") такой код придется переписывать начисто.


                                                                                Оптимизировать раньше срока, конечно плохо. Но надо писать optimization-friendly код.

                                                                                  0
                                                                                  В 95% прикладных задач основной затык на I/O (ожидание выполнения запроса на СУБД, ответа какого нибудь http api, чтение/запись файлов и т.д.), а уж никак не там где вы пишете. Это важно на вычислительных задачах, сложных алгоритмах, в системном ПО.
                                                                                    0
                                                                                    В 95% прикладных задач основной затык на I/O (ожидание выполнения запроса на СУБД, ответа какого нибудь http api, чтение/запись файлов и т.д.), а уж никак не там где вы пишете. Это важно на вычислительных задачах, сложных алгоритмах, в системном ПО.

                                                                                    Так это же религиозная мантра. Мой опыт говорит наоборот – если пишешь оптимальный код, то программы получаются быстрые. Если всегда думаешь "преждевременная оптимизация зло!", то получаются медленные монстры, которые только убить можно, но никак не исправить. :)


                                                                                    Вот статья, человек просто сделал код на 20000% (sic!) быстрее, просто думая о оптимальности кода, а не написанием оптимального код:


                                                                                    https://medium.com/@okaleniuk/premature-optimization-is-the-root-of-all-evil-is-the-root-of-evil-a8ab8056c6b

                                                                                      0
                                                                                      От задач зависит все же. Например в моей области примерно 80-90% времени выполнения по данным профилировщика — как раз работа с базой (это при том что все естественно стараются запросы и их количество оптимизировать). Ну, конечно если совсем очевидных ляпов не допускать. Недавно мобильное приложение делал — тоже основной проблемой было оптимизировать загрузку по сети больших объемов данных.
                                                                                        0
                                                                                        От задач зависит все же.

                                                                                        А кто спорит? Но если человек сознательно начинает писать суб-оптимальный код, то он его будет писать всегда. Те же запросы к БД можно и нужно оптимизировать. И скорость реально повышается в десятки раз. Я писал форум на ассемблере и SQLite. Все говорят что SQLite медленная. Но нет, оказывается что SQLite очень даже быстрая, просто готовит запросы правильно надо. И теперь у меня есть самый быстрый форум в мире, на "самой медленной" БД.

                                                                                          0
                                                                                          Ага, только вы писали его месяц, хотя там работы на пару дней)
                                                                                          А поскольку ожидание ввода-вывода от языка не зависит, то на PHP тот же функционал будет работать ну может на несколько процентов медленнее.
                                                                                            0
                                                                                            Ага, только вы писали его месяц, хотя там работы на пару дней

                                                                                            Ну, ну, а разработчики например myBB, SMF или Flarum тоже так считают?

                                                                                              0
                                                                                              Дак там и функционала побольше
                                                                                            0
                                                                                            Да, читал статью вашу, очень круто конечно, но для реальных задач бизнеса имеет мало смысла, имхо. Но например в моем случае вообще весь код процентов на 50 из запросов состоит. И запросы на 1к+ строк из за сложности бизнес логики, особенно в отчетах, но не только, вполне нормальное явление. И поверьте, за ту четверть секунды что выполняется такой запрос — выхода отработают даже на том языке что я пишу десятки тысяч раз. Понятно что не стоит пихать экспоненциальную сложность алгоритмов там где можно реализовать что то с линейной или другой низкой сложностью, но экономить на тактах в таких системах не приходится, все же гораздо важнее читабельность.
                                                                                      0
                                                                                      «Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте.»
                                                                                      Автора знаете?
                                                                                        0
                                                                                        Писатель-классик Стив Макконнелл
                                                                                        image
                                                                                    0
                                                                                    Точно. Буквально год-полтора назад на Хабре была статья, что лучше писать if-elif-else, чем if-if-if, именно по причине, что во втором случае все условия будут проверется всегда.
                                                                                      0
                                                                                      В данном случае оба подхода идентичны, так как после любого верного if идет возврат из функции (то, что после уже не проверяется)
                                                                                      0
                                                                                      > Правильные данные случаются намного чаще, чем неправильные.

                                                                                      А не наоборот?
                                                                                        0

                                                                                        В целом именно правильные случаются чаще хоть в публичных, хоть в закрытых контурах, если ваша система не является целью постоянных атак хакеров, причём брутфорсных. Ну или ваша система сугубо одноразовая, пользователи пользуются ею один раз в жизни. При многократном использовании пользователи, пускай и с разной скоростью, но учатся вводить корректные данные для достижения своих целей. Причём корректные именно с точки зрения системы, а не документации на неё и(или) здравого смысла. Пускай на уровне "это невозможно понять, это нужно запомнить", но учатся.

                                                                                        0
                                                                                        Не понятно что вы предлагает. Не валидировать данные?
                                                                                        0
                                                                                        Всегда так делаю, но просто потому что лень писать все эти вложенные if else if.
                                                                                          0
                                                                                          Я этот подход уже давно использую, и вот читая статью я подумал, ведь это пессимистичный подход. С точки зрения производительности этот код видимо хуже того который сразу переходит к решению задачи. Ведь если подумать, то пессимистичный подход нужен только во время написания кода, а потом после отладки когда все работает, весь этот проверочный хлам будет мешать работе программы и влиять на производительность.
                                                                                            0

                                                                                            Он нужен, прежде всего, во время чтения кода.

                                                                                              0
                                                                                              Да нет там пенальти по производительности. Проверку все равно делать, условный переход все равно делать. Зато компилятору удобнее оптимизировать при инлайнинге (если он видит, что в конкретном месте условия заведомо ложны, он может их спокойно выкинуть)
                                                                                                0
                                                                                                Не факт. Представим себе, что функция вызывается в цикле, тогда проверку можно было бы вынести из цикла, но компилятор не сможет об этом догадаться кроме самых тривиальных случаев.
                                                                                                  0
                                                                                                  Вот вот, проверку все равно делать, из за криворукости программистов, так? Ведь если параметры не проверять, то такая функция приведет к чудесам. Но вот скажем ваша функция вызывается миллион раз и лишь в одном случае я передал не правильные параметры. Получается я потратил ресурсы процессора просто так 999,999 раз и только чтоб отловить и залогировать всего одну ошибку. Я это к общей концепции современного программирования.
                                                                                                    0
                                                                                                    Ну, можете не делать никаких проверок. Только потом может прийти мальчиш-плохиш и взломать вашу программу.
                                                                                                    На самом деле, в языках с контрактами, а также хороших оптимизирующих компиляторах не надо делать 999999 проверок; они способны проанализировать вызывающую функцию и избыточные проверки отбросить.
                                                                                                    Однако, это не спасает от необходимости проверять пользовательский ввод, в том числе с диска или по сети.
                                                                                                      0
                                                                                                      Получается я потратил ресурсы процессора просто так 999,999 раз и только чтоб отловить и залогировать всего одну ошибку.

                                                                                                      А лог был "hack attempt!!!" в системе, которая ворочает деньгами на порядки бОльшими цена миллиардов тактов процессора.

                                                                                                  0
                                                                                                  Ещё можно через "… do {… } while (false);" сделать:
                                                                                                  // setup
                                                                                                  int status = 0;
                                                                                                  
                                                                                                  do {
                                                                                                     // preconditions
                                                                                                     status = doSomething();
                                                                                                     if (status) break;
                                                                                                     status = doSomethingElse();
                                                                                                     if (status) break;
                                                                                                  
                                                                                                     // computation
                                                                                                     status = doWhatYouWantedToInTheFirstPlace();
                                                                                                  } while (false);
                                                                                                  
                                                                                                  // cleanup
                                                                                                  return status;
                                                                                                    +1
                                                                                                    Можно, но когда Вы увидите такой код в чужом проекте повсеместно, Вы вряд ли сильно обрадуетесь.
                                                                                                    0
                                                                                                    Тут как с музыкой.
                                                                                                    Кто-то может играть на гитаре только по написанным аккордам и написанному бою (типа вверх-вниз-глушим).
                                                                                                    А кто-то может сам понять на слух, какой аккорд взять и как бить по струнам в конкретной песне.
                                                                                                      –1
                                                                                                      Писать в таком стиле удобно. А вот при отладке искать потом, где произошёл возврат — трудно. так что подход спорный.
                                                                                                        +1
                                                                                                        Не понятно чем сложнее найти возврат из 2-го Return по сравнению с присвоением в третьем else. Уж точно меньше кода надо перелопатить.
                                                                                                        0

                                                                                                        Только такой подход надо сочетать с принципом RAII.
                                                                                                        А то со всеми этими return'ами можно ресурсы растерять.

                                                                                                          0
                                                                                                          Не во всех языках есть концепции, близкие к RAII, хотя где-то есть поддержка на уровне синтаксического сахара.
                                                                                                            0
                                                                                                            Не во всех.
                                                                                                            Но принцип «раннего возврата» — более-менее универсальный.
                                                                                                            Потому там где есть RAII — нелишним будет ранний возврат сочетать с RAII.
                                                                                                            А то будут всякие утечки памяти, незакрытые файлы и навечно заблокированные мьютексы.
                                                                                                            Да и код самой функции с «ранним возвратом» будет более стройным.
                                                                                                            А то по мере углубления в функцию — возвраты становятся всё более хлопотными:
                                                                                                            на первом if — просто выход (ничего ещё не делали);
                                                                                                            на втором if — перед выходом надо файл закрыть, который открыли после первого if;
                                                                                                            на третьем if — перед выходом надо и файл закрыть и память освободить;
                                                                                                            на четвертом if — всё то же самое, плюс не забыть освободить мьютекс
                                                                                                            И т.д.

                                                                                                            Можно конечно возразить, мол надо сначала проверить все условия, а потом делать работу, в т.ч. выделять и захватывать ресурсы.
                                                                                                            Но, во-первых, не всегда это возможно. От алгоритма зависит.
                                                                                                            А во-вторых, принцип «раннего возврата» — более универсален, чем просто «проверка условий перед тем как засучить рукава». Необходимость возврата может возникнуть уже «в глубине» функции, а не в «проверочном заголовке». И легко можно что-то забыть освободить/закрыть.
                                                                                                              0
                                                                                                              Если акцент на то, что если язык поддерживает RAII, то он очень полезен в связке с ранним возвратом — то я полностью согласен.
                                                                                                              Но надо только понимать, когда сначала стоит проверить проверить предусловия, а потом уже выделять ресурсы и отдавать их под контроль Раи. Перемешивать проверку и выделение ресурсов может быть весьма неэффективным.
                                                                                                                0

                                                                                                                Вот, наверное, да, хороший принцип — ранний возврат в блоке проверки предусловий, а дальше уже вложенные проверки или try/catch/finally, когд аначинается реальная работа.

                                                                                                                  0
                                                                                                                  Это очень ограничивает применимость принципа «раннего возврата».
                                                                                                                  Т.е. мы «делаем красиво», только в небольшой части функции — в начале.
                                                                                                                  А дальше — всё та же «лестница» из ифов-элсов.
                                                                                                                  Полумеры.
                                                                                                                    0

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

                                                                                                                      0

                                                                                                                      А почему применение "раннего возврата" в любом месте функции (не только в "заголовке") — крайность?
                                                                                                                      При условии автоуправления ресурсами, конечно.

                                                                                                                        0

                                                                                                                        И при условии, что достаточно одного return, без, например, логирования, отката транзакций и т. п. В общем, при условии, что можно написать if (condition) return SOME_CONST или близкое по простоте выражение

                                                                                                          +1
                                                                                                          Очень много сталкивался с таким кодом, как у студентов, так и у опытных программистов, где имеется вложеный в три-пять условий возврат или положительное выполнение, при этом вся функция представляет собой одно условие. Всегда предпочитаю такое переписывать и проверять на негативные варианты сначала. Во-первых таким образом уменьшается количество возможных ветвей исполнения. Во-вторых код намного удобнее читать и модифицировать. Стараюсь по возможности своих студентов учить такому же
                                                                                                            0

                                                                                                            Фаулер описал этот прием 20 лет назад в книге "Рефакторинг".

                                                                                                              0
                                                                                                              Да вобщем-то довольно легко до него додуматься, даже без книг. Вобщем не удивительно что вещь известная и давняя
                                                                                                              0
                                                                                                              Вот ведь, а нам еще 15 лет назад руки за такое отрывали. Код возврата в отдельную переменную и все ветвления просто проверяют ее значение и не заходят. А return — только один. Хотя, надо признать, в некоторых случаях, когда знаешь, что функция будет вызываться очень часто наиболее вероятную входную комбинацию сразу отрезал. Последнее помогало производительности.

                                                                                                              Only users with full accounts can post comments. Log in, please.