Перебрасывание исключения - утечка абстракции?


12

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

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

Я использую этот метод, чтобы предотвратить утечку абстракции.

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

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

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

Исключения должны быть о причинах, а не о том, кто их бросает . Если ваш код может иметь file_not_found, то он должен бросить file_not_found_exception. Исключения не должны быть специфичными для библиотеки.
Mooing Duck

Ответы:


11

Мои вопросы: будет ли пропускать выброшенное внутреннее исключение как предыдущее исключение утечки абстракции? Если нет, было бы целесообразно просто повторно использовать сообщение предыдущего исключения?

Ответ: «это зависит».

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

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

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


8

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

НО.

Против этого можно привести немало важных аргументов . Наиболее заметно:

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

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


6

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

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

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

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

Но это не про дырявые абстракции, это другая проблема.

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

Например, если DoohickeyDisasterException является классом из фреймворка и ваш вызывающий код выглядит следующим образом, то у вас есть утечка абстракции:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

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

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


2

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.