Решение 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(). Более того, насколько я понимаю, идея непроверенных исключений заключается в том, чтобы сигнализировать о том, что реализация программы неверна, и даже более того, улавливание непроверенных исключений является плохой идеей, поскольку она как исправление плохой программы во время выполнения.

Кто-нибудь, пожалуйста, уточните, есть ли:

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

Обновление

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

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

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

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

Ваша цель - реализовать логику, которая принимает эти 2 String s, преобразует их в int и отображает окно сообщения, в котором говорится: "сумма xxx".

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

  • Место, где вы конвертируете String в int
  • Место, где вы добавляете 2 int s

Основной вопрос моего первоначального сообщения:

Integer.parseInt() ожидает, что правильная строка будет передана. Всякий раз, когда вы передаете плохую строку, это означает, что ваша программа неверна (а не "ваш пользователь идиот" ). Вам нужно реализовать фрагмент кода, в котором, с одной стороны, у вас есть Integer.parseInt() с MUST семантикой, а с другой стороны вам нужно быть в порядке со случаями, когда ввод неверный - СЛЕДУЕТ использовать семантику.

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

Ответ 1

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

ИМХО, выбрасывание непроверенных исключений является приемлемым, если вы прошли параметры мусора.

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

Относительно объявления 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;
}

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

Что касается вариантов 1-3, вы не определяете исключение, потому что вы ожидаете, что ваши абоненты предполагают, что в противном случае ваш код не может потерпеть неудачу, вы определяете исключение, чтобы определить, что происходит в известных условиях отказа, КОТОРЫЕ УНИКАЛЬНЫ ВАМ МЕТОД. То есть если у вас есть метод, который является оберткой вокруг другого объекта, и он генерирует исключение, а затем передает его. Только если исключение является уникальным для вашего метода, вы должны выбросить настраиваемое исключение (frex, в вашем примере, если сумма должна была только возвращать положительные результаты, тогда проверка этого и выдача исключения была бы подходящей, если, с другой стороны, java бросил исключение переполнения вместо обертывания, тогда вы передадите это, не определите его в своей подписи, переименуете его или съедите).

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

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

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

Ответ 4

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

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

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

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

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

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

4. Ваша идея?

Нет вообще.

Ответ 5

Nr 4.

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

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

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

  • Исключение номера формата
  • Исключение переполнения
  • Исключение Null и т.д.

и справиться со всеми этими отдельными

Ответ 9

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

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

Ответ 10

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

Относительно обновления к вопросу "Представьте себе, что это не фреймворк с открытым исходным кодом, который вы должны использовать по какой-то причине. Вы смотрите на подпись метода и думаете -" ОК, он никогда не бросает ". Затем, когда-нибудь, вы Исключено. Это нормально? javadoc вашего метода должен всегда различать поведение вашего метода (pre и post constraints). Подумайте о линиях интерфейсов коллекций, в которых if, если null не разрешен, javadoc говорит, что исключение нулевого указателя будет выбрано, хотя оно никогда не является частью сигнатуры метода.

Ответ 11

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

Ответ 12

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

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

e.g ArrayIndexOutofBound

Также является общим сюрпризом для каждого цикла.

ConcurrentModificationException or something like that

Ответ 13

Как вы говорите о хорошей практике Java, по-моему, это всегда лучше

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

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

  • Не нужно объявлять настраиваемое исключение как "бросает", поскольку оно не отмечено.

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

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

Ответ 14

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

/**
 * @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.lang.Integer:

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

Ответ 15

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

Для синтаксического анализа строк я всегда предпочитаю использовать "регулярные выражения". Пакет java.util.regex должен помочь нам. Таким образом, я закончу что-то вроде этого, которое никогда не вызывает никаких исключений. Мне нужно вернуть специальное значение, если я хочу поймать ошибку:

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 и т.д.) Мы могли бы даже написать более общую процедуру преобразования, к которой мы можем передавать строки, значения defaut return, код REGEX для компиляции, сообщения об ошибках для throw,...

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

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

Ответ 16

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

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

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

Ответ 17

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

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

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

Ответ 18

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

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

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