Cara terbaik untuk mendefinisikan kode kesalahan / string di Java?

118

Saya menulis layanan web di Java, dan saya mencoba mencari cara terbaik untuk menentukan kode kesalahan dan string kesalahan yang terkait . Saya perlu memiliki kode kesalahan numerik dan string kesalahan yang dikelompokkan bersama. Baik kode kesalahan dan string kesalahan akan dikirim ke klien yang mengakses layanan web. Misalnya, ketika SQLException terjadi, saya mungkin ingin melakukan hal berikut:

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

Program klien mungkin akan melihat pesan:

"Kesalahan # 1 telah terjadi: Ada masalah saat mengakses database."

Pikiran pertama saya adalah menggunakan salah Enumsatu kode kesalahan dan mengganti toStringmetode untuk mengembalikan string kesalahan. Inilah yang saya dapatkan:

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
}

Pertanyaan saya adalah: Apakah ada cara yang lebih baik untuk melakukan ini? Saya lebih suka solusi dalam kode, daripada membaca dari file eksternal. Saya menggunakan Javadoc untuk proyek ini, dan dapat mendokumentasikan kode kesalahan secara in-line dan memperbaruinya secara otomatis dalam dokumentasi akan sangat membantu.

William Brendel
sumber
Komentar terlambat tetapi patut disebutkan saya pikir ... 1) Apakah Anda benar-benar memerlukan kode kesalahan di sini untuk pengecualian? Lihat jawaban blabla999 di bawah ini. 2) Anda harus berhati-hati menyampaikan terlalu banyak informasi kesalahan kepada pengguna. Info kesalahan yang berguna harus ditulis ke log server tetapi klien harus diberi tahu seminimal mungkin (misalnya "ada masalah saat masuk"). Ini adalah pertanyaan tentang keamanan dan mencegah penipu mendapatkan pijakan.
wmorrison365

Jawaban:

161

Tentu ada implementasi yang lebih baik dari solusi enum (yang umumnya cukup bagus):

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;
  }
}

Anda mungkin ingin mengganti toString () untuk mengembalikan deskripsi saja - tidak yakin. Bagaimanapun, poin utamanya adalah Anda tidak perlu mengganti secara terpisah untuk setiap kode kesalahan. Perhatikan juga bahwa saya secara eksplisit menentukan kode daripada menggunakan nilai ordinal - ini membuatnya lebih mudah untuk mengubah urutan dan menambah / menghapus kesalahan nanti.

Jangan lupa bahwa ini sama sekali tidak diinternasionalkan - tetapi kecuali klien layanan web Anda mengirimi Anda deskripsi lokal, Anda tetap tidak dapat menginternasionalkan dengan mudah. Setidaknya mereka akan memiliki kode kesalahan untuk digunakan untuk i18n di sisi klien ...

Jon Skeet
sumber
13
Untuk menginternasionalkan, ganti bidang deskripsi dengan kode string yang dapat dicari di bundel sumber daya?
Marcus Downing
@ Marcus: Saya suka ide itu. Saya berkonsentrasi untuk mengeluarkan hal ini, tetapi ketika kita melihat internasionalisasi, saya pikir saya akan melakukan apa yang Anda sarankan. Terima kasih!
William Brendel
@marcus, jika toString () tidak ditimpa (yang tidak perlu), maka kode string hanya bisa menjadi nilai enum toString () yang akan menjadi DATABASE, atau DUPLICATE_USER dalam kasus ini.
rubel
@Jon Skeet! Saya suka solusi ini, bagaimana seseorang dapat menghasilkan solusi yang mudah dilokalkan (atau diterjemahkan dalam bahasa lain, dll.) Berpikir untuk menggunakannya di Android, dapatkah saya menggunakan R.string.IDS_XXXX alih-alih string kode keras di sana?
AB
1
@AB: Setelah Anda mendapatkan enum, Anda dapat dengan mudah menulis kelas untuk mengekstrak sumber daya lokal yang relevan dari nilai enum, melalui file properti atau apa pun.
Jon Skeet
34

Sejauh yang saya ketahui, saya lebih suka mengeksternalisasi pesan kesalahan dalam file properti. Ini akan sangat membantu dalam kasus internasionalisasi aplikasi Anda (satu file properti per bahasa). Ini juga lebih mudah untuk mengubah pesan kesalahan, dan tidak perlu kompilasi ulang dari sumber Java.

Pada proyek saya, umumnya saya memiliki antarmuka yang berisi kode kesalahan (String atau integer, tidak terlalu peduli), yang berisi kunci dalam file properti untuk kesalahan ini:

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

di file properti:

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

Masalah lain dengan solusi Anda adalah pemeliharaan: Anda hanya memiliki 2 kesalahan, dan sudah 12 baris kode. Jadi bayangkan file Enumerasi Anda ketika Anda akan memiliki ratusan kesalahan untuk dikelola!

Romain Linsolas
sumber
2
Saya akan melakukan ini lebih dari 1 jika saya bisa. Hardcode string jelek untuk pemeliharaan.
Robin
3
Menyimpan konstanta String di antarmuka adalah Ide yang buruk. Anda dapat menggunakan enum atau menggunakan konstanta String di kelas akhir dengan konstruktor privat, per paket atau area terkait. Tolong jawab John Skeets dengan Enums. Silakan periksa. stackoverflow.com/questions/320588/…
Anand Varkey Philips
21

Overloading toString () tampaknya agak menjijikkan - yang tampaknya sedikit berlebihan dari penggunaan normal toString ().

Bagaimana dengan:

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; }
}

tampak jauh lebih bersih bagiku ... dan tidak terlalu bertele-tele.

Cowan
sumber
5
Overloading toString () pada objek apa pun (apalagi enum) cukup normal.
cletus
+1 Tidak sefleksibel solusi Jon Skeet, tetapi masih dapat menyelesaikan masalah dengan baik. Terima kasih!
William Brendel
2
Maksud saya toString () paling umum dan berguna digunakan untuk memberikan informasi yang cukup untuk mengidentifikasi objek - ini sering kali menyertakan nama kelas, atau beberapa cara untuk memberi tahu jenis objek secara bermakna. Sebuah toString () yang mengembalikan hanya 'Terjadi kesalahan database' akan mengejutkan dalam banyak konteks.
Cowan
1
Saya setuju dengan Cowan, menggunakan toString () dengan cara ini sepertinya agak 'hackish'. Hanya ledakan cepat untuk uang dan bukan penggunaan normal. Untuk enum, toString () harus mengembalikan nama konstanta enum. Ini akan terlihat menarik di debugger saat Anda menginginkan nilai variabel.
Robin
19

Di pekerjaan terakhir saya, saya membahas lebih dalam versi enum:

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

@Error, @Info, @Warning dipertahankan dalam file kelas dan tersedia saat runtime. (Kami juga memiliki beberapa anotasi lain untuk membantu menjelaskan pengiriman pesan)

@Teks adalah anotasi waktu kompilasi.

Saya menulis pemroses anotasi untuk ini yang melakukan hal berikut:

  • Pastikan tidak ada nomor pesan duplikat (bagian sebelum garis bawah pertama)
  • Sintaks-memeriksa teks pesan
  • Buat file messages.properties yang berisi teks, yang dikunci oleh nilai enum.

Saya menulis beberapa rutinitas utilitas yang membantu mencatat kesalahan, membungkusnya sebagai pengecualian (jika diinginkan) dan sebagainya.

Saya mencoba membuat mereka mengizinkan saya membukanya ... - Scott

Scott Stanchfield
sumber
Cara yang bagus untuk menangani pesan kesalahan. Apakah Anda sudah membuatnya menjadi open source?
bobbel
5

Saya merekomendasikan agar Anda melihat java.util.ResourceBundle. Anda harus peduli tentang I18N, tetapi itu sepadan meskipun tidak. Eksternalisasi pesan adalah ide yang sangat bagus. Saya telah menemukan bahwa sangat berguna untuk dapat memberikan spreadsheet kepada orang-orang bisnis yang memungkinkan mereka untuk menggunakan bahasa yang tepat seperti yang mereka ingin lihat. Kami menulis tugas Ant untuk menghasilkan file .properties pada waktu kompilasi. Itu membuat I18N sepele.

Jika Anda juga menggunakan Spring, jauh lebih baik. Kelas MessageSource mereka berguna untuk hal-hal semacam ini.

duffymo
sumber
4

Hanya untuk terus mencambuk kuda mati khusus ini- kami telah menggunakan kode kesalahan numerik dengan baik ketika kesalahan ditunjukkan kepada pelanggan akhir, karena mereka sering lupa atau salah membaca pesan kesalahan yang sebenarnya tetapi kadang-kadang dapat mempertahankan dan melaporkan nilai numerik yang dapat memberikan Anda petunjuk tentang apa yang sebenarnya terjadi.

telcopro.dll
sumber
3

Ada banyak cara untuk mengatasinya. Pendekatan pilihan saya adalah memiliki antarmuka:

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

public interface IMessage {
    ICode code();
}

Sekarang Anda dapat menentukan sejumlah enum yang menyediakan pesan:

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

Sekarang Anda memiliki beberapa opsi untuk mengubahnya menjadi String. Anda dapat mengompilasi string ke dalam kode Anda (menggunakan anotasi atau parameter konstruktor enum) atau Anda dapat membacanya dari file config / properti atau dari tabel database atau campuran. Yang terakhir adalah pendekatan yang saya sukai karena Anda akan selalu membutuhkan beberapa pesan yang dapat Anda ubah menjadi teks sejak awal (mis. Saat Anda terhubung ke database atau membaca konfigurasi).

Saya menggunakan pengujian unit dan kerangka kerja refleksi untuk menemukan semua jenis yang mengimplementasikan antarmuka saya untuk memastikan setiap kode digunakan di suatu tempat dan bahwa file konfigurasi berisi semua pesan yang diharapkan, dll.

Menggunakan kerangka kerja yang dapat mengurai Java seperti https://github.com/javaparser/javaparser atau yang dari Eclipse , Anda bahkan dapat memeriksa di mana enum digunakan dan menemukan yang tidak digunakan.

Aaron Digulla
sumber
2

Saya (dan tim kami lainnya di perusahaan saya) lebih memilih untuk memberikan pengecualian daripada mengembalikan kode kesalahan. Kode kesalahan harus diperiksa di mana-mana, diedarkan, dan cenderung membuat kode tidak dapat dibaca ketika jumlah kode menjadi lebih besar.

Kelas kesalahan kemudian akan menentukan pesannya.

PS: dan sebenarnya juga peduli dengan internasionalisasi!
PPS: Anda juga dapat mendefinisikan ulang metode kenaikan dan menambahkan logging, pemfilteran, dll. Jika diperlukan (setidaknya di lingkungan, di mana kelas Exception dan teman dapat diperpanjang / diubah)

blabla999
sumber
maaf, Robin, tapi kemudian (setidaknya dari contoh di atas), ini seharusnya menjadi dua pengecualian - "kesalahan database" dan "pengguna duplikat" sangat berbeda sehingga dua subkelas kesalahan yang terpisah harus dibuat, yang masing-masing dapat ditangkap ( satu adalah sistem, yang lainnya adalah kesalahan admin)
blabla999
dan untuk apa kode kesalahan digunakan, jika tidak untuk membedakan antara satu atau pengecualian lainnya? Jadi setidaknya di atas handler, dia persis seperti itu: berurusan dengan kode kesalahan yang diedarkan dan jika diaktifkan.
blabla999
Saya pikir nama pengecualian akan jauh lebih ilustratif dan menggambarkan diri sendiri daripada kode kesalahan. Lebih baik lebih memikirkan untuk menemukan nama pengecualian yang baik, IMO.
duffymo
@ blabla999 ah, sebenarnya pikiranku. Mengapa menangkap pengecualian berbutir kasar dan kemudian menguji "jika kode kesalahan == x, atau y, atau z". Sungguh menyakitkan dan bertentangan dengan arus. Kemudian, Anda juga tidak dapat menangkap pengecualian yang berbeda pada level yang berbeda di tumpukan Anda. Anda harus menangkap di setiap level dan menguji kode kesalahan di setiap level. Itu membuat kode klien jauh lebih bertele-tele ... +1 + lebih jika saya bisa. Karena itu, saya kira kita harus menjawab pertanyaan OP.
wmorrison365
2
Ingatlah, ini untuk layanan web. Klien hanya dapat mengurai string. Di sisi server masih akan ada pengecualian yang memiliki anggota errorCode, yang dapat digunakan dalam respons akhir ke klien.
pkrish
1

Sedikit terlambat tetapi, saya hanya mencari solusi yang bagus untuk diri saya sendiri. Jika Anda memiliki jenis kesalahan pesan yang berbeda, Anda dapat menambahkan pabrik pesan kustom sederhana sehingga Anda dapat menentukan lebih banyak detail dan format yang Anda inginkan nanti.

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: ok, menggunakan enum di sini sedikit berbahaya karena Anda mengubah enum tertentu secara permanen. Saya kira lebih baik mengganti ke kelas dan menggunakan bidang statis, tetapi Anda tidak dapat menggunakan '==' lagi. Jadi saya rasa itu adalah contoh yang baik tentang apa yang tidak boleh dilakukan, (atau melakukannya hanya selama inisialisasi) :)

pprzemek
sumber
1
Setuju sepenuhnya dengan EDIT Anda, mengubah kolom enum saat runtime bukanlah praktik yang baik. Dengan desain ini setiap orang dapat mengedit pesan kesalahan tersebut. Ini sangat berbahaya. Kolom enum harus selalu final.
b3nyc
0

enum untuk kode kesalahan / definisi pesan masih merupakan solusi yang bagus meskipun memiliki masalah i18n. Sebenarnya kita mungkin memiliki dua situasi: kode / pesan ditampilkan kepada pengguna akhir atau integrator sistem. Untuk kasus selanjutnya, I18N tidak diperlukan. Saya pikir layanan web kemungkinan besar adalah kasus selanjutnya.

Jimmy
sumber
0

Menggunakan interfacesebagai konstanta pesan umumnya merupakan ide yang buruk. Ini akan bocor ke program klien secara permanen sebagai bagian dari API yang diekspor. Siapa tahu, programmer klien nanti mungkin mengurai pesan kesalahan itu (publik) sebagai bagian dari program mereka.

Anda akan terkunci selamanya untuk mendukung ini, karena perubahan dalam format string akan / dapat merusak program klien.

Awan Biru
sumber
0

Silakan ikuti contoh di bawah ini:

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;
}

};

Dalam kode Anda, sebut seperti:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());
Chinmoy
sumber
0

Saya menggunakan PropertyResourceBundle untuk menentukan kode kesalahan dalam aplikasi perusahaan untuk mengelola sumber daya kode kesalahan lokal. Ini adalah cara terbaik untuk menangani kode kesalahan daripada menulis kode (mungkin berlaku untuk beberapa kode kesalahan) bila jumlah kode kesalahan sangat besar dan terstruktur.

Lihat dokumen java untuk informasi lebih lanjut tentang PropertyResourceBundle

Mujibur Rahman
sumber