Я пишу код 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() не имеет никакого значения.
Как я могу сказать линтеру, что он не ошибется (не перехватив исключение и не выбросив его снова)?
Вы не должны этого делать. Если единственной целью вызова метода является безоговорочное генерирование исключения, вместо этого заставьте его вернуть экземпляр исключения и бросить его в вашу функцию, которая на самом деле должна его генерировать. Кто-то может позже изменить внедоговорную семантику паники, чтобы вызвать ее условно, и ваш метод, использующий панику, получит скрытую ошибку.
@MDSayemAhmed Может быть, я слишком упростил свой случай. Обновленный фрагмент кода может лучше прояснить ситуацию. Ключевым моментом является то, что может быть несколько мест, где ожидается вызов panic, поэтому я не думаю, что размещать чек везде - хорошая идея. GuardClauses может быть хорошим выбором для простых случаев (например, должна быть назначена только ОДНА переменная), но может быть недостаточно хорошим для сложных.
@OlegSklyar Метод panic - это вспомогательная функция, написанная мной, только для того, чтобы обернуть некоторые общие вещи, которые я хотел бы делать при отладке (например, ведение журнала, а затем исключение). Это не просто "просто" выбросить исключение, но да, безусловно. Все дело в том, как упростить простые случаи / быстрое прототипирование, сохранив при этом некоторую возможность отладки. Я думаю, что «от вас не ждут этого» не означает «вы не можете этого сделать», если только это не предусмотрено языком.




Линт правильный. Учтите, что «паника» может быть изменена в будущем или переопределена подклассами, чтобы не генерировать исключение. 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");
}
Опять же, как указано в пересмотренной формулировке вопроса, это всего лишь упрощенный код, предназначенный только для более прямого представления проблемы. Фактический код выполняет намного больше проверок условий и дополнительных вещей (и я думал, что они слишком сложные, поэтому я не копировал его напрямую), поэтому упрощение их, как это, недопустимо (много возвратов, потому что есть много назначений в разных условиях).
Один короткий комментарий: добавление throws RuntimeException к определению panic() не мешает lint критиковать это.
Это имеет смысл, потому что все java-функции неявно генерируют исключение RuntimeException. Это в основном определение RuntimeException: любая функция может выбросить его в любое время, без необходимости объявлять его в предложении throws. Добавление исключений RuntimeException не влияет на Java.
То, что вы хотите, невозможно. Это не просто правило линтера в Android Studio, это правило языка, обеспечиваемое компилятором. JLS 16 состояний (выделено автором):
For every access of a local variable or blank
finalfieldx,xmust 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.
Ведение журнала и выброс имеет смысл в среде Android, где я не могу собрать сообщение об исключении (и трассировку стека). Я согласен с @GonenI, что затем метод необходимо переименовать (и это все еще немного сложнее, потому что мне все еще нужно записать throw после вызова pacnic, хотя НАМНОГО лучше, чем раньше).
@renyuneyun Почему вы не можете собрать сообщение и трассировку стека?
Ну как можно (если телефон не мой, т.е. не подключается по adb к моему компьютеру)? Некоторые пользователи просто не умеют пользоваться adb.
@renyuneyun Обычно исключения регистрируются только тогда, когда они обрабатываются и не передаются вызывающей стороне. Если это поток, который вы не создавали, например поток пользовательского интерфейса, вы все равно можете установить обработчик неперехваченных исключений в этом потоке для ведения журнала. При настройке вашего, вы должны проверить, есть ли он уже, и делегировать ему полномочия после вызова ваших собственных хуков.
Хотя я все еще жду «ответа» по поводу 1) если то, что я хочу, возможно 2) как этого добиться (если предположить, что это возможно)? Я предпочитаю немного подождать, прежде чем принять наиболее известный ответ (да, этот ответ) о том, «как минимизировать стоимость [реструктуризации и написания вещей], соглашаясь с lint (не зная, можем ли мы изменить его поведение или нет)».
@renyuneyun Я обновил свой ответ, чтобы более точно ответить на ваш вопрос.
@erickson Спасибо за обновление :) Значит, вы имеете в виду, что исключение, вызванное вызовом функции, должно быть «определенно назначено» (переменной)? Но почему явный throws Exception может откладывать сигнал тревоги (линтера)?
@renyuneyun Нет, я имел в виду, что в вашем примере есть пути к return o, которые однозначно не назначают переменную o. Если этот код скомпилирован, он разрешит возврат неопределенного значения при определенных условиях. Когда вы прерываете эти пути, генерируя исключение, компилятор видит, что оставшиеся пути определенно присваивают o, прежде чем он будет возвращен. Это удовлетворяет компилятор.
@erickson Понятно. Теперь это имеет большой смысл: вызов функции, которая «может» генерировать исключение, не гарантирует, что выполнение будет прекращено в этот момент (т.е. это исключение будет генерироваться всегда). И это также означает: нет способа аннотировать / отмечать что-то, что "определенно" вызовет исключение (кроме явного исключения).
Если вы действительно уверены, что переход к блоку
elseвсегда будет приводить к появлениюIllegalStateException, то сначала проверьте это условие и выбросьте исключение. Что-то вроде это. Если вы не можете, то другой альтернативой является инициализацияoс помощьюnull.