Cách tốt nhất để xác định mã / chuỗi lỗi trong Java?


118

Tôi đang viết một dịch vụ web bằng Java và tôi đang cố gắng tìm ra cách tốt nhất để xác định mã lỗi và chuỗi lỗi liên quan của chúng . Tôi cần có mã lỗi số và chuỗi lỗi được nhóm lại với nhau. Cả mã lỗi và chuỗi lỗi sẽ được gửi đến máy khách đang truy cập dịch vụ web. Ví dụ: khi một SQLException xảy ra, tôi có thể muốn làm như sau:

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

Chương trình khách hàng có thể được hiển thị thông báo:

"Lỗi # 1 đã xảy ra: Đã xảy ra sự cố khi truy cập cơ sở dữ liệu."

Suy nghĩ đầu tiên của tôi là sử dụng một Enumtrong các mã lỗi và ghi đè các toStringphương thức để trả về các chuỗi lỗi. Đây là những gì tôi nghĩ ra:

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
}

Câu hỏi của tôi là: Có cách nào tốt hơn để làm điều này không? Tôi thích một giải pháp trong mã hơn là đọc từ một tệp bên ngoài. Tôi đang sử dụng Javadoc cho dự án này và có thể ghi lại các mã lỗi trong dòng và tự động cập nhật chúng trong tài liệu sẽ rất hữu ích.


Nhận xét muộn nhưng đáng đề cập Tôi điều ... 1) Bạn có thực sự cần mã lỗi ở đây trong trường hợp ngoại lệ? Xem câu trả lời blabla999 bên dưới. 2) Bạn nên cẩn thận chuyển lại quá nhiều thông tin lỗi cho người dùng. Thông tin lỗi hữu ích phải được ghi vào nhật ký máy chủ nhưng khách hàng phải được thông báo ở mức tối thiểu nhất (ví dụ: "đã xảy ra sự cố khi đăng nhập"). Đây là một câu hỏi về bảo mật và ngăn chặn những kẻ giả mạo có được chỗ đứng.
wmorrison365

Câu trả lời:


161

Chà chắc chắn có một cách triển khai giải pháp enum tốt hơn (nói chung là khá tốt):

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

Bạn có thể muốn ghi đè toString () để chỉ trả lại mô tả thay thế - không chắc chắn. Dù sao, điểm chính là bạn không cần phải ghi đè riêng cho từng mã lỗi. Cũng lưu ý rằng tôi đã chỉ định rõ ràng mã thay vì sử dụng giá trị thứ tự - điều này giúp bạn dễ dàng thay đổi thứ tự và thêm / xóa lỗi sau này.

Đừng quên rằng điều này hoàn toàn không được quốc tế hóa - nhưng trừ khi ứng dụng khách dịch vụ web của bạn gửi cho bạn mô tả ngôn ngữ, bạn không thể dễ dàng tự quốc tế hóa nó. Ít nhất họ sẽ có mã lỗi để sử dụng cho i18n ở phía máy khách ...


13
Để quốc tế hóa, hãy thay thế trường mô tả bằng một mã chuỗi có thể được tra cứu trong một gói tài nguyên?
Marcus Downing

@Marcus: Tôi thích ý tưởng đó. Tôi đang tập trung để đưa điều này ra khỏi cửa, nhưng khi chúng tôi nhìn vào quốc tế hóa, tôi nghĩ tôi sẽ làm theo những gì bạn đề xuất. Cảm ơn!
William Brendel

@marcus, nếu toString () không bị overrriden (không cần phải như vậy), thì mã chuỗi có thể chỉ là giá trị enum toString () sẽ là DATABASE hoặc DUPLICATE_USER trong trường hợp này.
rúp

@Jon Skeet! Tôi thích giải pháp này, làm thế nào người ta có thể tạo ra một giải pháp dễ bản địa hóa (hoặc dịch sang các ngôn ngữ khác, v.v.) Nghĩ về việc sử dụng nó trong Android, tôi có thể sử dụng R.string.IDS_XXXX thay vì các chuỗi mã cứng ở đó không?
AB

1
@AB: Khi bạn đã có enum, bạn có thể dễ dàng viết một lớp để trích xuất tài nguyên bản địa hóa có liên quan từ giá trị enum, thông qua các tệp thuộc tính hoặc bất cứ thứ gì.
Jon Skeet

34

Theo những gì tôi có liên quan, tôi muốn ngoại hóa thông báo lỗi trong tệp thuộc tính. Điều này sẽ thực sự hữu ích trong trường hợp quốc tế hóa ứng dụng của bạn (một tệp thuộc tính cho mỗi ngôn ngữ). Việc sửa đổi thông báo lỗi cũng dễ dàng hơn và nó sẽ không cần biên dịch lại các nguồn Java.

Trên các dự án của tôi, nói chung tôi có một giao diện chứa mã lỗi (Chuỗi hoặc số nguyên, nó không quan tâm lắm), chứa khóa trong tệp thuộc tính cho lỗi này:

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

trong tệp thuộc tính:

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

Một vấn đề khác với giải pháp của bạn là khả năng bảo trì: bạn chỉ có 2 lỗi và đã có 12 dòng mã. Vì vậy, hãy tưởng tượng tệp Enumeration của bạn khi đó bạn sẽ có hàng trăm lỗi cần quản lý!


2
Tôi sẽ lên cái này nhiều hơn 1 nếu tôi có thể. Mã hóa cứng các dây là xấu để bảo trì.
Robin

3
Lưu trữ các hằng số chuỗi trong giao diện là một ý tưởng tồi. Bạn có thể sử dụng enum hoặc sử dụng hằng số chuỗi trong các lớp cuối cùng với phương thức khởi tạo riêng, trên mỗi gói hoặc vùng liên quan. Hãy John Skeets trả lời với Enums. Hãy kiểm tra. stackoverflow.com/questions/320588/…
Anand Varkey Philips

21

Quá tải toString () có vẻ hơi khó khăn - điều đó có vẻ hơi xa cách sử dụng bình thường của toString ().

Thế còn:

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

có vẻ sạch sẽ hơn rất nhiều đối với tôi ... và ít dài dòng hơn.


5
Việc ghi đè toString () lên bất kỳ đối tượng nào (chưa nói đến enums) là điều khá bình thường.
cletus

+1 Không hoàn toàn linh hoạt như giải pháp của Jon Skeet, nhưng nó vẫn giải quyết vấn đề một cách độc đáo. Cảm ơn!
William Brendel

2
Ý tôi là toString () được sử dụng phổ biến và hữu ích nhất để cung cấp đủ thông tin để xác định đối tượng - nó thường bao gồm tên lớp hoặc một số cách để cho biết loại đối tượng một cách có ý nghĩa. Một toString () chỉ trả về 'Đã xảy ra lỗi cơ sở dữ liệu' sẽ gây ngạc nhiên trong nhiều ngữ cảnh.
Cowan

1
Tôi đồng ý với Cowan, sử dụng toString () theo cách này có vẻ hơi 'hackish'. Chỉ là một tiếng nổ nhanh chóng cho buck và không phải là một cách sử dụng bình thường. Đối với enum, toString () sẽ trả về tên của hằng số enum. Điều này trông sẽ thú vị trong trình gỡ lỗi khi bạn muốn giá trị của một biến.
Robin

19

Ở công việc cuối cùng của tôi, tôi đã đi sâu hơn một chút trong phiên bản enum:

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

@Error, @Info, @Warning được giữ lại trong tệp lớp và có sẵn trong thời gian chạy. (Chúng tôi cũng có một số chú thích khác để giúp mô tả việc gửi thông điệp)

@Text là một chú thích thời gian biên dịch.

Tôi đã viết một bộ xử lý chú thích cho việc này đã làm như sau:

  • Xác minh rằng không có số tin nhắn trùng lặp (phần trước dấu gạch dưới đầu tiên)
  • Cú pháp-kiểm tra văn bản tin nhắn
  • Tạo một tệp tin messages.properties có chứa văn bản, được khóa bằng giá trị enum.

Tôi đã viết một vài quy trình tiện ích giúp ghi lại lỗi, gói chúng thành ngoại lệ (nếu muốn), v.v.

Tôi đang cố gắng để họ cho phép tôi mã nguồn mở nó ... - Scott


Cách tốt để xử lý các thông báo lỗi. Bạn đã mở nguồn nó?
bobbel

5

Tôi khuyên bạn nên xem qua java.util.ResourceBundle. Bạn nên quan tâm đến I18N, nhưng nó đáng giá ngay cả khi bạn không. Bên ngoài các thông điệp là một ý tưởng rất hay. Tôi thấy rằng rất hữu ích khi có thể cung cấp một bảng tính cho nhân viên kinh doanh cho phép họ đặt ngôn ngữ chính xác mà họ muốn xem. Chúng tôi đã viết một nhiệm vụ Ant để tạo tệp .properties tại thời điểm biên dịch. Nó làm cho I18N trở nên tầm thường.

Nếu bạn cũng đang sử dụng Spring, thì càng tốt. Lớp MessageSource của họ rất hữu ích cho những thứ này.


4

Chỉ để tiếp tục đánh con ngựa chết đặc biệt này- chúng tôi đã sử dụng tốt các mã lỗi số khi lỗi được hiển thị cho khách hàng cuối, vì họ thường quên hoặc đọc sai thông báo lỗi thực tế nhưng đôi khi có thể giữ lại và báo cáo một giá trị số có thể cung cấp bạn là manh mối cho những gì thực sự đã xảy ra.


3

Có nhiều cách để giải quyết vấn đề này. Cách tiếp cận ưa thích của tôi là có các giao diện:

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

public interface IMessage {
    ICode code();
}

Bây giờ bạn có thể xác định bất kỳ số lượng enum nào cung cấp thông báo:

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

Bây giờ bạn có một số tùy chọn để biến chúng thành Chuỗi. Bạn có thể biên dịch các chuỗi thành mã của mình (sử dụng chú thích hoặc tham số phương thức khởi tạo enum) hoặc bạn có thể đọc chúng từ tệp cấu hình / thuộc tính hoặc từ bảng cơ sở dữ liệu hoặc hỗn hợp. Cách thứ hai là cách tiếp cận ưa thích của tôi vì bạn sẽ luôn cần một số thông báo mà bạn có thể chuyển thành văn bản từ rất sớm (tức là trong khi bạn kết nối với cơ sở dữ liệu hoặc đọc cấu hình).

Tôi đang sử dụng các bài kiểm tra đơn vị và khung phản chiếu để tìm tất cả các loại triển khai các giao diện của tôi để đảm bảo mỗi mã được sử dụng ở đâu đó và tệp cấu hình chứa tất cả các thông báo mong đợi, v.v.

Sử dụng các khung công tác có thể phân tích cú pháp Java như https://github.com/javaparser/javaparser hoặc từ Eclipse , bạn thậm chí có thể kiểm tra nơi các enum được sử dụng và tìm những khung không sử dụng.


2

Tôi (và các thành viên còn lại trong nhóm của chúng tôi trong công ty của tôi) muốn đưa ra các ngoại lệ thay vì trả lại mã lỗi. Mã lỗi phải được kiểm tra ở khắp mọi nơi, chuyển xung quanh và có xu hướng làm cho mã không thể đọc được khi số lượng mã trở nên lớn hơn.

Sau đó, lớp lỗi sẽ xác định thông báo.

Tái bút: và thực sự cũng quan tâm đến quốc tế hóa!
PPS: bạn cũng có thể xác định lại phương pháp tăng và thêm ghi nhật ký, lọc, v.v. nếu cần (tại môi trường leastin, nơi các lớp Ngoại lệ và bạn bè có thể mở rộng / có thể thay đổi)


xin lỗi, Robin, nhưng sau đó (ít nhất là từ ví dụ trên), chúng phải là hai ngoại lệ - "lỗi cơ sở dữ liệu" và "người dùng trùng lặp" hoàn toàn khác nhau nên phải tạo hai lớp con lỗi riêng biệt, có thể truy xuất riêng lẻ ( một là một hệ thống, là khác một lỗi admin)
blabla999

và mã lỗi được sử dụng để làm gì, nếu không phải để phân biệt giữa một hay một ngoại lệ khác? Vì vậy, ít nhất ở trên trình xử lý, anh ta chính xác là: xử lý các mã lỗi được chuyển xung quanh và nếu được chuyển sang.
blabla999

Tôi nghĩ rằng tên của ngoại lệ sẽ mang tính minh họa và tự mô tả nhiều hơn là một mã lỗi. Tốt hơn là bạn nên suy nghĩ nhiều hơn về việc khám phá các tên ngoại lệ tốt, IMO.
duffymo

@ blabla999 à, chính xác là suy nghĩ của mình. Tại sao lại bắt một ngoại lệ chi tiết thô và sau đó kiểm tra "if errorcode == x, or y, or z". Như một nỗi đau và đi ngược lại với hạt. Sau đó, bạn cũng không thể bắt các ngoại lệ khác nhau ở các cấp khác nhau trong ngăn xếp của mình. Bạn phải nắm bắt ở mọi cấp độ và kiểm tra mã lỗi ở từng cấp độ. Nó làm cho mã khách hàng dài hơn rất nhiều ... +1 + nhiều hơn nữa nếu tôi có thể. Điều đó nói rằng, tôi đoán chúng ta phải trả lời câu hỏi của OP.
wmorrison365

2
Hãy ghi nhớ, đây là một dịch vụ web. Máy khách chỉ có thể phân tích cú pháp chuỗi. Ở phía máy chủ vẫn sẽ có các ngoại lệ được đưa ra có thành viên Mã lỗi, có thể được sử dụng trong phản hồi cuối cùng cho máy khách.
pkrish

1

Hơi muộn nhưng, tôi chỉ đang tìm kiếm một giải pháp tốt cho mình. Nếu bạn gặp loại lỗi thông báo khác, bạn có thể thêm nhà máy sản xuất thông báo tùy chỉnh, đơn giản để bạn có thể chỉ định thêm chi tiết và định dạng mà bạn muốn sau này.

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)");

CHỈNH SỬA: ok, sử dụng enum ở đây hơi nguy hiểm vì bạn thay đổi enum cụ thể vĩnh viễn. Tôi đoán tốt hơn là nên thay đổi thành lớp và sử dụng các trường tĩnh, nhưng bạn không thể sử dụng '==' nữa. Vì vậy, tôi đoán đó là một ví dụ tốt về những gì không nên làm, (hoặc chỉ làm điều đó trong quá trình khởi tạo) :)


1
Hoàn toàn đồng ý với CHỈNH SỬA của bạn, việc thay đổi trường enum trong thời gian chạy không phải là một phương pháp hay. Với thiết kế này, mọi người đều có thể chỉnh sửa thông báo lỗi. Điều này khá nguy hiểm. Các trường enum phải luôn là trường cuối cùng.
b3nyc

0

enum cho mã lỗi / định nghĩa thông báo vẫn là một giải pháp tốt mặc dù nó có một mối quan tâm i18n. Trên thực tế, chúng ta có thể gặp hai trường hợp: mã / thông báo được hiển thị cho người dùng cuối hoặc cho người tích hợp hệ thống. Đối với trường hợp sau này, I18N là không cần thiết. Tôi nghĩ rằng các dịch vụ web rất có thể là trường hợp muộn hơn.


0

Sử dụng interfacelàm hằng số thông báo thường là một ý tưởng tồi. Nó sẽ bị rò rỉ vĩnh viễn vào chương trình khách hàng như một phần của API đã xuất. Ai biết được, các lập trình viên máy khách sau này có thể phân tích cú pháp thông báo lỗi đó (công khai) như một phần của chương trình của họ.

Bạn sẽ bị khóa vĩnh viễn để hỗ trợ điều này, vì những thay đổi trong định dạng chuỗi sẽ / có thể phá vỡ chương trình khách hàng.


0

Vui lòng làm theo ví dụ dưới đây:

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

};

Trong mã của bạn, hãy gọi nó như sau:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());

0

Tôi sử dụng PropertyResourceBundle để xác định mã lỗi trong ứng dụng doanh nghiệp nhằm quản lý tài nguyên mã lỗi ngôn ngữ. Đây là cách tốt nhất để xử lý mã lỗi thay vì viết mã (có thể tốt đối với một vài mã lỗi) khi số lượng mã lỗi rất lớn và có cấu trúc.

Xem tài liệu java để biết thêm thông tin trên PropertyResourceBundle

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.