Современный способ обработки ошибок…


118

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

Избранные исключения по кодам ошибок

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

Но - мне кажется, это противоречит ...

Кодирование к интерфейсам, а не реализации

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

Если только - интерфейс имеет ...

Спецификации исключений

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

Но - это, кажется, предполагает ...

Утечка абстракции

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

Так...

Заключить

Почему исключения предпочтительны, когда они (на мой взгляд) противоречат лучшим практикам программного обеспечения? И, если коды ошибок настолько плохи (и мне не нужно продаваться с пороками кодов ошибок), есть ли другая альтернатива? Каково текущее (или скорое) состояние в области обработки ошибок, которое соответствует требованиям передового опыта, изложенным выше, но не зависит от вызова кода, проверяющего возвращаемое значение кодов ошибок?


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

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

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

1
Утечка здесь заключается в том, что код может обрабатывать исключение, когда он выходит за границы интерфейса. Это очень маловероятно, учитывая, что он недостаточно знает о том, что на самом деле пошло не так. Все, что он знает, это то, что что-то пошло не так, и реализация интерфейса не знала, как с этим бороться. Шансы на то, что он может сделать лучшую работу, невелики, если не сообщать об этом и не прекращать программу. Или игнорировать его, если интерфейс не критичен для правильной работы программы. Детали реализации, которые вы не можете и не должны кодировать в контракте интерфейса.
Ганс Пассант

Ответы:


31

Прежде всего, я бы не согласился с этим утверждением:

Избранные исключения по кодам ошибок

Это не всегда так: например, взгляните на Objective-C (со структурой Foundation). Там NSError является предпочтительным способом обработки ошибок, несмотря на существование того, что разработчик Java назвал бы истинными исключениями: @try, @catch, @throw, класс NSException и т. Д.

Однако это правда, что многие интерфейсы пропускают свои абстракции с исключениями. Я считаю, что это не вина "исключительного" стиля распространения / обработки ошибок. В общем, я считаю, что лучший совет по обработке ошибок заключается в следующем:

Обработать ошибку / исключение на минимально возможном уровне, период

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

Относительно того, должны ли исключения, сгенерированные методом, быть частью его объявления, я полагаю, что они должны: они являются частью контракта, определенного этим интерфейсом: этот метод делает A, или терпит неудачу с B или C.

Например, если класс является синтаксическим анализатором XML, часть его дизайна должна указывать на то, что предоставленный файл XML является просто неправильным. В Java вы обычно делаете это, объявляя исключения, которые ожидаете встретить, и добавляя их в throwsчасть объявления метода. С другой стороны, если один из алгоритмов синтаксического анализа потерпел неудачу, нет никакой причины пропускать это исключение выше необработанным.

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

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

И последнее: некоторые языки, например, Eiffel, имеют другие механизмы для обработки ошибок и просто не включают в себя возможности выбрасывания. Там «исключение» сортировки автоматически возникает, когда постусловие для подпрограммы не выполняется.


12
+1 за отрицание «Избранные исключения по кодам ошибок». Меня учили, что исключения хороши и хороши, если они, на самом деле, являются исключениями, а не правилом. Вызов метода, который выдает исключение, чтобы определить, является ли условие истинным или нет, является крайне плохой практикой.
Нейл

4
@JoshuaDrake: Он определенно не прав. Исключения и gotoочень разные. Например, исключения всегда идут в одном и том же направлении - вниз по стеку вызовов. И, во-вторых, защита себя от неожиданных исключений - та же самая практика, что и DRY - например, в C ++, если вы используете RAII для обеспечения очистки, то она обеспечивает очистку во всех случаях, не только в исключениях, но и во всем обычном потоке управления. Это бесконечно более надежно. try/finallyвыполняет что-то похожее. Когда вы должным образом гарантируете очистку, вам не нужно рассматривать исключения как особый случай.
DeadMG

4
@JoshuaDrake Извините, но Джоэл далеко отсюда. Исключения не совпадают с goto, вы всегда поднимаетесь хотя бы на один уровень вверх по стеку вызовов. А что делать, если уровень выше не может сразу обработать код ошибки? Вернуть еще один код ошибки? Часть проблемы с ошибками заключается в том, что они МОГУТ быть проигнорированы, что приводит к более серьезным проблемам, чем выдача исключения.
Энди

25
-1, потому что я полностью не согласен с «Обработать ошибку / исключение на самом низком уровне» - это неправильно, точка. Разберитесь с ошибками / исключениями на соответствующем уровне. Довольно часто это намного более высокий уровень, и в этом случае коды ошибок являются огромной болью. Причиной предпочтения исключений по сравнению с кодами ошибок является то, что они позволяют вам свободно выбирать, на каком уровне работать с ними, не влияя на уровни между ними.
Майкл Боргвардт

2
@ Джорджио: см. Artima.com/intv/handcuffs.html - особенно страницы 2 и 3.
Майкл Боргвардт,

27

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

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

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail - это функция, которая возвращает значение, заключенное в Maybe. Он работает как обнуляемый указатель, но нотация do гарантирует, что все это будет равно нулю, если любой из a, b или c потерпит неудачу. (и компилятор защищает вас от случайного исключения NullPointerException)

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

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


1
Это действительно здорово. Спасибо за ответ на часть об альтернативах :)
RichK

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

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

8

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

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

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

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


1
Я не понимаю, почему код ошибки может вызвать утечку абстракции. Если вместо нормального возвращаемого значения возвращается код ошибки, и это поведение описано в спецификации функции / метода, то в IMO утечки нет. Или я что-то упускаю?
Джорджио

Если код ошибки является специфическим для реализации, в то время как API должен быть независимым от реализации, то указание и возврат кода ошибки приводит к утечке нежелательных деталей реализации. Типичный пример: API журналирования с реализацией на основе файлов и на основе БД, где у первого может быть ошибка «переполнение диска», а у второго - ошибка «соединение с БД отклонено хостом».
Майкл Боргвардт

Я понимаю, что ты имеешь в виду. Если вы хотите сообщить об ошибках, связанных с реализацией, API не может быть независимым от реализации (ни с кодами ошибок, ни с исключениями). Полагаю, что единственным решением было бы определить код ошибки, не зависящий от реализации, например «ресурс недоступен», или решить, что API не зависит от реализации.
Джорджио

1
@ Джорджио: Да, сообщать об ошибках, связанных с реализацией, сложно с API, не зависящим от реализации. Тем не менее, вы можете сделать это с исключениями, потому что (в отличие от кодов ошибок) они могут иметь несколько полей. Таким образом, вы можете использовать тип исключения для предоставления общей информации об ошибке (ResourceMissingException) и включить в качестве поля код ошибки / сообщение, зависящее от реализации. Лучшее обоих миров :-).
слеське

И кстати, именно это и делает java.lang.SQLException. Он имеет getSQLState(общий) и getErrorCode(зависит от поставщика). Вот если бы у него были правильные подклассы ...
Слёске

5

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

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

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

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


4

Утечка абстракции

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

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

Коды ошибок выше плохих. Они ужасны Вы должны вручную помнить, чтобы проверять и распространять их, каждый раз, для каждого звонка. Для начала, это нарушает DRY и сильно взрывает ваш код обработки ошибок. Это повторение представляет собой гораздо большую проблему, чем любая другая, с которой сталкиваются исключения. Вы никогда не можете молча игнорировать исключение, но люди могут и молчаливо игнорировать коды возврата - определенно плохая вещь.


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

@missingno: Это потому, что, как обычно, Java имеет ужасную реализацию, а не потому, что проверенные исключения по своей сути плохие.
DeadMG

1
@missingno: Какие проблемы могут быть вызваны проверенными исключениями?
Джорджио

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

@missingno: анонимные функции AFAIK в Java являются просто синтаксическим сахаром для анонимных внутренних классов с одним и тем же методом, поэтому я не уверен, что понимаю, почему проверенные исключения будут проблемой (но я признаю, что не очень разбираюсь в этой теме). Да, трудно предугадать, какие исключения вызовет метод, поэтому IMO может быть полезно проверить исключения, чтобы вам не приходилось угадывать. Конечно, вы можете написать плохой код для их обработки, но вы также можете сделать это с непроверенными исключениями. Однако я знаю, что дискуссия довольно сложная, и, честно говоря, я вижу плюсы и минусы в обеих сторонах.
Джорджио

2

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

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

Реализация кодов ошибок является традиционным способом обработки исключений. Это похоже на использование строки против строителя строк.


4
другими словами: реализации бросают подклассы исключений, определенных в API.
Эндрю Кук

2

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

Разрешение вызываемому нарушить ваш поток управления может не принести никакой реальной выгоды, и не может быть никакого значимого способа справиться с этим исключением. Для прямого примера: если кто-то реализует шаблон Observable (на языке, таком как C #, где у вас есть события повсюду, а throwsв определении нет явного ), нет реальной причины позволить Observer нарушать ваш поток управления в случае сбоя, и нет осмысленного способа разобраться со своими вещами (конечно, хороший сосед не должен бросать при наблюдении, но никто не идеален).

Наблюдение выше может быть распространено на любой слабо связанный интерфейс (как вы указали); Я думаю, что на самом деле является нормой, что после ползания 3-6 стековых фреймов неперехваченное исключение может оказаться в разделе кода, который либо:

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

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

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

С теоретической точки зрения, я думаю, что SoC-кошерный способ обработки исключений может быть получен непосредственно из наблюдения, что в большинстве случаев прямой вызывающий абонент заботится только о том, что вы потерпели неудачу, а не почему. Бросок вызываемого абонента, кто-то очень близко выше (2-3 кадра) ловит версию с обновлением, и фактическое исключение всегда погружается в специализированный обработчик ошибок (даже если только трассировка) - это то, где AOP пригодится, потому что эти обработчики скорее всего будет горизонтальным.


1

Избранные исключения по кодам ошибок

  • Оба должны сосуществовать.

  • Вернуть код ошибки, когда вы ожидаете определенного поведения.

  • Возвратите исключение, если вы не ожидали какого-либо поведения.

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

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

Кодирование к интерфейсам не реализации

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

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

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

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

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

Изменится ли что-нибудь, если вместо исключения ваша реализация вернет объект результата? Этот объект будет содержать результаты ваших действий, а также любые ошибки / сбои, если таковые имеются. Затем вы можете опросить этот объект.


1

Утечка абстракции

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

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

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


1

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

С другой стороны, я согласен, что исключения раскрывают детали реализации, которые должны быть скрыты в коде, вызывающем интерфейс. Поскольку априори невозможно определить, какой фрагмент кода может генерировать какие исключения (если они не объявлены в сигнатуре метода, как в Java), с помощью исключений мы вводим очень сложные неявные зависимости между различными частями кода, что против принципа минимизации зависимостей.

Подводя итог:

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

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

Код ошибки является частью контракта между вызывающим абонентом m1 и вызываемым абонентом m2: возможные коды ошибок определены только в интерфейсе m2. За исключением исключений (если вы не используете Java и не объявляете все сгенерированные исключения в сигнатурах методов), у вас есть неявный контракт между вызывающей стороной m1 и всеми методами, которые могут вызываться с помощью m2 рекурсивно. Поэтому, конечно, ошибочно не проверять возвращенный код ошибки, но всегда можно это сделать. С другой стороны, не всегда возможно проверить все исключения, вызванные методом, если вы не знаете, как он реализован.
Джорджио

Во-первых: Вы можете проверить все исключения - просто выполните «Обработку исключений Pokemon» (нужно перехватить их все - т.е. catch Exceptionили даже Throwable, или эквивалент).
слеське

1
На практике, если API спроектирован должным образом, он будет указывать все исключения, которые клиент может обрабатывать осмысленно - их нужно специально отлавливать. Это эквиваленты кодов ошибок). Любое другое исключение означает «внутренняя ошибка», и приложение должно будет закрыть или, по крайней мере, закрыть соответствующую подсистему. Вы можете поймать их, если хотите (см. Выше), но обычно вы просто должны позволить им всплыть. Именно «всплытие» является основным преимуществом использования исключений. Вы все еще можете поймать их дальше или нет, в зависимости от потребностей.
слеське

-1

Право предвзятый Либо.

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

Даунсайд? Код с правильной обработкой ошибок выглядит отвратительно (верно для всех механизмов).


Похоже, это не дает ничего существенного по поводу высказанных и объясненных в предыдущих 10 ответах вопросов. Зачем натыкаться на двухлетний вопрос с такими вещами
gnat

За исключением того, что здесь никто не упомянул о предвзятости. Хугомг близко подошел к разговору о haskell, однако, возможно, это дерьмовый обработчик ошибок, поскольку он не оставляет объяснения причин возникновения ошибки или какого-либо прямого метода ее восстановления, а обратные вызовы являются одним из величайших грехов в разработке потока управления. И этот вопрос возник в Google.
Кейнан
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.