Лучший способ определить коды ошибок / строки в Java?

118

Я пишу веб-службу на Java и пытаюсь найти лучший способ определения кодов ошибок и связанных с ними строк ошибок . Мне нужно сгруппировать числовой код ошибки и строку ошибки. И код ошибки, и строка ошибки будут отправлены клиенту, обращающемуся к веб-службе. Например, при возникновении SQLException я могу сделать следующее:

// Example: errorCode = 1, 
//          errorString = "There was a problem accessing the database."
throw new SomeWebServiceException(errorCode, errorString);

Клиентской программе может быть показано сообщение:

«Произошла ошибка №1: возникла проблема с доступом к базе данных».

Моя первая мысль заключалась в том, чтобы использовать один Enumиз кодов ошибок и переопределить toStringметоды для возврата строк ошибок. Вот что я придумал:

public enum Errors {
  DATABASE {
    @Override
    public String toString() {
      return "A database error has occured.";
    }
  },

  DUPLICATE_USER {
    @Override
    public String toString() {
      return "This user already exists.";
    }
  },

  // more errors follow
}

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

Уильям Брендель
источник
Поздний комментарий, но стоит упомянуть, что я ... 1) Вам действительно нужны коды ошибок здесь, в исключении? См. Ответ blabla999 ниже. 2) Вы должны быть осторожны, передавая пользователю слишком много информации об ошибках. Полезная информация об ошибках должна быть записана в журналы сервера, но клиенту следует сообщить минимум (например, «возникла проблема при входе в систему»). Это вопрос безопасности и предотвращения проникновения спуферов.
wmorrison365

Ответы:

161

Ну, безусловно, есть лучшая реализация решения enum (что в целом довольно приятно):

public enum Error {
  DATABASE(0, "A database error has occured."),
  DUPLICATE_USER(1, "This user already exists.");

  private final int code;
  private final String description;

  private Error(int code, String description) {
    this.code = code;
    this.description = description;
  }

  public String getDescription() {
     return description;
  }

  public int getCode() {
     return code;
  }

  @Override
  public String toString() {
    return code + ": " + description;
  }
}

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

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

Джон Скит
источник
13
Для интернационализации замените поле описания строковым кодом, который можно найти в пакете ресурсов?
Маркус Даунинг,
@Marcus: Мне нравится эта идея. Я концентрируюсь на том, чтобы выпустить эту вещь на рынок, но когда мы посмотрим на интернационализацию, я думаю, что сделаю то, что вы предложили. Спасибо!
Уильям Брендель,
@marcus, если toString () не переопределяется (что не обязательно), то код строки может быть просто значением перечисления toString (), которое в данном случае будет DATABASE или DUPLICATE_USER.
рубль
@Jon Skeet! Мне нравится это решение, как можно создать решение, которое легко локализовать (или перевести на другие языки и т. Д.). Думая об использовании его в Android, могу ли я использовать R.string.IDS_XXXX вместо жестко закодированных строк?
AB
1
@AB: Хорошо, когда у вас есть перечисление, вы можете легко написать класс для извлечения соответствующего локализованного ресурса из значения перечисления через файлы свойств или что-то еще.
Джон Скит,
34

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

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

public interface ErrorCodes {
    String DATABASE_ERROR = "DATABASE_ERROR";
    String DUPLICATE_USER = "DUPLICATE_USER";
    ...
}

в файле свойств:

DATABASE_ERROR=An error occurred in the database.
DUPLICATE_USER=The user already exists.
...

Еще одна проблема с вашим решением - это ремонтопригодность: у вас всего 2 ошибки и уже 12 строк кода. Итак, представьте свой файл перечисления, когда вам придется управлять сотнями ошибок!

Ромен Линсолас
источник
2
Я бы поднял это больше, чем 1, если бы мог. Жесткое кодирование строк некрасиво для обслуживания.
Робин
3
Хранение строковых констант в интерфейсе - плохая идея. Вы можете использовать перечисления или строковые константы в конечных классах с частным конструктором для каждого пакета или связанной области. Пожалуйста, ответьте Джон Скитс с помощью Enums. Пожалуйста, проверьте. stackoverflow.com/questions/320588/…
Ананд Варки Филипс
21

Перегрузка toString () кажется немного неприятной - это немного похоже на обычное использование toString ().

Что о:

public enum Errors {
  DATABASE(1, "A database error has occured."),
  DUPLICATE_USER(5007, "This user already exists.");
  //... add more cases here ...

  private final int id;
  private final String message;

  Errors(int id, String message) {
     this.id = id;
     this.message = message;
  }

  public int getId() { return id; }
  public String getMessage() { return message; }
}

мне кажется намного чище ... и менее многословным.

Коуэн
источник
5
Перегрузка toString () для любых объектов (не говоря уже о перечислениях) вполне нормально.
cletus
+1 Не такое гибкое, как решение Джона Скита, но все же хорошо решает проблему. Спасибо!
Уильям Брендель,
2
Я имел в виду, что toString () наиболее часто и полезно используется для предоставления достаточной информации для идентификации объекта - он часто включает имя класса или какой-либо способ значимого определения типа объекта. Метод toString (), который возвращает только «Произошла ошибка базы данных», во многих контекстах может вызывать удивление.
Коуэн
1
Я согласен с Коуэном, использование toString () таким способом кажется немного «хакерским». Просто быстрый удар, а не нормальное использование. Для перечисления toString () должен возвращать имя константы перечисления. Это будет интересно смотреться в отладчике, когда вам нужно значение переменной.
Робин
19

На последней работе я немного углубился в версию enum:

public enum Messages {
    @Error
    @Text("You can''t put a {0} in a {1}")
    XYZ00001_CONTAINMENT_NOT_ALLOWED,
    ...
}

@Error, @Info, @Warning сохраняются в файле класса и доступны во время выполнения. (У нас была пара других аннотаций, которые также помогают описать доставку сообщений)

@Text - это аннотация времени компиляции.

Я написал для этого обработчик аннотаций, который делал следующее:

  • Убедитесь, что нет повторяющихся номеров сообщений (часть перед первым подчеркиванием)
  • Синтаксис - проверьте текст сообщения
  • Создайте файл messages.properties, содержащий текст с ключом из значения перечисления.

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

Я пытаюсь убедить их разрешить мне открыть его исходный код ... - Скотт

Скотт Стэнчфилд
источник
Хороший способ обработки сообщений об ошибках. Вы уже открыли исходный код?
bobbel
5

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

Если вы также используете Spring, тем лучше. Их класс MessageSource полезен для такого рода вещей.

duffymo
источник
4

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

telcopro
источник
3

Есть много способов решить эту проблему. Я предпочитаю иметь интерфейсы:

public interface ICode {
     /*your preferred code type here, can be int or string or whatever*/ id();
}

public interface IMessage {
    ICode code();
}

Теперь вы можете определить любое количество перечислений, которые предоставляют сообщения:

public enum DatabaseMessage implements IMessage {
     CONNECTION_FAILURE(DatabaseCode.CONNECTION_FAILURE, ...);
}

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

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

Используя фреймворки, которые могут анализировать Java, например https://github.com/javaparser/javaparser или Eclipse , вы даже можете проверить, где используются перечисления, и найти неиспользуемые.

Аарон Дигулла
источник
2

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

Затем класс ошибки будет определять сообщение.

PS: а собственно еще и забота об интернационализации!
PPS: вы также можете переопределить метод повышения и при необходимости добавить ведение журнала, фильтрацию и т. Д. (По крайней мере, в средах, где классы исключений и их друзья расширяются / изменяются)

blabla999
источник
извините, Робин, но тогда (по крайней мере, из приведенного выше примера) должно быть два исключения - «ошибка базы данных» и «дублирующийся пользователь» настолько совершенно разные, что необходимо создать два отдельных подкласса ошибок, которые можно отловить по отдельности ( один - система, другой - ошибка администратора)
blabla999
и для чего используются коды ошибок, если не различать одно или другое исключение? Так что, по крайней мере, над обработчиком, он именно такой: имеет дело с кодами ошибок, которые передаются и включаются.
blabla999
Я думаю, что имя исключения было бы гораздо более наглядным и самоописывающим, чем код ошибки. Лучше подумать над поиском хороших имен исключений, ИМО.
duffymo
@ blabla999 а, в точности мои мысли. Зачем ловить крупномасштабное исключение, а затем проверять «если errorcode == x, или y, или z». Такая боль и идет вразрез. Кроме того, вы не можете перехватывать разные исключения на разных уровнях вашего стека. Вам придется ловить на каждом уровне и проверять код ошибки на каждом. Это делает клиентский код намного более подробным ... +1 + еще, если можно. Тем не менее, я думаю, мы должны ответить на вопрос ОП.
wmorrison365
2
Имейте в виду, это для веб-сервиса. Клиент может только разбирать строки. На стороне сервера по-прежнему будут генерироваться исключения, имеющие член errorCode, который можно использовать в окончательном ответе клиенту.
pkrish
1

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

public enum Error {
    DATABASE(0, "A database error has occured. "), 
    DUPLICATE_USER(1, "User already exists. ");
    ....
    private String description = "";
    public Error changeDescription(String description) {
        this.description = description;
        return this;
    }
    ....
}

Error genericError = Error.DATABASE;
Error specific = Error.DUPLICATE_USER.changeDescription("(Call Admin)");

РЕДАКТИРОВАТЬ: хорошо, использование перечисления здесь немного опасно, поскольку вы постоянно меняете конкретное перечисление. Я думаю, лучше было бы перейти на класс и использовать статические поля, но вы больше не можете использовать '=='. Думаю, это хороший пример того, чего не следует делать (или делать это только во время инициализации) :)

pprzemek
источник
1
Полностью согласен с вашим РЕДАКТИРОВАНИЕМ, не рекомендуется изменять поле перечисления во время выполнения. Благодаря такому дизайну каждый может редактировать сообщение об ошибке. Это довольно опасно. Поля перечисления всегда должны быть окончательными.
b3nyc
0

enum для определения кода ошибки / сообщения по-прежнему является хорошим решением, хотя у него есть проблемы с i18n. Фактически у нас может быть две ситуации: код / ​​сообщение отображается конечному пользователю или системному интегратору. В последнем случае I18N не нужен. Я думаю, что веб-сервисы - это, скорее всего, более поздний случай.

Джимми
источник
0

С помощью interface качестве константы сообщения обычно плохая идея. Он будет постоянно попадать в клиентскую программу как часть экспортируемого API. Кто знает, что более поздние клиентские программисты могут анализировать эти сообщения об ошибках (публичные) как часть своей программы.

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

Аван Биру
источник
0

Пожалуйста, следуйте приведенному ниже примеру:

public enum ErrorCodes {
NO_File("No file found. "),
private ErrorCodes(String value) { 
    this.errordesc = value; 
    }
private String errordesc = ""; 
public String errordesc() {
    return errordesc;
}
public void setValue(String errordesc) {
    this.errordesc = errordesc;
}

};

В вашем коде назовите это так:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());
Чинмой
источник
0

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

См. Java doc для получения дополнительной информации о PropertyResourceBundle

Муджибур Рахман
источник