Помилка обробки міркувань


31

Проблема:

З давнього часу я переживаю за exceptionsмеханізм, бо відчуваю, що він насправді не вирішує те, що повинен.

СТВЕРДЖЕННЯ: Поза цією темою тривають дебати, і більшість з них намагаються порівняти exceptionsпорівняно з поверненням коду помилки. Це, безумовно, не тут тема.

Намагаючись визначити помилку, я погодився б із CppCoreGuidelines від Bjarne Stroustrup & Herb Sutter

Помилка означає, що функція не може досягти своєї рекламованої мети

ЗАЯВКА: exceptionМеханізм є мовною семантикою для обробки помилок.

СТВЕРДЖЕННЯ: Для мене функція "не має виправдання" для невиконання завдання: або ми неправильно визначили умови до / після, тому функція не може забезпечити результати, або якийсь конкретний винятковий випадок не вважається достатньо важливим для витрачання часу на розробку вирішення. Враховуючи те, що IMO, різниця між нормальним кодом та обробкою коду помилки є (до впровадження) дуже суб'єктивною лінією.

ВИМОГА: Використання винятків для вказівки, коли умова до чи пост не виконується - це ще одна мета exceptionмеханізму, головним чином для налагодження. Я не націлююсь на це використання exceptionsтут.

У багатьох книгах, навчальних посібниках та інших джерелах вони, як правило, показують поводження з помилками як досить об'єктивну науку, що вирішується, exceptionsі вам просто потрібно, щоб catchвони мали надійне програмне забезпечення, здатне відновитися з будь-якої ситуації. Але мої кілька років роботи як розробника змушують мене бачити проблему з іншого підходу:

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

Це має два наслідки:

  1. Помилки, що часто трапляються, виявляються на початку розвитку та налагоджуються (що добре).
  2. Рідкісні винятки не керуються і змушують систему вийти з ладу (з приємним повідомленням журналу) в будинку користувача. Деякі випадки повідомляється про помилку, або навіть навіть.

Враховуючи це, основною метою механізму помилок має бути IMO:

  1. Зробити видимим у коді, коли певний випадок не керується.
  2. Повідомте час виконання проблеми відповідному коду (принаймні абоненту), коли ця ситуація станеться.
  3. Забезпечує механізми відновлення

Основним недоліком exceptionсемантичного механізму обробки помилок є IMO: легко зрозуміти, де a throwзнаходиться у вихідному коді, але абсолютно не очевидно, щоб знати, чи може конкретна функція кинути, дивлячись на декларацію. Це приносить всю проблему, яку я представив вище.

Мова не застосовує та перевіряє код помилки так само суворо, як це стосується інших аспектів мови (наприклад, сильні типи змінних)

Спробу рішення

З метою покращення цього я розробив дуже просту систему обробки помилок, яка намагається поставити обробку помилок на той самий рівень важливості, що і звичайний код.

Ідея така:

  • Кожна (відповідна) функція отримує посилання на successдуже легкий об'єкт, і може встановити його на стан помилки на випадок. Об'єкт дуже легкий, поки не буде збережена помилка з текстом.
  • Функція рекомендується пропустити своє завдання, якщо наданий об'єкт вже містить помилку.
  • Помилка ніколи не повинна перевищувати.

Повний дизайн, очевидно, ретельно враховує кожен аспект (приблизно 10 сторінок), а також як його застосувати до OOP.

Приклад 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 ++), а не обробляти їх. Однак ваша конструкція «Успіх» має такий самий недолік у дизайні. Як винятки, вони просто ігнорують це. Ще гірше: вона більш багатослівна, призводить до каскадної віддачі, і ви навіть не можете "зловити" її за течією.
dagnelies

3
Чому б просто не використати щось на зразок монад? Вони роблять ваші помилки неявними, але вони не будуть мовчати під час бігу. Власне, перше, що я подумав, дивлячись на ваш код, - це "монади, приємно". Погляньте на них.
bash0r

2
Основна причина, чому мені подобаються винятки, - це те, що вони дозволяють виловлювати всі несподівані помилки із заданого блоку коду та послідовно обробляти їх. Так, немає жодних вагомих причин, що код не повинен виконувати своє завдання - "виникла помилка" - це погана причина, але все-таки це відбувається , і коли це трапляється, ви бажаєте зареєструвати причину і відобразити повідомлення або повторити спробу. (У мене є якийсь код, який робить складну взаємодію з перезапуском із віддаленою системою; якщо віддалена система знизиться, я хочу її ввійти та спробувати з самого початку)
user253751

Відповіді:


32

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

Загалом, зрозуміти, що є умова помилки, легко; однак сигналізувати його таким чином, що його не можна обійти, а поводження з ним належним чином (див. рівні безпеки винятку Авраама ) справді важко.

В C помилки сигналізації робляться кодом повернення, який є ізоморфним для вашого рішення.

C ++ запровадив винятки через скорочення такого підходу; а саме, це працює лише в тому випадку, якщо абоненти пам'ятають перевірити, чи сталася помилка чи ні, і жахливо не виходять з ладу. Кожного разу, коли ви виявляєте, що говорите "Це нормально, як і кожного разу ...", у вас виникають проблеми; люди не такі прискіпливі, навіть коли вони піклуються.

Однак проблема полягає в тому, що у винятку є свої проблеми. А саме, невидимий / прихований контрольний потік. Це було призначено: приховати випадок помилки, щоб логіка коду не затуманилася коробкою з помилками. Це робить "щасливий шлях" набагато зрозумілішим (і швидким!), Ціною зробивши шляхи помилок майже непереборними.


Мені цікаво подивитися, як інші мови підходять до цього питання:

  • Java перевірила винятки (і неперевірені),
  • Go використовує коди помилок / паніку,
  • Іржа використовує типи сум / паніку).
  • Мови FP загалом.

У C ++ використовувалася якась форма перевірених винятків, ви, можливо, помітили, що вона застаріла та спрощена до базового noexcept(<bool>): замість того, що функція оголошується, можливо, кидати, або вона ніколи не оголошується. Перевірені винятки дещо проблематичні тим, що їм не вистачає розширюваності, що може спричинити незручні відображення / вкладення. І згортання ієрархій винятків (один із випадків основного використання віртуального успадкування - винятки ...).

Навпаки, Go і Rust застосовують такий підхід, який:

  • помилки повинні бути сигналізовані в діапазоні,
  • виняток слід використовувати для дійсно виняткових ситуацій.

Останнє досить очевидно в тому, що (1) вони називають свої винятки панікою і (2) тут немає ієрархії / складного пункту. Мова не пропонує можливості перевіряти вміст "паніки": немає ієрархії типів, не визначеного користувачем вмісту, просто "ой, все пішло не так, що неможливо відновити".

Це ефективно заохочує користувачів до належного поводження з помилками, поки залишає простий спосіб вирішення проблем у виняткових ситуаціях (наприклад, "зачекайте, я цього ще не реалізував!").

Звичайно, підхід Go, на жаль, дуже схожий на ваш, що ви можете легко забути перевірити помилку ...

... Однак підхід до іржі в основному зосереджений на двох типах:

  • 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. Розповідаючи мові, що у нас є екземпляр для класу type 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 ofяк m.apply(...)і крапки є інстанцією класу, що реалізує функції диспетчеризації. Рядки під case ... ofвиразом - це відповідні функції диспетчеризації, що приносять поля класу безпосередньо в область застосування за назвою. У Nothingствореній нам гілці, Nothingа в Just aгілці ми називаємо єдине значення aі створюємо інше, застосоване до Just ...функції перетворення . Читайте , як: .fanew Just(f(a))

Тепер вони можуть обробляти помилкові обчислення, одночасно абстрагуючи фактичні перевірки помилок. Існують реалізації для інших інтерфейсів, що робить цей вид обчислень дуже потужним. Насправді, Maybeце натхнення для типу Руста Option.


Я б закликав вас замість цього переробити свій Successклас Result. Олександреску насправді запропонував щось по-справжньому близьке, закликав expected<T>, для чого були зроблені стандартні пропозиції .

Я буду дотримуватися іменування Rust і API просто тому, що ... це документовано і працює. Звичайно, у Русті є чудовий ?суфікс-оператор, який зробить код набагато солодшим; в 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;? Ваш стан з’являється назад, і ви робите непотрібну копію помилки.
Mooing Duck

@MooingDuck Відповідь пояснює, що ({...})це вираз gcc .
jamesdlin

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
@ Джустін: Використовувати std::variantчи ні, це дещо питання смаку, враховуючи компроміси навколо обробки винятків. [[nodiscard]]це справді чистий виграш.
Матьє М.

46

ВИМОГА: Механізм винятку - це мовна семантика для обробки помилок

винятки становлять механізм регулювання потоку. Мотивація цього механізму управління потоком полягає в тому, щоб спеціально відокремити обробку помилок від коду обробки помилок, у звичайному випадку обробка помилок дуже повторювана і мало стосується основної частини логіки.

СТВЕРДЖЕННЯ: Для мене функція "не має виправдання" для невиконання завдання: або ми неправильно визначили умови до / після, тому функція не може забезпечити результати, або якийсь конкретний винятковий випадок не вважається достатньо важливим для витрачання часу на розробку вирішення

Поміркуйте: я намагаюся створити файл. Пристрій зберігання заповнений.

Тепер це не є невдачею визначити мої передумови: ви не можете використовувати "обов'язково достатньо місця для зберігання" як попередню умову, оскільки спільне зберігання підлягає расовим умовам, які роблять це неможливим.

Отже, чи повинна моя програма якось звільнити простір, а потім успішно продовжувати, інакше я просто лінивий "розробляти рішення"? Це здається відверто безглуздим. "Рішення" управління спільним сховищем знаходиться поза межами моєї програми , і дозволяє моїй програмі виграшно вийти з ладу, і повторно запуститись, як тільки користувач звільнить деякий простір, або додасть ще трохи сховища, добре .


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

Зауважте також, що вашій mySqrtфункції потрібно повернути значення, навіть якщо воно не вдалося (або попередня функція вийшла з ладу). Отже, ви або повертаєте магічне значення (як NaN), або вводите невизначене значення у свою програму і сподіваєтесь, що нічого не використовує, не перевіряючи стан успіху, який ви провели через виконання.

Для коректності та працездатності набагато краще повернути контроль поза сферою дії, коли ви не зможете досягти жодного прогресу. Виключення та явна перевірка помилок у стилі C із достроковим поверненням - і це.


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

Я не впевнений, що стиль монади та лінива оцінка добре перекладаються на C ++.


1
Завдяки вашій відповіді це додає світла темі. Я думаю, що користувач не погодиться, and allowing my program to fail gracefully, and be re-runколи він просто втратив роботу за два години:
Адріан Мейр

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
@ Невикористане Лениве оцінювання не має нічого спільного з використанням монади помилок, про що свідчать суворі мови оцінювання, такі як Rust, OCaml і F #, які всі з нею сильно використовують.
8bittree

1
@ Без використання IMO для якісного програмного забезпечення, має сенс, що "у кожному місці, де ви можете створити файл, вам потрібно запропонувати користувачеві виправити ситуацію та повторити спробу". Ранні програмісти часто докладали значних зусиль до відновлення помилок, принаймні, програма TeX Knuth повна ними. І завдяки своїй програмі "грамотне програмування" він знайшов спосіб зберегти обробку помилок в іншому розділі, щоб код залишався читабельним, а відновлення помилок записувалося більш уважно (адже коли ви пишете розділ відновлення помилок, у цьому справа, і програміст прагне зробити кращу роботу).
ShreevatsaR

15

Я хотів би краще зрозуміти наслідки використання такої парадигми в проекті:

  • Чи правильна передумова проблеми? чи я пропустив щось відповідне?
  • Чи є рішення гарною архітектурною ідеєю? чи ціна зависока?

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

  • він покладається на клієнтський код, який завжди пам'ятає, щоб перевірити значення s. Це звичайно з використанням кодів повернення для підходу до обробки помилок , і одна з причин того, що в мову були введені винятки: за винятком випадків, якщо ви не працюєте, ви не виходите з ладу.

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

Але мої кілька років роботи як розробника змушують мене бачити проблему з іншого підходу:

До вирішення цих проблем слід підходити на рівні технічного керівництва або на рівні команди:

Програмісти, як правило, спрощують своє завдання, викидаючи винятки, коли конкретний вигляд здається надто рідкісним, щоб його ретельно реалізовувати. Типовими випадками цього є: проблеми з пам'яттю, проблеми з повним диском, пошкоджені проблеми з файлами тощо. Це може бути достатньо, але це не завжди вирішується з архітектурного рівня.

Якщо ви виявляєте, що постійно керуєте всіма винятками, які можуть бути викинуті, то дизайн не годиться; Які помилки отримують, слід вирішувати відповідно до специфікацій проекту, а не відповідно до того, що розробники відчувають як реалізацію.

Адреса, встановивши автоматизоване тестування, відокремивши специфікацію одиничних тестів та реалізацію (майте це зробити дві різні особи).

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

Ви не вирішите це питання, написавши більше коду. Я вважаю, що найкраща ставка - це ретельно застосовані огляди коду.

Програмісти, як правило, не ловлять винятки досить рано, і коли вони це роблять, то це здебільшого реєструватися та кидати далі. (див. перший пункт).

Правильне поводження з помилками є складним, але менш стомлюючим винятками, ніж значеннями повернення (незалежно від того, чи вони насправді повертаються чи передаються як аргументи вводу-виводу).

Найскладніша частина поводження з помилками полягає не в тому, як ви отримуєте помилку, а в тому, щоб переконатися, що ваша програма зберігає стійкий стан при наявності помилок.

Для вирішення цього питання потрібно приділяти більше уваги ідентифікації та запуску в умовах помилок (більше тестування, більше тестів на одиницю / інтеграцію тощо).


12
Весь код після помилки пропускається, якщо ви пам'ятаєте перевіряти кожен раз, коли ви отримуєте екземпляр як аргумент . Це те, що я мав на увазі під тим, «чим більше коду ви пишете при такому підході, тим більше код помилки потрібно буде також додати». Вам доведеться загадати свій код з ifs в екземплярі успіху, і кожен раз, коли ви забудете, це помилка. Друга проблема, викликана забуттям перевірити: код, який виконується, поки ви знову не перевірите, не повинен був виконуватися взагалі (продовжується, якщо ви забудете перевірити, пошкоджує ваші дані).
utnapistim

11
Ні, поводження з винятком (або повернення коду помилки) не є збоєм - якщо тільки помилка / виняток не є логічно фатальною або ви не вирішите її обробляти. Ви все ще маєте можливість вирішувати випадок помилок, не маючи на кожному кроці чітко перевіряти, чи сталася помилка раніше
Марно

11
@AdrianMaire Практично в кожному додатку, над яким я працюю, я б більше віддав перевагу краху над мовчазним продовженням. Я працюю над критично важливим для бізнесу програмним забезпеченням, коли отримання поганого результату та продовження його роботи може призвести до втрати великих грошей. Якщо коректність має вирішальне значення, і збій прийнятний, то тут винятки мають дуже велику перевагу.
Кріс Хейс

1
@AdrianMaire - Я думаю, що набагато складніше забути обробляти виняток, що ваш метод забуття оператора if ... Крім того - головна перевага винятків полягає в тому, який шар обробляє їх. Ви можете дозволити подальшій вимкненням системного винятку, щоб показати повідомлення про помилку на рівні програми, але обробляти ситуації, про які ви знаєте, на нижчому рівні. Якщо ви використовуєте сторонні бібліотеки чи інші коди розробників, це дійсно єдиний вибір ...
Milney

5
@ Adrian Не помиляйтесь, ви, здається, неправильно прочитали те, що я написав або пропустив другу половину цього. Вся моя суть не в тому, що виняток буде кидатися під час тестування / розробки, і що розробники зрозуміють, що їм потрібно впоратися. Річ у тім, що наслідком цілком необробленого винятку у виробництві є кращим, ніж наслідком неконтрольованого коду помилки. якщо ви пропустите код помилки, ви отримуєте і продовжуєте використовувати неправильні результати. Якщо ви пропустите виняток, збої додатків і не продовжуєте працювати, ви не отримаєте ніяких результатів НЕ неправильні результатів . (продовження)
Містер Міндор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.