Java: как заставить lint учитывать исключение RuntimeException, возникшее при вызове внутренней функции?

Я пишу код Android с помощью Android Studio, и он выполняет автоматическую проверку линта при написании кода.

У меня есть такой фрагмент кода:

Obj fun() {
    Obj o;
    if (SOME_CONDITION) {
        if (SOME_OTHER_CONDITION) {
            o = SOMETHING;
        } else {
            panic();
        }
    } else {
        panic();
    }
    return o;
}

где panic() - еще одна такая функция

void panic() {
    throw new IllegalStateException();
}

Однако программа проверки ворса сообщает об ошибке, которая может быть не инициализирована. По-видимому, при переходе к ветви else выдается IllegalStateException (подкласс RuntimeException), поэтому выполнение прекращается.

Обратите внимание, что инструкция o = SOMETHING; представляет собой упрощенное описание. Фактический код более сложный и содержит другие проверки условий.

Добавление throws RuntimeException (или IllegalStateException) к panic() не имеет никакого значения.

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

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

MD Sayem Ahmed 27.03.2018 23:49

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

Oleg Sklyar 27.03.2018 23:55

@MDSayemAhmed Может быть, я слишком упростил свой случай. Обновленный фрагмент кода может лучше прояснить ситуацию. Ключевым моментом является то, что может быть несколько мест, где ожидается вызов panic, поэтому я не думаю, что размещать чек везде - хорошая идея. GuardClauses может быть хорошим выбором для простых случаев (например, должна быть назначена только ОДНА переменная), но может быть недостаточно хорошим для сложных.

renyuneyun 28.03.2018 01:26

@OlegSklyar Метод panic - это вспомогательная функция, написанная мной, только для того, чтобы обернуть некоторые общие вещи, которые я хотел бы делать при отладке (например, ведение журнала, а затем исключение). Это не просто "просто" выбросить исключение, но да, безусловно. Все дело в том, как упростить простые случаи / быстрое прототипирование, сохранив при этом некоторую возможность отладки. Я думаю, что «от вас не ждут этого» не означает «вы не можете этого сделать», если только это не предусмотрено языком.

renyuneyun 28.03.2018 01:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
232
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Линт правильный. Учтите, что «паника» может быть изменена в будущем или переопределена подклассами, чтобы не генерировать исключение. o будет неинициализирован. Если вы действительно хотите, чтобы lint заткнулся, просто инициализируйте его.

Лучшим вариантом было бы переместить объявление o и оператор return в ветку, где o = something. Это единственное место, где он действительно используется, и он скрывает истинное предназначение кода в других местах. Вместо возврата в конце функции вызовите исключение. Если panic () действительно вызывает исключение, эта строка никогда не будет вызвана, но она должна сделать всех счастливыми.

Фактически, в зависимости от вашего реального кода, вы можете еще больше упростить:

Obj fun() {
    if (SOME_CONDITION && SOME_OTHER_CONDITION) {
            return SOMETHING;
    } 
    panic();
    throw new RuntimeException("We don't expect to get here");
}

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

renyuneyun 28.03.2018 02:15

Один короткий комментарий: добавление throws RuntimeException к определению panic() не мешает lint критиковать это.

renyuneyun 30.03.2018 00:35

Это имеет смысл, потому что все java-функции неявно генерируют исключение RuntimeException. Это в основном определение RuntimeException: любая функция может выбросить его в любое время, без необходимости объявлять его в предложении throws. Добавление исключений RuntimeException не влияет на Java.

Gonen I 31.03.2018 00:16
Ответ принят как подходящий

Локальная переменная должна быть определенно назначена

То, что вы хотите, невозможно. Это не просто правило линтера в Android Studio, это правило языка, обеспечиваемое компилятором. JLS 16 состояний (выделено автором):

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

Что ты делаешь может

Переопределите panic():

RuntimeException panic() {
  return new IllegalStateException();
}

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

...
} else {
    throw panic();
}
...

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

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

Gonen I 28.03.2018 02:12

Ведение журнала и выброс имеет смысл в среде Android, где я не могу собрать сообщение об исключении (и трассировку стека). Я согласен с @GonenI, что затем метод необходимо переименовать (и это все еще немного сложнее, потому что мне все еще нужно записать throw после вызова pacnic, хотя НАМНОГО лучше, чем раньше).

renyuneyun 28.03.2018 02:22

@renyuneyun Почему вы не можете собрать сообщение и трассировку стека?

erickson 28.03.2018 02:29

Ну как можно (если телефон не мой, т.е. не подключается по adb к моему компьютеру)? Некоторые пользователи просто не умеют пользоваться adb.

renyuneyun 28.03.2018 19:38

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

erickson 28.03.2018 19:43

Хотя я все еще жду «ответа» по поводу 1) если то, что я хочу, возможно 2) как этого добиться (если предположить, что это возможно)? Я предпочитаю немного подождать, прежде чем принять наиболее известный ответ (да, этот ответ) о том, «как минимизировать стоимость [реструктуризации и написания вещей], соглашаясь с lint (не зная, можем ли мы изменить его поведение или нет)».

renyuneyun 30.03.2018 00:34

@renyuneyun Я обновил свой ответ, чтобы более точно ответить на ваш вопрос.

erickson 30.03.2018 01:42

@erickson Спасибо за обновление :) Значит, вы имеете в виду, что исключение, вызванное вызовом функции, должно быть «определенно назначено» (переменной)? Но почему явный throws Exception может откладывать сигнал тревоги (линтера)?

renyuneyun 30.03.2018 23:34

@renyuneyun Нет, я имел в виду, что в вашем примере есть пути к return o, которые однозначно не назначают переменную o. Если этот код скомпилирован, он разрешит возврат неопределенного значения при определенных условиях. Когда вы прерываете эти пути, генерируя исключение, компилятор видит, что оставшиеся пути определенно присваивают o, прежде чем он будет возвращен. Это удовлетворяет компилятор.

erickson 31.03.2018 00:07

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

renyuneyun 31.03.2018 01:01

Другие вопросы по теме