Найкращий спосіб визначення кодів / рядків помилок на Java?


118

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

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

Клієнтській програмі може бути показано повідомлення:

"Виникла помилка: виникла проблема з доступом до бази даних."

Моя перша думка полягала в тому, щоб використати 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 () не буде переосмислено (що не потрібно), то рядковий код може просто бути значенням enum toString (), яке в цьому випадку буде DATABASE або DUPLICATE_USER.
рубль

@Jon Skeet! Мені подобається це рішення, як можна створити рішення, яке легко локалізувати (або перекласти на інші мови тощо). Думаючи, як використовувати його в Android, чи можу я використовувати R.string.IDS_XXXX замість жорстких кодованих рядків?
AB

1
@AB: Щойно ви отримаєте перерахунок, ви зможете легко написати клас для вилучення відповідного локалізованого ресурсу зі значення enum, через файли властивостей чи будь-що інше.
Джон Скіт

34

Що стосується мене, я вважаю за краще зовнішні повідомлення про помилки у файлах властивостей. Це буде дуже корисно у випадку інтернаціоналізації вашої програми (один файл властивостей на кожній мові). Також простіше змінити повідомлення про помилку, і воно не потребуватиме повторної компіляції джерел Java.

У моїх проектах, як правило, у мене є інтерфейс, який містить коди помилок (String або integer, це не дуже важливо), який містить ключ у файлах властивостей для цієї помилки:

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
Зберігання струнних констант в інтерфейсі - погана ідея. Ви можете використовувати переліки або використовувати струнні константи в останніх класах із приватним конструктором, за пакетом або суміжною областю. Будь ласка, Джон Скітс відповість Енумом. Будь ласка, перевірте. stackoverflow.com/questions/320588/…
Anand Varkey Philips

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 () будь-якими об'єктами (не кажучи вже про перерахунки) - цілком нормально.
клент

+1 Не настільки гнучка, як рішення Джона Скіта, але вона все ще добре вирішує проблему. Дякую!
Вільям Брендель,

2
Я мав на увазі, що toString () найчастіше і корисніше використовується для надання достатньої інформації для ідентифікації об'єкта - він часто включає в себе ім'я класу або якийсь спосіб змістовно вказати тип об'єкта. ToString (), який повертає просто "Виникла помилка бази даних", був би дивовижним у багатьох контекстах.
Кован

1
Я погоджуюся з Cowan, використовуючи toString () таким чином, здається, трохи "хакіш". Просто швидкий удар за долар і не нормальне використання. Для enum, toString () повинен повертати ім'я постійної enum. Це виглядатиме цікаво в налагоджувачі, коли потрібно значення змінної.
Робін

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, який містить текст, введений значенням enum.

Я написав кілька підпрограм утиліти, які допомагали помилкам журналу, обгортали їх як винятки (за бажанням) тощо.

Я намагаюся змусити їх дозволити мені відкрити його ... - Скотт


Хороший спосіб обробки повідомлень про помилки. Ви вже відкрили його?
боббел

5

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

Якщо ви також використовуєте Spring, тим краще. Їх клас MessageSource корисний для подібних речей.


4

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


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, ...);
}

Тепер у вас є кілька варіантів, щоб перетворити їх на Strings. Ви можете скласти рядки у свій код (використовуючи анотації або параметри конструктора enum) або можете прочитати їх з файлу config / властивості або з таблиці бази даних або суміші. Останнє - це мій переважний підхід, оскільки вам завжди знадобляться деякі повідомлення, які ви зможете перетворити на текст дуже рано (тобто під час підключення до бази даних чи читання конфігурації).

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

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


2

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

Потім клас помилок визначатиме повідомлення.

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


Вибачте, Робіне, але тоді (принаймні з наведеного вище прикладу) це повинні бути два винятки - "помилка бази даних" та "дублікат користувача" настільки різні, що слід створити два окремі підкласи помилок, які можна піддавати індивідуальному запису ( одна - система, а інша - помилка адміністратора)
blabla999

і для чого використовуються коди помилок, якщо не розмежовувати той чи інший виняток? Так, принаймні над обробником, він саме такий: робота з кодами помилок, які передаються навколо і якщо вони перемикаються.
blabla999

Я думаю, що назва винятку була б набагато більш наочною і самоописуючою, ніж код помилки. Краще більше роздумувати над тим, щоб відкрити хороші імена виключень, ІМО.
duffymo

@ blabla999 ах, мої думки точно. Навіщо ловити грубозернистий виняток і перевіряти "якщо код помилки == x, або y, або z". Такий біль і йде проти зерна. Тоді також ви не можете вловлювати різні винятки на різних рівнях своєї стеки. Вам доведеться ловити на кожному рівні та перевіряти код помилки на кожному. Це робить код клієнта набагато більш детальним ... +1 + більше, якщо я міг. Однак, мабуть, ми повинні відповісти на питання ОП.
wmorrison365

2
Майте на увазі, це для веб-сервісу. Клієнт може розбирати лише рядки. На стороні сервера все ще залишатимуться винятки, у яких є член CodeCode, який може бути використаний у кінцевій відповіді клієнту.
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)");

EDIT: добре, використання enum тут мало небезпечно, оскільки ви постійно змінюєте певний перелік. Напевно, краще було б перейти до класу та використовувати статичні поля, але ніж ви більше не можете використовувати '=='. Тож я думаю, що це гарний приклад, що не робити, (або робити це лише під час ініціалізації) :)


1
Цілком погоджуйтеся з вашим EDIT, це не є хорошою практикою змінювати поле enum під час виконання. Завдяки такому дизайну кожен може редагувати повідомлення про помилку. Це досить небезпечно. Поля Enum завжди повинні бути остаточними.
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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.