Рассмотрим следующий код:
public Object getClone(Cloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException();
}
else {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//cant be reached, in for syntax
return null;
}
Это return null;
необходимо, поскольку исключение может быть перехвачено, однако в таком случае, поскольку мы уже проверили, было ли оно нулевым (и предположим, что мы знаем, что вызываемый нами класс поддерживает клонирование), поэтому мы знаем, что оператор try никогда не завершится ошибкой.
Является ли плохой практикой помещать дополнительный оператор возврата в конце, чтобы удовлетворить синтаксис и избежать ошибок компиляции (с пояснением, что он не будет достигнут), или есть лучший способ закодировать что-то вроде этого, чтобы дополнительный оператор возврата не нужен?
Object
параметр. Еслиa
это не класс, поддерживающийclone
метод (или это определено вObject
?), Или если во времяclone
метода возникает ошибка (или любая другая ошибка, о которой я не могу сейчас вспомнить), может быть сгенерировано исключение, и вы достигнете заявление о возврате.Cloneable
созданиеCloneNotSupportedException
. Источник: javadocsAssertionError
вместоInternalError
. Последний, кажется, предназначен для специального использования, если что-то в JVM пойдет не так. А вы в основном утверждаете, что код не дошел.Ответы:
Более четкий способ без дополнительного оператора возврата выглядит следующим образом. Я бы не поймала
CloneNotSupportedException
, но отдала звонящему.if (a != null) { try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } throw new TotallyFooException();
Почти всегда можно повозиться с порядком, чтобы получить более простой синтаксис, чем тот, который у вас изначально есть.
источник
AssertionError
- это встроенный класс с аналогичным намерениемTotallyFooException
(которого на самом деле не существует).Это определенно может быть достигнуто. Обратите внимание, что вы печатаете только трассировку стека в
catch
предложении.В сценарии, где
a != null
и будет исключение,return null
будет достигнуто. Вы можете удалить этот оператор и заменить его наthrow new TotallyFooException();
.В общем * , если
null
это действительный результат метода (т.е. пользователь ожидает его и это что-то значит), то возвращать его как сигнал «данные не найдены» или возникло исключение - не лучшая идея. В противном случае я не вижу проблем, почему ты не должен возвращатьсяnull
.Возьмем, к примеру,
Scanner#ioException
метод:В этом случае возвращаемое значение
null
имеет ясный смысл, когда я использую метод, я могу быть уверен, что получилnull
только потому, что не было такого исключения, а не потому, что метод пытался что-то сделать, но он не удался.* Обратите внимание, что иногда вы все же хотите вернуться,
null
даже если значение неоднозначно. НапримерHashMap#get
:В этом случае
null
может указывать, что значениеnull
было найдено и возвращено, или что хэш-карта не содержит запрошенный ключ.источник
Optional<Throwable>
илиOptional<TValue>
соответственно. Примечание: это критика не вашего ответа, а скорее конструкции API этих двух методов. (Однако это довольно распространенная ошибка дизайна, которая распространяется не только на весь Java API, но и, например, на .NET.)null
это не является допустимым значением возврата, возвращениеnull
является еще хуже идея. Другими словами, возвращениеnull
в неожиданной ситуации - плохая идея.null
, тоnull
никогда не может быть «действителен» возвращаемое значение в том смысле , что он должен указать что - то другое , а не нормальный возвращаемое значение. В этом случае его возврат имеет особое значение, и я не вижу ничего плохого в этом дизайне (конечно, пользователь должен знать, как справиться с этой ситуацией).null
это возможное возвращаемое значение, с которым должен иметь дело вызывающий. Это делает его допустимым значением, и у нас здесь то же самое понимание «действительного», поскольку вы описываете «действительный» как «пользователь этого ожидает, и это что-то значит». Но здесь ситуация иная. OP заявляет для каждого комментария кода, что он утверждает, чтоreturn null;
утверждение «не может быть достигнуто», что означает, что возвратnull
неверен.throw new AssertionError();
Вместо этого он должен быть …Я считаю, что
return null
это плохая практика для завершения недоступной ветви. Лучше закинутьRuntimeException
(AssertionError
также было бы приемлемо), чтобы добраться до этой строки, что-то пошло не так, и приложение находится в неизвестном состоянии. Скорее всего, это (как указано выше), потому что разработчик что-то упустил (объекты могут быть непустыми и неклонируемыми).Скорее всего, я не буду использовать,
InternalError
если не очень уверен, что код недоступен (например, послеSystem.exit()
), так как более вероятно, что я сделаю ошибку, чем виртуальная машина.Я бы использовал настраиваемое исключение (например,
TotallyFooException
), только если переход к этой «недоступной строке» означает то же самое, что и везде, где вы вызываете это исключение.источник
AssertionError
может быть разумным выбором добавить событие «не может произойти» .SomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionException
. Очевидно, это расширитсяRuntimeException
.Cloneable
не реализует общедоступныйclone()
метод иclone()
в объекте приведенныйprotected
выше код фактически не компилируется.JerkException
, я не думаю, что это будет оценено по достоинству).Вы поймали,
CloneNotSupportedException
что означает, что ваш код может с этим справиться. Но после того, как вы его поймаете, вы буквально не знаете, что делать, когда дойдете до конца функции, что означает, что вы не можете справиться с этим. Так что вы правы, что в данном случае это запах кода и, на мой взгляд, означает, что вы не должны были его пойматьCloneNotSupportedException
.источник
Я бы предпочел использовать,
Objects.requireNonNull()
чтобы проверить, не равен ли параметр a нулю. Когда вы читаете код, становится ясно, что параметр не должен быть нулевым.И чтобы избежать отмеченных исключений, я бы снова бросил
CloneNotSupportedException
как файлRuntimeException
.Для обоих вы можете добавить красивый текст с намерением, почему этого не должно происходить или быть.
public Object getClone(Object a) { Objects.requireNonNull(a); try { return a.clone(); } catch (CloneNotSupportedException e) { throw new IllegalArgumentException(e); } }
источник
В такой ситуации я бы написал
public Object getClone(SomeInterface a) throws TotallyFooException { // Precondition: "a" should be null or should have a someMethod method that // does not throw a SomeException. if (a == null) { throw new TotallyFooException() ; } else { try { return a.someMethod(); } catch (SomeException e) { throw new IllegalArgumentException(e) ; } } }
Интересно, что вы говорите, что «оператор try никогда не потерпит неудачу», но вы все же потрудились написать оператор,
e.printStackTrace();
который, по вашему мнению, никогда не будет выполнен. Почему?Возможно, ваша вера не так прочна. Это хорошо (на мой взгляд), поскольку ваша вера основана не на написанном вами коде, а на ожидании того, что ваш клиент не нарушит предварительное условие. Лучше программировать публичные методы в защитных целях.
Кстати, ваш код для меня не компилируется. Вы не можете позвонить,
a.clone()
даже если типa
такойCloneable
. По крайней мере, так говорит компилятор Eclipse. Выражениеa.clone()
дает ошибкуЧто бы я сделал для вашего конкретного случая, так это
public Object getClone(PubliclyCloneable a) throws TotallyFooException { if (a == null) { throw new TotallyFooException(); } else { return a.clone(); } }
Где
PubliclyCloneable
определяетсяinterface PubliclyCloneable { public Object clone() ; }
Или, если вам абсолютно необходим тип параметра
Cloneable
, по крайней мере компилируется следующий.public static Object getClone(Cloneable a) throws TotallyFooException { // Precondition: "a" should be null or point to an object that can be cloned without // throwing any checked exception. if (a == null) { throw new TotallyFooException(); } else { try { return a.getClass().getMethod("clone").invoke(a) ; } catch( IllegalAccessException e ) { throw new AssertionError(null, e) ; } catch( InvocationTargetException e ) { Throwable t = e.getTargetException() ; if( t instanceof Error ) { // Unchecked exceptions are bubbled throw (Error) t ; } else if( t instanceof RuntimeException ) { // Unchecked exceptions are bubbled throw (RuntimeException) t ; } else { // Checked exceptions indicate a precondition violation. throw new IllegalArgumentException(t) ; } } catch( NoSuchMethodException e ) { throw new AssertionError(null, e) ; } } }
источник
Cloneable
не объявляетсяclone
общедоступным методом, который не генерирует никаких проверенных исключений. На bugs.java.com/view_bug.do?bug_id=4098033Приведенные выше примеры действительны и очень Java. Однако вот как я бы ответил на вопрос OP о том, как обрабатывать этот возврат:
public Object getClone(Cloneable a) throws CloneNotSupportedException { return a.clone(); }
Нет никакой пользы от проверки,
a
является ли он нулевым. Это будет NPE. Печать трассировки стека также бесполезна. Трассировка стека одинакова независимо от того, где она обрабатывается.Нет никакой пользы в том, чтобы забивать код бесполезными нулевыми тестами и бесполезной обработкой исключений. Удалив утиль, вопрос возврата спорный.
(Обратите внимание, что OP включил ошибку в обработку исключений; вот почему был необходим возврат. OP не ошиблась бы в методе, который я предлагаю.)
источник
Как уже упоминали другие, в вашем случае это фактически не применяется.
Однако, чтобы ответить на вопрос, программы типа Lint наверняка этого не поняли! Я видел, как два разных человека боролись из-за этого в инструкции switch.
switch (var) { case A: break; default: return; break; // Unreachable code. Coding standard violation? }
Один жаловался, что отсутствие перерыва является нарушением стандарта кодирования. Другой жаловался, что он был одним из-за недоступности кода.
Я заметил это, потому что два разных программиста продолжали перепроверять код, добавляя разрыв, затем удаляя, затем добавляя и удаляя, в зависимости от того, какой анализатор кода они запускали в тот день.
Если вы попали в такую ситуацию, выберите одну и прокомментируйте аномалию, что является хорошим тоном, который вы проявили. Это лучший и самый важный вывод.
источник
Это не «просто для удовлетворения синтаксиса». Семантическое требование языка состоит в том, что каждый путь кода приводит к возврату или отклонению. Этот код не соответствует. Если исключение обнаружено, требуется следующий возврат.
Никакой «плохой практики» по этому поводу или по поводу удовлетворения компилятора в целом.
В любом случае, будь то синтаксис или семантика, у вас нет выбора.
источник
Я бы переписал это, чтобы вернуть в конце. Псевдокод:
if a == null throw ... // else not needed, if this is reached, a is not null Object b try { b = a.clone } catch ... return b
источник
Никто еще не упомянул об этом, так что вот оно:
public static final Object ERROR_OBJECT = ... //... public Object getClone(Cloneable a) throws TotallyFooException { Object ret; if (a == null) throw new TotallyFooException(); //no need for else here try { ret = a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); //something went wrong! ERROR_OBJECT could also be null ret = ERROR_OBJECT; } return ret; }
Я не люблю
return
внутренниеtry
блоки именно по этой причине.источник
Если вы знаете подробности о вводимых данных таким образом, чтобы вы знали, что
try
утверждение никогда не может потерпеть неудачу, какой в нем смысл? Избегайтеtry
если вы точно знаете, что всегда все будет успешно (хотя редко можно быть абсолютно уверенным на протяжении всего срока службы вашей кодовой базы).В любом случае компилятор, к сожалению, не умеет читать мысли. Он видит функцию и ее входные данные, и, учитывая имеющуюся информацию, ему нужен этот
return
оператор внизу, как и у вас.Напротив, я бы посоветовал избегать любых предупреждений компилятора, например, даже если это стоит еще одной строчки кода. Не беспокойтесь здесь о количестве строк. Определите надежность функции с помощью тестирования, а затем двигайтесь дальше. Просто представьте, что вы можете пропустить это
return
утверждение, представьте, что вы вернетесь к этому коду через год, а затем попытайтесь решить,return
не вызовет ли этот оператор внизу больше путаницы, чем какой-то комментарий с подробным описанием того, почему он был опущен из-за предположений, которые вы можете сделать о входных параметрах. Скорее всего, сreturn
заявлением будет легче разобраться.Тем не менее, конкретно об этой части:
try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } ... //cant be reached, in for syntax return null;
Я думаю, что здесь есть что-то немного странное с мышлением об обработке исключений. Обычно вы хотите проглатывать исключения на сайте, где у вас есть что-то значимое, что вы можете сделать в ответ.
Вы можете думать о
try/catch
механизме транзакции.try
внесение этих изменений, если они потерпят неудачу и мы перейдем вcatch
блок, сделайте это (что бы ни было вcatch
блоке) в ответ как часть процесса отката и восстановления.В этом случае простая печать трассировки стека с последующим принудительным возвратом null - это не совсем подход к транзакции / восстановлению. Код передает ответственность за обработку ошибок всему коду, вызывающему
getClone
вручную проверку на наличие сбоев. Вы могли бы предпочесть пойматьCloneNotSupportedException
и преобразовать его в другую, более значимую форму исключения и выбросить это, но вы не хотите просто проглатывать исключение и возвращать нуль в этом случае, поскольку это не похоже на сайт восстановления транзакций.В конечном итоге вы передадите вызывающим абонентам ответственность за ручную проверку и устранение сбоев таким образом, когда выброс исключения позволит избежать этого.
Это как если вы загружаете файл, это транзакция высокого уровня. У вас может быть
try/catch
там. В процессеtrying
загрузки файла вы можете клонировать объекты. Если где-то в этой высокоуровневой операции (загрузка файла) происходит сбой, вы обычно хотите, чтобы исключения полностью возвращались в этотtry/catch
блок транзакции верхнего уровня, чтобы вы могли корректно восстановиться после сбоя при загрузке файла (будь то из-за ошибки клонирования или чего-то еще). Таким образом, мы обычно не хотим просто проглотить исключение в таком детализированном месте, как это, а затем вернуть null, например, поскольку это нарушит большую часть значения и цели исключений. Вместо этого мы хотим полностью распространить исключения на сайт, где мы сможем с ними справиться.источник
Ваш пример не идеален для иллюстрации вашего вопроса, как указано в последнем абзаце:
Лучшим примером может быть реализация самого клона:
public class A implements Cloneable { public Object clone() { try { return super.clone() ; } catch (CloneNotSupportedException e) { throw new InternalError(e) ; // vm bug. } } }
Здесь никогда не следует вводить оговорку о перехвате . Тем не менее, синтаксис требует либо выбросить что-то, либо вернуть значение. Поскольку возвращение чего-либо не имеет смысла,
InternalError
используется для обозначения серьезного состояния виртуальной машины.источник
CloneNotSupportedException
это не «VM ошибки» - это совершенно законно дляCloneable
бросить его. Это может представлять собой ошибку в вашем коде и / или в суперклассе, и в этом случае может быть целесообразным заключить ее вRuntimeException
или, возможно,AssertionError
(но неInternalError
). Или вы можете просто уклониться от исключения - если ваш суперкласс не поддерживает клонирование, то вы тоже, поэтомуCloneNotSupportedException
семантически приемлемо.