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

Лучше ли на Java делать все необходимые проверки от вызывающего или в методе, который он вызывает?

Посмотрим на эти два примера.

Во-первых:

try {
    execute(testObj);
} catch(Exception e) {
    //do somethingwith that
}

public void execute(TestObj testObj) throws Exception {
    if (testObj == null){
        throw new Exception("No such object");
    }
    //do something with object
}

Второе:

if (testObj != null){
    execute(testObj);
} else {
    //handle this differently
}

public void execute(TestObj testObj) {
    //do something with object
}

Сейчас не так, если нам нужно проверить "null" или что-то еще. Я хочу знать, какая практика лучше в целом - "проверить, затем сделать" или "сделать, а затем обработать исключение, если произойдет"?

4b9b3361

Ответ 1

Также вы не должны проверять границы между системами.

Каковы границы между системой?

  • Когда вы получаете ввод от пользователя,
  • Когда вы читаете файл, сеть или сокет,
  • При выполнении системных вызовов или межпроцессного взаимодействия
  • В кодах библиотек во всех общедоступных API.
  • и др.

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

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

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

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

Короче говоря, поскольку проверка выполняется только на границах системы, , если у вас действительно есть выбор, следует ли проверять внутри или вне метода, тогда вы, вероятно, не должны там проверяться, так как это не системная граница.

В системной границе обе стороны всегда должны проверять. Вызывающий должен был проверить, что внешний вызов удался, и вызываемому пришлось проверить, что вызывающий абонент дает разумный ввод/аргумент. Игнорирование ошибки в вызывающем абоненте почти всегда является ошибкой. Принцип надежности, всегда проверяйте все, что вы получаете от внешней системы, но отправляете только то, что знаете, что внешняя система может принять точно.

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

TL;DR; проверка сложна.

Ответ 2

Как сказал @LieRyan, вам нужно только проверять границы между системами. Но как только вы попадете в свой собственный код, вам все равно нужно будет обнаруживать неожиданные проблемы, главным образом потому, что:

  • вы (и ваши сотрудники) не совершенны и можете передать null метод, который не может справиться с этим.
  • Если одна строка использует два объекта, а одна - нулевая, NPE не скажет вам, какой из них виноват (System.out.println( "a.getName()" + "b.getName()" ) выбрасывает NPE... есть нуль или b null или оба?)

Четко документируйте, какие методы принимают null в качестве параметров или могут возвращать значение null.
Хороший инструмент, который может вам помочь, если вы используете Eclipse Juno (я думаю, что IntelliJ-Idea имеет что-то подобное) - это включить проверку нулевой проверки. Это позволяет вам писать некоторые аннотации, чтобы сделать проверку времени компиляции нулевой. Это действительно здорово. Вы можете написать что-то вроде

public @NonNull String method(@Nullable String a){
//since a may be null, you need to make the check or code will not compile
    if(a != null){
        return a.toUppercase();        
    }
    return ""; //if you write return null it won't compile, 
     //because method is marked NonNull 
}

У этого также есть хороший @NonNullByDefault, который в основном говорит: "No method не принимает или не возвращает null, если не отмечен @Nullable". Это хороший по умолчанию, он сохраняет ваш код в чистоте и безопасности.

Подробнее... Справка Eclipse

Ответ 3

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

Пример:

public void doSomething(MyObject arg1, MyObject arg2) throw SomeException {
   if (arg1==null || arg2==null) {
       throw new IllegalArgumentException(); //unchecked
   }
   // do something which could throw SomeException
   return;
}

призвание:

try {
    doSomething(arg1, arg2);
} catch (SomeException e) {
    //handle
}

Ответ 4

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

Ответ 5

В соответствии с Joshua Bloch Effective Java Second Edition, item 38 лучше проверить параметры для методов внутри public. Итак, первый подход намного лучше, кроме факта, что в этой ситуации вам нужно в основном бросить NullPointerException, который равен RuntimeException:

// do not need try...catch due to RuntimeException
execute(testObj);

public void execute(TestObj testObj) {
    if (testObj == null){
        throw new NullPointerException("No such object");
    }
    ;//do smth with object
}

Ответ 6

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

Кроме того, использование исключений для управления потоком программы является дорогостоящим по затратам, так как создает эффективность затрат на исключение. Например, в спящем режиме лучше сделать SELECT COUNT(*), чтобы проверить, существует ли строка, чем просмотр, если срабатывает recordNotFoundException и основывается на вашей логике приложения при этом исключении.

Ответ 7

второй подход лучше, потому что его легче читать и поддерживать.

Вы также должны помнить, что исключение исключений иногда только переносит вашу проблему из A в B и не решает проблему.

Ответ 8

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

E.G., если это метод, который записывает данные в файл и получает путь к файлу в качестве входного. Вы можете создать исключение FileNotFound.

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

Ответ 9

По назначению:

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

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

На самом деле, высказывания с исключениями и if-check могут сосуществовать, и я рекомендую его. Но в вашем коде вы должны избегать вызывать исключения как можно больше.


При использовании:

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

If-clause полезно во всех случаях. Фактически, операторы throw часто обернуты в предложение if. Это означает, что вы не хотите (или не можете) справиться с этим условием, вы просто оставите его в отладчике (диспетчере исключений). Или вместо этого вы можете написать свой код, как вам нравится.


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

A (плохой) пример, показывающий, что иногда исключения являются расточительными:

public void method1 () throws Exception1, Exception2 {
    if (condition) {
        throw new Exception1("condition match");
    } else {
        throw new Exception2("condition mismatch");
    }
}

public void method2 () {
    try {
        method1();
    } catch (Exception1 e1) {
        // do something
    } catch (Exception2 e2) {
        // do something
    }
}

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

Ответ 10

Исключения должны использоваться для exceptional conditions; вещи, которые вы не ожидаете. Проверка ввода не является исключительной.

Ответ 11

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

Даже если метод будет использоваться только внутри одного собственного кода, часто бывает полезно проверить параметры перед тем, как делать что-либо с ними, что может негативно повлиять на другой код. Например, предположим, что код использует лениво отсортированный список (т.е. Он хранит список, который может сортироваться или не сортироваться вместе с флагом, который указывает, отсортирован он или нет); при добавлении элемента в список он просто добавляет его в конец и устанавливает флаг "Сортировка требуется". Подпрограмма "добавить элемент" может завершиться "успешно", если ей была дана нулевая ссылка или объект, который нельзя было сравнить с элементами в списке, но добавление такого элемента приведет к сбою последующих операций сортировки. Даже если метод "добавить элемент" не должен заботиться о том, был ли элемент действительным, было бы намного лучше, если бы метод "добавить элемент" выбрал исключение, чем оставить список в состоянии, в котором более поздние операции могут fail (в этот момент, даже если выяснилось, что сортировка завершилась неудачно из-за нулевого элемента в списке, не было бы способа определить, как он туда попал).

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