Подтвердить что ты не робот

Является ли активно бросать AssertionError в Java хорошей практикой?

Просматривая Joshua Bloch "Эффективное Java - второе издание", я наткнулся на следующий код на стр. 152:

double apply(double x, double y) {
    switch(this) {
        case PLUS:   return x + y;
        case MINUS:  return x - y;
        case TIMES:  return x * y;
        case DIVIDE: return x / y;
    }
    throw new AssertionError("Unknown op: " + this);
}

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

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

Так что хорошая практика бросить здесь AssertionException, или было бы лучше бросить другую? Если да, то какой из них лучше всего подходит? Может быть IllegalArgumentException?


Изменить для пояснения: Мой вопрос не в том, нужно ли нам здесь Error, но если мы хотим выбросить Exception или Error, какой он должен быть? И хорошая практика активно бросить AssertionError s? В документации говорится, что Thrown указывает, что утверждение не сработало, поэтому у меня возникает ощущение, что мы не должны активно его бросать. Это правильно?


Второе редактирование: ясный вопрос: полезно ли активно перебрасывать AssertionError, или этого следует избегать, даже если это возможно? (Мое предположение, что чтение документов является последним)

4b9b3361

Ответ 1

Я согласен с г-ном Блохом здесь - альтернативы (IllegalArgumentException, IllegalStateException и UnsupportedOperationException) не позволяют правильно передать серьезность проблемы, и вызывающие могут ошибочно пытаться поймать и обработать этот случай. Фактически, если эта линия когда-либо достигнута, указанная программа сломана, и единственная нормальная задача - выйти.

Здесь указывается, что перечисление имеет конечный набор значений, поэтому невозможно достичь строки throw - это произойдет только в том случае, если определение перечисления изменилось без установки этого метода экземпляра. Бросок RuntimeException предполагает, что вызывающий совершил ошибку, когда на самом деле сам метод (и перечислитель) нарушен. Явное повышение AssertionError правильно указывает на то, что инварианты, которые этот метод ожидает, были нарушены.

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

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

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

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

Что касается конкретного вопроса Error или RuntimeException, это не имеет особого значения (оба неконтролируемы и, следовательно, потенциально будут перемещаться по стеке вызовов, не будучи пойманным), но вызывающие абоненты с большей вероятностью попытаются восстановить от a RuntimeException. Сбой приложения в таком случае, как это, является особенностью, потому что в противном случае мы продолжаем запускать приложение, которое (на данный момент) явно неверно. Конечно, менее вероятно, что вызывающие абоненты поймают и обработают AssertionError (или Error или Throwable), но, конечно, абоненты могут делать все, что захотят.

Ответ 2

По-моему, AssertionError было бы неверно использовать здесь.

Из документов, AssertionError расширяет базовый класс Error

Ошибка - это подкласс Throwable, который указывает на серьезные проблемы, которые разумное приложение не должно пытаться поймать.

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

Если что-нибудь здесь, я бы ожидал, что UnsupportedOperationException будет загружен и обработан в другом месте в стеке вызовов.

Брошено, чтобы указать, что запрошенная операция не поддерживается.

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

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

Ответ 3

Что касается ошибок, Java Tutorial утверждает:

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

Кроме того, в Programming With Assertions говорится:

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

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

Я рекомендую использовать new UnsupportedOperationException("Operator " + name() + " is not supported.");, поскольку он лучше описывает проблему на мой взгляд (т.е. разработчик добавил значение перечисления, но забыл реализовать требуемый случай).

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

PLUS {
    double apply(double x, double y) {
        return x + y;
    }
},
MINUS {
    double apply(double x, double y) {
        return x - y;
    }
},
TIMES {
    double apply(double x, double y) {
        return x * y;
    }
},
DIVIDE {
    double apply(double x, double y) {
        return x / y;
    }
};

abstract double apply(double x, double y);

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

Ответ 4

Я бы предпочел

    double apply(double x, double y) {
    switch(this) {
        case PLUS:   return x + y;
        case MINUS:  return x - y;
        case TIMES:  return x * y;
        default: assert this==DIVIDE: return x / y;
    }
}
  • Мы не должны бросать AssertionError, потому что он должен быть зарезервирован для фактических утверждений.
  • Помимо утверждений и некоторых блоков catch, не должно быть ни одного бита кода, который практически невозможно достичь.

Но я бы предпочел fooobar.com/questions/265443/...

Ответ 5

Я думаю, что оба AssertionError или IllegalAE здесь не очень хороши. Ошибка утверждения неверна, как указано в ответе Мэтта. И здесь не все аргументы, они просто передаются методу в неправильной операции this. Поэтому ИАЭ может быть не очень хорошим. Конечно, это вопрос и ответ на основе мнения.

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

Ответ 6

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