Решение True-way в Java: проанализируйте 2 числа из 2 строк, а затем верните их сумму


довольно глупый вопрос. Учитывая код:

public static int sum(String a, String b) /* throws? WHAT? */ {
  int x = Integer.parseInt(a); // throws NumberFormatException
  int y = Integer.parseInt(b); // throws NumberFormatException
  return x + y;
}

не могли бы вы сказать, хорошо ли это Java или нет? То, о чем я говорю,NumberFormatException это исключение непроверенное. Ты пока нет чтобы указать его как часть sum() подпись. Более того, насколько я понимаю, идея непроверенных исключений-это просто сигнал о том, что реализация программы неверна, и даже более того, ловить непроверенные исключения-плохая идея, так как это похоже на исправление плохой программы во время выполнения.

кто-нибудь, пожалуйста, пояснить:

  1. я должен указать NumberFormatException как часть сигнатуры метода.
  2. я должен определить свое собственное проверенное исключение (BadDataException), ручка NumberFormatException внутри метода и повторно бросить его как BadDataException.
  3. я должен определить свое собственное проверенное исключение (BadDataException), проверить обе строки каким-то образом, как регулярные выражения и бросить мой BadDataException если это не соответствует.
  4. ваш идея?

обновление:

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

обновление 2:

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

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

ваша цель-реализовать логику, которая принимает эти 2 Strings, преобразует их в ints и отображает окно сообщения с надписью "сумма xxx".

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

  1. место, где вы конвертируете String до int
  2. место, где вы добавляете 2 ints

основной вопрос моего оригинального поста:

Integer.parseInt() ждет правильно строка для передачи. Всякий раз, когда вы проходите плохая строку, это означает, что программа некорректно (не "ваш - идиот"). Вам нужно реализовать кусок кода, где с одной стороны у вас есть целое число.parseInt() с должны семантика и с другой стороны, вы должны быть в порядке со случаями, когда ввод неверен - должна семантика.

Итак, кратко: как реализовать должна семантика если бы у меня только обязательно библиотеки.

18 84

18 ответов:

это хороший вопрос. Я бы хотел, чтобы больше людей думали о таких вещах.

IMHO, выбрасывание непроверенных исключений допустимо, если вам были переданы параметры мусора.

вообще говоря, вы не должны бросать BadDataException потому что вы не должны использовать исключения для управления потоком программы. Исключения - для исключительных. Вызывающие к вашему методу могут знать до они называют это, если их строки являются числами или нет, поэтому передача мусора в is избежать и поэтому можно считать Программирование ошибка, что означает, что это нормально, чтобы бросить неотмеченные исключения.

об объявлении throws NumberFormatException - это не так полезно, потому что мало кто заметит из-за того, что NumberFormatException не установлен. Однако IDE может использовать его и предложить обернуть в try/catch правильно. Хорошим вариантом является использование javadoc, например:

/**
 * Adds two string numbers
 * @param a
 * @param b
 * @return
 * @throws NumberFormatException if either of a or b is not an integer
 */
public static int sum(String a, String b) throws NumberFormatException {
    int x = Integer.parseInt(a); 
    int y = Integer.parseInt(b); 
    return x + y;
}

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

если метод будет использоваться повсюду, и важно, чтобы все вызывающие абоненты обрабатывали проблемы, объявите метод как бросающий проверенное исключение (заставляя вызывающих абонентов иметь дело с проблемами), но загромождая код с try/catch блоки.

если с другой стороны мы используем этот метод с данными, которым мы доверяем, то объявите его, как указано выше, потому что он никогда не взорвется и вы избегаете беспорядка кода по существу ненужного try/catch блоки.

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

 public static int sum(int a, int b);

С вашей сигнатурой метода я бы не изменить что-либо. Либо ты

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

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

номер 4. Как указано, этот метод не должен принимать строки в качестве параметров, он должен принимать целые числа. В этом случае (поскольку Java обертывает вместо переполнения) нет никакой возможности исключения.

 x = sum(Integer.parseInt(a), Integer.parseInt(b))

намного понятнее, что имеется в виду, чем x = sum(a, b)

вы хотите, чтобы исключение произошло как можно ближе к источнику (входу).

что касается опций 1-3, вы не определяете исключение, потому что вы ожидаете, что ваши абоненты предполагают, что в противном случае ваш код не может завершиться ошибкой, вы определяете исключение, чтобы определить, что происходит при известных условиях сбоя, которые уникальны для вашего метода. Т. е. если у вас есть метод, который является оболочкой вокруг другого объекта, и он выдает исключение, то передайте его. Только если исключение уникально для вашего метода, вы должны создать пользовательское исключение (frex, в вашем примере, если sum должна была возвращать только положительные результаты, то проверка этого и создание исключения было бы уместно, если на другая рука java бросила исключение переполнения вместо обертывания, тогда вы передали бы это, не определяя его в своей подписи, переименовывая его или съедая его).

обновление в ответ на обновленный вопрос:

Итак, кратко: как я могу реализовать семантику SHOULD, если у меня есть только библиотеки MUST.

решение этой проблемы заключается в том, чтобы обернуть библиотеку MUST и вернуть значение SHOULD. В этом случае функция, которая возвращает целое число. Написать функция, которая принимает строку и возвращает целочисленный объект-либо она работает, либо она возвращает null (например, Ints guava.tryParse). Сделайте свою проверку отдельно от своей операции, ваша операция должна принимать ints. Будет ли ваша операция вызываться со значениями по умолчанию, когда у вас есть недопустимый ввод, или вы делаете что-то еще, будет зависеть от ваших спецификаций-большинство я могу сказать об этом, что это действительно маловероятно, что место для принятия этого решения находится в методе операции.

1. Я должен указать NumberFormatException как часть сигнатуры метода.

Я так думаю. Это хорошая документация.

2. Я должен определить свое собственное проверенное исключение (BadDataException), обработать NumberFormatException внутри метода и повторно бросить его как BadDataException.

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

3. Я должен определить свое собственное проверенное исключение (BadDataException), проверить обе строки каким-то образом, как регулярные выражения, и бросить мое исключение BadDataException, если оно не соответствует.

никогда. Больше работы, меньше скорости (если вы не ожидаете, что исключение будет правилом), и никакого выигрыша вообще.

4. Твоя идея?

вообще нет.

Nr 4.

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

Я бы точно не сделал #3, поскольку я считаю это излишним.

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

  1. сначала вам нужно спросить себя, Должен ли пользователь моего метода беспокоиться о вводе неправильных данных или от него ожидается ввод правильных данных (в этом случае строка). Это ожидание также известно как дизайн от контракт.

  2. и 3. Да, вы, вероятно, должны определить BadDataException или даже лучше использовать некоторые из вырезающих, таких как NumberFormatException, а скорее оставить стандартное сообщение для показа. Поймать NumberFormatException в методе и повторно бросить его с сообщением, не забывая включить исходную трассировку стека.

  3. Это зависит от ситуации, но я бы, вероятно, пошел с повторным выбросом NumberFormatException с некоторой дополнительной информацией. А также должно быть объяснение javadoc того, что являются ожидаемыми значениями для String a, String b

во многом зависит от сценария, в котором вы находитесь.

корпус 1. Его всегда вы, кто отлаживать код и никто другой и исключение не вызовет плохой пользовательский опыт

бросьте значение по умолчанию NumberFormatException

Case2: код должен быть чрезвычайно ремонтопригодным и понятным

определите свое собственное исключение и добавьте намного больше данных для отладки, бросая его.

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

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

  1. исключение числового формата
  2. исключение переполнения
  3. исключение Null и т. д...

и разобраться со всем этим отдельно

  1. вы можете сделать так, чтобы было ясно, что это может произойти при неправильном вводе. Это может помочь кому-то, кто использует ваш код, чтобы помнить об обработке этой ситуации. Более конкретно, вы даете понять, что вы не обрабатываете его в коде самостоятельно или вместо этого возвращаете какое-то конкретное значение. Конечно, javadoc должен сделать это ясно.
  2. только если вы хотите заставить вызывающего абонента работать с проверенным исключением.
  3. это кажется излишним. Полагайтесь на парсинг, чтобы обнаружения плохих данных.

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

любое исключительное поведение должно быть разъяснено в документации. Либо он должен заявить, что этот метод возвращает специальное значение в случае неудачи (например,null, изменив тип возврата на Integer) или следует использовать случай 1. Наличие его явного в сигнатуре метода позволяет пользователю игнорировать его, если он обеспечивает правильные строки другими средствами, но по-прежнему очевидно, что метод сам по себе не обрабатывает этот вид сбоя.

ответ на ваш обновленный вопрос.

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

e.g ArrayIndexOutofBound

также общее исключение неожиданности из каждого цикла.

ConcurrentModificationException or something like that

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

Что касается обновления к вопросу " представьте себе, это не с открытым исходным кодом основы, которые вы должны использовать по какой-то причине. Вы смотрите на подпись метода и думаете - "хорошо, он никогда не бросает". А потом, в один прекрасный день, вы получили исключение. Это нормально?"javadoc вашего метода всегда должен выплескивать поведение вашего метода (до и после ограничений). Подумайте о строках say Collection interfaces, где, если null не разрешен, javadoc говорит, что исключение нулевого указателя будет брошено, хотя оно никогда не является частью сигнатуры метода.

Как вы говорите о хорошей практике java, на мой взгляд, это всегда лучше

  • чтобы обработать непроверенное исключение, проанализируйте его и через пользовательское непроверенное исключение.

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

  • нет необходимости объявлять пользовательское исключение как " броски" как это непроверено один.

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

  • также правильное документирование в java-doc является хорошей практикой и очень помогает.

Я думаю, что это зависит от вашей цели, но я бы задокументировал ее как минимум:

/**
 * @return the sum (as an int) of the two strings
 * @throws NumberFormatException if either string can't be converted to an Integer
 */
public static int sum(String a, String b)
  int x = Integer.parseInt(a);
  int y = Integer.parseInt(b);
  return x + y;
}

или возьмите страницу из исходного кода Java для java.ленг.Целочисленный класс:

public static int parseInt(java.lang.String string) throws java.lang.NumberFormatException;

Как насчет шаблона проверки ввода, реализованного библиотека Google 'Guava' или библиотека "валидатор" Apache (сравнение)?

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

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

Я думаю, что ваше самое первое предложение "довольно глупый вопрос" очень актуально. Зачем вам вообще писать метод с такой подписью? Есть ли вообще смысл суммировать две строки? Если вызывающий метод хочет суммировать две строки, он должен убедиться, что они являются допустимыми ints и преобразовать их перед вызовом метода.

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

  1. установите сообщение об ошибке и остановите обработку или перенаправление на страницу ошибки
  2. Return false (т. е. он поместил бы сумму в какой-то другой объект и не требовал бы ее возврата)
  3. бросьте некоторые BadDataException, как вы предлагаете, но если суммирование этих двух чисел не очень важно, это излишне, и, как упоминалось выше, это, вероятно, плохой дизайн, поскольку он подразумевает, что преобразование выполняется в неправильном месте

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

для разбора строк, я всегда предпочитаю использовать "регулярные выражения". Ява.утиль.пакет регулярных выражений, чтобы помочь нам. Так что я в конечном итоге с чем-то подобным, что никогда не бросает исключения. Это до меня, чтобы вернуть специальное значение, если я хочу поймать какую-то ошибку :

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public static int sum(String a, String b) {
  final String REGEX = "\d"; // a single digit
  Pattern pattern = Pattern.compile(REGEX);
  Matcher matcher = pattern.matcher(a);
  if (matcher.find()) { x = Integer.matcher.group(); }
  Matcher matcher = pattern.matcher(b);
  if (matcher.find()) { y = Integer.matcher.group(); }
  return x + y;
}

Как видно, код немного длиннее, но мы можем обрабатывать то, что хотим (и установить по умолчанию значения для x и y, контролируют, что происходит с предложениями else и т. д...) Мы могли бы даже написать более общую процедуру преобразования, в которую мы можем передавать строки, дефаут возвращаемые значения, код регулярных выражений для компиляции, сообщения об ошибках, чтобы бросить,...

надеюсь, что это было полезно.

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

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

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

дело в том, что семантика Integer.parseInt() известны-это основная цель его разобрать действительный целых чисел. Вам не хватает явного пользователя шаг проверки ввода/синтаксического анализа.