Вопросы обработки ошибок

31

Проблема:

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

ПРЕТЕНЗИЯ: Есть долгие дебаты по этой теме, и большинство из них пытаются сравнить с exceptionsвозвратом кода ошибки. Это определенно не тема здесь.

Пытаясь определить ошибку, я бы согласился с CppCoreGuidelines, от Bjarne Stroustrup & Herb Sutter

Ошибка означает, что функция не может достичь своей объявленной цели

ПРЕТЕНЗИЯ: exceptionМеханизм является языковой семантикой для обработки ошибок.

ПРЕТЕНЗИЯ: Для меня "нет оправдания" функции для невыполнения задачи: либо мы неправильно определили предварительные / последующие условия, чтобы функция не могла обеспечить результаты, либо какой-то конкретный исключительный случай не считается достаточно важным для того, чтобы тратить время на разработку решение. Учитывая, что, IMO, разница между обычным кодом и обработкой кода ошибки (до реализации) очень субъективная линия.

ПРЕТЕНЗИЯ: Использование исключений для указания того, что условие pre или post не выполняется, является еще одной целью exceptionмеханизма, главным образом для целей отладки. Я не нацелена на это использование exceptionsздесь.

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

  • Программисты стремятся упростить свою задачу, создавая исключения, когда конкретный случай кажется слишком редким, чтобы его можно было тщательно реализовать. Типичные случаи этого: проблемы с нехваткой памяти, проблемы с заполнением диска, проблемы с поврежденными файлами и т. Д. Этого может быть достаточно, но это не всегда решается на архитектурном уровне.
  • Программисты, как правило, не читают внимательно документацию об исключениях в библиотеках и обычно не знают, что и когда выдает функция. Кроме того, даже когда они знают, они на самом деле не управляют ими.
  • Программисты имеют тенденцию не ловить исключения достаточно рано, и когда они это делают, это в основном, чтобы войти и бросить дальше. (обратитесь к первому пункту).

Это имеет два последствия:

  1. Часто возникающие ошибки выявляются на ранних этапах разработки и отлаживаются (что хорошо).
  2. Редкие исключения не управляются и приводят к сбою системы (с хорошим сообщением журнала) в доме пользователя. Иногда сообщается об ошибке, или даже нет.

Учитывая это, IMO основной целью механизма ошибок должно быть:

  1. Сделать видимым в коде, где какой-то конкретный случай не управляется.
  2. Сообщите время выполнения проблемы связанному коду (по крайней мере, вызывающему), когда такая ситуация произойдет.
  3. Обеспечивает механизмы восстановления

Основным недостатком exceptionсемантики как механизма обработки ошибок является IMO: легко увидеть, где throwнаходится a в исходном коде, но совершенно не очевидно, может ли конкретная функция выдать, посмотрев объявление. Это приносит все проблемы, которые я представил выше.

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

Попытка решения

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

Идея заключается в следующем:

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

Полный дизайн, очевидно, тщательно рассматривает каждый аспект (около 10 страниц), а также то, как применить его к ООП.

Пример Successкласса:

class Success
{
public:
    enum SuccessStatus
    {
        ok = 0,             // All is fine
        error = 1,          // Any error has been reached
        uninitialized = 2,  // Initialization is required
        finished = 3,       // This object already performed its task and is not useful anymore
        unimplemented = 4,  // This feature is not implemented already
    };

    Success(){}
    Success( const Success& v);
    virtual ~Success() = default;
    virtual Success& operator= (const Success& v);

    // Comparators
    virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
    virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}

    // Retrieve if the status is not "ok"
    virtual bool operator!() const { return status!=ok;}

    // Retrieve if the status is "ok"
    operator bool() const { return status==ok;}

    // Set a new status
    virtual Success& set( SuccessStatus status, std::string msg="");
    virtual void reset();

    virtual std::string toString() const{ return stateStr;}
    virtual SuccessStatus getStatus() const { return status; }
    virtual operator SuccessStatus() const { return status; }

private:
    std::string stateStr;
    SuccessStatus status = Success::ok;
};

Использование:

double mySqrt( Success& s, double v)
{
    double result = 0.0;
    if (!s) ; // do nothing
    else if (v<0.0) s.set(Error, "Square root require non-negative input.");
    else result = std::sqrt(v);
    return result;
}

Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;

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

Вопрос

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

  • Является ли предпосылка к проблеме правильной? или я пропустил что-то актуальное?
  • Является ли решение хорошей архитектурной идеей? или цена слишком высока?

РЕДАКТИРОВАТЬ:

Сравнение между методами:

//Exceptions:

    // Incorrect
    File f = open("text.txt"); // Could throw but nothing tell it! Will crash
    save(f);

    // Correct
    File f;
    try
    {
        f = open("text.txt");
        save(f);
    }
    catch( ... )
    {
        // do something 
    }

//Error code (mixed):

    // Incorrect
    File f = open("text.txt"); //Nothing tell you it may fail! Will crash
    save(f);

    // Correct
    File f = open("text.txt");
    if (f) save(f);

//Error code (pure);

    // Incorrect
    File f;
    open(f, "text.txt"); //Easy to forget the return value! will crash
    save(f);

    //Correct
    File f;
    Error er = open(f, "text.txt");
    if (!er) save(f);

//Success mechanism:

    Success s;
    File f;
    open(s, "text.txt");
    save(s, f); //s cannot be avoided, will never crash.
    if (s) ... //optional. If you created s, you probably don't forget it.
Адриан Мэйр
источник
25
Голосование за «Этот вопрос свидетельствует об исследовательских усилиях; оно полезно и понятно», а не потому, что я согласен: я думаю, что некоторые мысли ошибочны. (Подробности могут последовать в ответ.)
Мартин Ба
2
Абсолютно, я понимаю и согласен с этим! Цель этого вопроса - подвергнуться критике. И оценка вопроса, чтобы указать хорошие / плохие вопросы, а не то, что ОП является правильным.
Адриан Мэйр
2
Если я правильно понимаю, ваша главная претензия к исключениям состоит в том, что люди могут игнорировать их (в c ++) вместо того, чтобы обрабатывать их. Тем не менее, ваша конструкция успеха имеет тот же недостаток в дизайне. Как и исключения, они просто проигнорируют это. Еще хуже: это более многословно, приводит к каскадному возврату, и вы даже не можете «поймать» его вверх по течению.
17
3
Почему бы просто не использовать что-то вроде монад? Они делают ваши ошибки неявными, но они не будут молчать во время выполнения. На самом деле, первое, что я подумал, глядя на твой код, было: «Монады, хорошо». Посмотри на них.
bash0r
2
Основная причина, по которой мне нравятся исключения, заключается в том, что они позволяют вам отлавливать все непредвиденные ошибки из данного блока кода и обрабатывать их последовательно. Да, нет веской причины, по которой код не должен выполнять свою задачу - «произошла ошибка» - это плохая причина, но она все же происходит , и когда это происходит, вы хотите записать причину и отобразить сообщение или повторить попытку. (У меня есть некоторый код, который выполняет сложное, перезапускаемое взаимодействие с удаленной системой; если удаленная система выйдет из строя, я хочу зарегистрировать ее и повторить с самого начала)
user253751

Ответы:

32

Обработка ошибок , пожалуй, самая сложная часть программы.

В общем, понять, что есть условие ошибки, легко; однако сигнализировать об этом таким способом, который невозможно обойти, и обращаться с ним надлежащим образом (см. Уровни исключительной безопасности Абрахамса ) действительно сложно.

В C сигнализация ошибок осуществляется кодом возврата, который изоморфен вашему решению.

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

Проблема, однако, в том, что исключения имеют свои проблемы. А именно невидимый / скрытый поток управления. Это было задумано: скрыть регистр ошибок, чтобы логика кода не была запутана шаблоном обработки ошибок. Это делает «счастливый путь» намного более понятным (и быстрым!) За счет того, что пути ошибок становятся почти непостижимыми.


Мне интересно посмотреть, как другие языки подходят к этой проблеме:

  • Java проверила исключения (и непроверенные),
  • Go использует коды ошибок / паники,
  • Rust использует суммы типов / паники).
  • Языки FP в целом.

В C ++ раньше была какая-то форма проверяемых исключений, вы, возможно, заметили, что она устарела и noexcept(<bool>)вместо этого упростилась до базовой : либо объявлена ​​функция, которая может выдавать, либо она никогда не объявляется. Проверенные исключения несколько проблематичны тем, что им не хватает расширяемости, что может привести к неуклюжему отображению / вложению. И извилистые иерархии исключений (один из основных случаев виртуального наследования - исключения ...).

Go и Rust, напротив, используют подход, который:

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

Последнее довольно очевидно в том, что (1) они называют свои исключения паникой и (2) здесь нет иерархии типов / сложного предложения. Язык не предлагает средств для проверки содержимого «паники»: нет иерархии типов, нет пользовательского содержимого, просто «упс, все пошло не так, восстановление невозможно».

Это эффективно побуждает пользователей использовать правильную обработку ошибок, в то же время оставляя простой способ помочь в исключительных ситуациях (таких как: «подождите, я еще не реализовал это!»).

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

... однако подход Rust в основном сосредоточен вокруг двух типов:

  • Option, который похож на std::optional,
  • Result, который является вариантом двух вариантов: Ok и Err.

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


Языки FP формируют свою обработку ошибок в конструкциях, которые можно разбить на три уровня: - Функтор - Применимый / Альтернативный - Монады / Альтернативный

Давайте посмотрим на Functorкласс типов Haskell :

class Functor m where
  fmap :: (a -> b) -> m a -> m b

Прежде всего, классы типов несколько похожи, но не равны интерфейсам. Сигнатуры функций Haskell на первый взгляд выглядят немного страшно. Но давайте расшифруем их. Функция fmapпринимает функцию в качестве первого параметра, который чем-то похож на std::function<a,b>. Следующая вещь - это m a. Вы можете представить mкак нечто подобное std::vectorи m aкак нечто подобное std::vector<a>. Но разница в том, что m aэто не значит, что это должно быть явно std:vector. Так что это std::optionтоже может быть . Сказав языку, что у нас есть экземпляр класса типов Functorдля определенного типа, такого как std::vectorили std::option, мы можем использовать функцию fmapдля этого типа. То же самое должно быть сделано для классов типов Applicative, AlternativeиMonadкоторый позволяет вам выполнять вычисления с возможным состоянием, возможные ошибки. Класс Alternativeтипов реализует абстракции восстановления после ошибок. Таким образом, вы можете сказать что-то вроде a <|> bзначения aили термин b. Если ни одно из обоих вычислений не выполнено, это все равно ошибка.

Давайте посмотрим на Maybeтип Хаскелла .

data Maybe a
  = Nothing
  | Just a

Это означает, что там , где вы ожидаете Maybe a, вы получаете либо Nothingили Just a. Если смотреть fmapсверху, реализация может выглядеть так:

fmap f m = case m of
  Nothing -> Nothing
  Just a -> Just (f a)

case ... ofВыражение называется сопоставлением с образцом и напоминает то , что известно в мире как ООП visitor pattern. Представьте, что строка case m ofas, m.apply(...)а точки - это экземпляр класса, реализующего функции диспетчеризации. Строки под case ... ofвыражением - это соответствующие диспетчерские функции, доставляющие поля класса непосредственно в области видимости по имени. В Nothingветви, которую мы создаем, Nothingи в Just aветви мы называем наше единственное значение aи создаем другое Just ...с fпримененной функцией преобразования a. Читайте , как: new Just(f(a)).

Теперь он может обрабатывать ошибочные вычисления, абстрагируя от фактических ошибок. Существуют реализации для других интерфейсов, что делает этот вид вычислений очень мощным. На самом деле, Maybeэто вдохновение для OptionRust's-Type.


Я бы посоветовал вам переделать свой Successкласс Resultвместо этого. Александреску фактически предложил нечто очень близкое, названное expected<T>, для чего были сделаны стандартные предложения .

Я буду придерживаться именования и API Rust просто потому, что ... это задокументировано и работает. Конечно, у Rust есть отличный ?суффиксный оператор, который сделает код намного слаще; в C ++ мы будем использовать TRYмакрос и выражение операторов GCC для его эмуляции.

template <typename E>
struct Error {
    Error(E e): error(std::move(e)) {}

    E error;
};

template <typename E>
Error<E> error(E e) { return Error<E>(std::move(e)); }

template <typename T, typename E>
struct [[nodiscard]] Result {
    template <typename U>
    Result(U u): ok(true), data(std::move(u)), error() {}

    template <typename F>
    Result(Error<F> f): ok(false), data(), error(std::move(f.error)) {}

    template <typename U, typename F>
    Result(Result<U, F> other):
        ok(other.ok), data(std::move(other.data)),  error(std::move(other.error)) {}

    bool ok = false;
    T data;
    E error;
};

#define TRY(Expr_) \
    ({ auto result = (Expr_); \
       if (!result.ok) { return result; } \
       std::move(result.data); })

Примечание: это Resultзаполнитель. Правильная реализация будет использовать инкапсуляцию и union. Однако этого достаточно, чтобы понять суть.

Что позволяет мне написать ( увидеть это в действии ):

Result<double, std::string> sqrt(double x) {
    if (x < 0) {
        return error("sqrt does not accept negative numbers");
    }
    return x;
}

Result<double, std::string> double_sqrt(double x) {
    auto y = TRY(sqrt(x));
    return sqrt(y);
}

который я считаю действительно опрятным:

  • в отличие от использования кодов ошибок (или вашего Successкласса), забыв проверить ошибки, вы получите ошибку времени выполнения 1, а не случайное поведение,
  • в отличие от использования исключений, на сайте вызовов очевидно, какие функции могут не работать, поэтому нет ничего удивительного.
  • со стандартом C ++ - 2X мы можем получить conceptsв стандарте. Это сделало бы такой вид программирования гораздо более приятным, поскольку мы могли бы оставить выбор над типом ошибки. Например, с реализацией std::vectorкак результат, мы могли бы вычислить все возможные решения одновременно. Или мы могли бы улучшить обработку ошибок, как вы предложили.

1 С правильно инкапсулированной Resultреализацией;)


Примечание: в отличие от исключения, этот легкий Resultне имеет следов, что делает регистрацию менее эффективной; может оказаться полезным, по крайней мере, записать номер файла / строки, в котором генерируется сообщение об ошибке, и вообще написать расширенное сообщение об ошибке. Это может быть составлено путем захвата файла / строки каждый раз, когда используется TRYмакрос, по сути, создания обратной трассировки вручную или использования кода и библиотек, специфичных для платформы, таких как libbacktraceсписок символов в стеке вызовов.


Однако есть одна большая оговорка: существующие библиотеки C ++ и даже stdоснованы на исключениях. Использовать этот стиль будет непросто, поскольку API любой сторонней библиотеки должен быть заключен в адаптер ...

Матье М.
источник
3
Этот макрос выглядит ... очень неправильно. Я предполагаю, что ({...})это какое-то расширение gcc, но не так ли if (!result.ok) return result;? Ваше состояние появляется в обратном направлении, и вы делаете ненужную копию ошибки.
Утка
@MooingDuck Ответ объясняет, что ({...})это выражение выражения gcc .
Джеймсдлин
1
@ bash0r, самый новый документ намного приятнее: scala-lang.org/api/2.12.2/scala/util/Try.html Это en.wikipedia.org/wiki/Tagged_union - scala-lang.org/api/2.12. 2 / scala / util / Either.html
Reactormonk
1
Я бы рекомендовал использовать std::variantдля реализации, Resultесли вы используете C ++ 17. Кроме того, чтобы получить предупреждение, если вы игнорируете ошибку, используйте[[nodiscard]]
Джастин
2
@Justin: использовать std::variantили нет - дело вкуса, учитывая компромиссы в обработке исключений. [[nodiscard]]это действительно чистая победа.
Матье М.
46

ЗАЯВКА: Механизм исключений является языковой семантикой для обработки ошибок.

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

ПРЕТЕНЗИЯ: Для меня "нет оправдания" функции для невыполнения задачи: либо мы неправильно определили предварительные / последующие условия, чтобы функция не могла обеспечить результаты, либо какой-то конкретный исключительный случай не считается достаточно важным для того, чтобы тратить время на разработку решение

Подумайте: я пытаюсь создать файл. Запоминающее устройство заполнено.

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

Итак, должна ли моя программа каким-то образом освободить место и затем продолжить успешно, иначе мне просто лень «разрабатывать решение»? Это кажется откровенно бессмысленным. «Решение» по управлению общим хранилищем выходит за рамки моей программы , и позволить моей программе изящно завершиться с ошибкой и перезапуститься, как только пользователь освободит некоторое пространство или добавит дополнительное хранилище, это нормально .


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

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

Для корректности - и производительности - гораздо лучше вывести контроль из области видимости, если вы не можете добиться прогресса. Исключения и явная проверка ошибок в стиле C с ранним возвратом позволяют это сделать.


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

Я не уверен, что стиль монады и ленивая оценка хорошо переводят на C ++.

Бесполезный
источник
1
Благодаря вашему ответу, это добавляет свет к теме. Я предполагаю, что пользователь не согласился бы, and allowing my program to fail gracefully, and be re-runкогда он только что потерял 2-
Адриан Мэйр
14
Ваше решение означает, что в каждом месте, где вы можете создать файл, вам нужно предложить пользователю исправить ситуацию и повторить попытку. Тогда все остальное , что может пойти не так, нужно также как-то исправить локально. За исключением, вы просто перехватываете std::exceptionлогические операции на более высоком уровне, говорите пользователю «X не удалось из-за ex.what ()» и предлагаете повторить всю операцию, когда и когда они будут готовы.
бесполезно
13
@AdrianMaire: «Разрешение изящного сбоя и повторного запуска» также может быть реализовано как showing the Save dialog again along with an error message and allowing the user to specify an alternative location to try. Это изящная обработка проблемы, которая обычно не может быть выполнена из кода, который обнаруживает, что первое хранилище заполнено.
Барт ван Инген Шенау
3
@Useless Ленивая оценка не имеет ничего общего с использованием монады Error, о чем свидетельствуют строгие языки оценки, такие как Rust, OCaml и F #, которые все интенсивно ее используют.
бит
1
@Useless ИМО по качеству программного обеспечения, это действительно имеет смысл , что «каждое место вы можете создать файл, вам нужно , чтобы побудить пользователя исправить ситуацию и повторить попытку». Ранние программисты часто шли на значительные усилия по исправлению ошибок, по крайней мере, программа Кнута TeX полна ими. И с его структурой «грамотного программирования» он нашел способ сохранить обработку ошибок в другом разделе, чтобы код оставался читаемым, а восстановление после ошибок записывалось с большей осторожностью (потому что, когда вы пишете раздел восстановления после ошибок, в этом-то и дело, и программист стремится лучше работать).
ShreevatsaR
15

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

  • Является ли предпосылка к проблеме правильной? или я пропустил что-то актуальное?
  • Является ли решение хорошей архитектурной идеей? или цена слишком высока?

Ваш подход приносит некоторые большие проблемы в ваш исходный код:

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

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

Но мои несколько лет как разработчика заставляют меня видеть проблему с другого подхода:

Решения этих проблем должны быть найдены на уровне технического лидера или команды:

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

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

Решите проблему, установив автоматическое тестирование, разделив спецификацию модульных тестов и реализации (пусть это делают два разных человека).

Программисты склонны не читать внимательно документацию [...] Более того, даже когда они знают, они на самом деле ими не управляют.

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

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

Правильная обработка ошибок трудна, но менее утомительна с исключениями, чем с возвращаемыми значениями (независимо от того, возвращаются ли они на самом деле или передаются как аргументы ввода / вывода).

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

Чтобы решить эту проблему, необходимо уделять больше внимания идентификации и работе в условиях ошибки (больше тестирования, больше юнит-тестов / интеграционных тестов и т. Д.).

utnapistim
источник
12
Весь код после ошибки пропускается, если вы не забываете проверять каждый раз, когда получаете экземпляр в качестве аргумента . Это то, что я имел в виду, «чем больше кода вы пишете с помощью этого подхода, тем больше кода ошибки вы также должны будете добавить». Вам придется загадывать ваш код с помощью if в экземпляре успеха, и каждый раз, когда вы забываете, это ошибка. Вторая проблема, вызванная забыванием проверки: код, который выполняется до тех пор, пока вы не выполните повторную проверку, вообще не должен был выполняться (продолжение, если вы забыли проверить, повреждает ваши данные).
utnapistim
11
Нет, обработка исключения (или возвращение кода ошибки) не является сбоем - если только ошибка / исключение не является логически фатальной, или вы решили не обрабатывать ее. У вас все еще есть возможность обработать ошибку, без необходимости явно проверять на каждом шаге, была ли ошибка ранее
Бесполезно
11
@AdrianMaire Почти во всех приложениях, над которыми я работаю, я бы предпочел сбой, а не продолжая молча. Я работаю над критически важным для бизнеса программным обеспечением, где получение плохих результатов и продолжение работы с ним может привести к большим потерям денег. Если корректность имеет решающее значение и сбой приемлем, тогда исключения имеют очень большое преимущество.
Крис Хейс
1
@AdrianMaire - я думаю, что гораздо сложнее забыть обработать исключение, чем ваш метод забытия оператора if ... Кроме того, главное преимущество исключений заключается в том, какой слой обрабатывает их. Возможно, вы захотите, чтобы системное исключение вспыхнуло дальше, чтобы отобразить сообщение об ошибке уровня приложения, но обрабатывать ситуации, о которых вы знаете, на более низком уровне. Если вы используете сторонние библиотеки или код других разработчиков, это действительно единственный выбор ...
Милни
5
@ Адриан Нет ошибок, вы, кажется, неправильно поняли то, что я написал, или пропустили вторую половину. Весь мой смысл не в том, что это исключение возникнет при тестировании / разработке, и что разработчики поймут, что им нужно с ними справиться. Дело в том, что последствия полностью необработанного исключения в производстве предпочтительнее, чем следствие неконтролируемого кода ошибки. если вы пропустите код ошибки, вы получите и продолжаете использовать неправильные результаты. Если вы пропустите исключение, сбои приложений и не продолжаете работать, вы не получите никаких результатов не неправильные результатов . (продолжение)
Mr.Mindor