วิธีที่ดีที่สุดในการกำหนดรหัส / สตริงข้อผิดพลาดใน 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 Downing

@Marcus: ฉันชอบความคิดนั้น ฉันกำลังจดจ่อที่จะนำสิ่งนี้ออกไป แต่เมื่อเรามองไปที่ความเป็นสากลฉันคิดว่าฉันจะทำตามที่คุณแนะนำ ขอบคุณ!
William Brendel

@marcus ถ้า toString () ไม่ถูกแทนที่ (ซึ่งไม่จำเป็นต้องเป็น) รหัสสตริงอาจเป็นเพียงค่า enum toString () ซึ่งจะเป็น DATABASE หรือ DUPLICATE_USER ในกรณีนี้
รูเบิล

@ จอนโครง! ฉันชอบโซลูชันนี้วิธีที่เราสามารถสร้างโซลูชันที่ง่ายต่อการแปล (หรือแปลเป็นภาษาอื่น ๆ ) คิดว่าจะใช้ใน Android ฉันสามารถใช้ R.string.IDS_XXXX แทนสตริงที่เข้ารหัสได้หรือไม่
AB

1
@AB: เมื่อคุณมี enum แล้วคุณสามารถเขียนคลาสเพื่อแยกทรัพยากรที่แปลเป็นภาษาท้องถิ่นที่เกี่ยวข้องออกจากค่า enum ผ่านไฟล์คุณสมบัติหรืออะไรก็ได้
Jon Skeet

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 บรรทัดแล้ว ลองนึกภาพไฟล์ Enumeration ของคุณเมื่อคุณจะมีข้อผิดพลาดหลายร้อยข้อให้จัดการ!


2
ฉันจะอัพมากกว่า 1 ถ้าทำได้ การเข้ารหัสสตริงเป็นสิ่งที่น่าเกลียดสำหรับการบำรุงรักษา
Robin

3
การจัดเก็บค่าคงที่สตริงในอินเทอร์เฟซเป็นความคิดที่ไม่ดี คุณสามารถใช้ enums หรือใช้ค่าคงที่สตริงในคลาสสุดท้ายที่มีคอนสตรัคเตอร์ส่วนตัวต่อแพ็คเกจหรือพื้นที่ที่เกี่ยวข้อง โปรด John Skeets ตอบด้วย Enums โปรดตรวจสอบ. 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 () บนวัตถุใด ๆ (นับประสาอะไรกับ enums) เป็นเรื่องปกติ
cletus

+1 ไม่ค่อยยืดหยุ่นเท่าโซลูชันของ Jon Skeet แต่ก็ยังแก้ปัญหาได้ดี ขอบคุณ!
William Brendel

2
ฉันหมายความว่า toString () เป็นสิ่งที่ใช้กันทั่วไปและมีประโยชน์มากที่สุดเพื่อให้ข้อมูลที่เพียงพอในการระบุวัตถุ - มักมีชื่อคลาสหรือวิธีการบอกประเภทของวัตถุอย่างมีความหมาย toString () ซึ่งส่งคืนเพียงแค่ 'เกิดข้อผิดพลาดของฐานข้อมูล' จะน่าแปลกใจในหลาย ๆ บริบท
Cowan

1
ฉันเห็นด้วยกับ Cowan การใช้ toString () ในลักษณะนี้ดูเหมือนจะ 'แฮ็ก' เล็กน้อย เพียงแค่บางอย่างสำหรับเจ้าชู้และไม่ใช่การใช้งานปกติ สำหรับ enum toString () ควรส่งคืนชื่อของค่าคงที่ enum สิ่งนี้จะดูน่าสนใจในดีบักเกอร์เมื่อคุณต้องการค่าของตัวแปร
Robin

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

ฉันเขียนรูทีนยูทิลิตี้สองสามอย่างที่ช่วยบันทึกข้อผิดพลาดห่อเป็นข้อยกเว้น (หากต้องการ) และอื่น ๆ

ฉันพยายามให้พวกเขาปล่อยให้ฉันเปิดแหล่งที่มา ... - สก็อตต์


วิธีที่ดีในการจัดการข้อความแสดงข้อผิดพลาด คุณเปิดแหล่งที่มาแล้วหรือยัง?
bobbel

5

ผมขอแนะนำให้คุณดูที่ java.util.ResourceBundle คุณควรสนใจ I18N แต่ก็คุ้มค่าแม้ว่าคุณจะไม่ทำก็ตาม การเปลี่ยนข้อความเป็นความคิดที่ดีมาก ฉันพบว่าการให้สเปรดชีตกับนักธุรกิจเป็นประโยชน์ซึ่งช่วยให้พวกเขาใส่ภาษาที่ต้องการดูได้ เราเขียนงาน Ant เพื่อสร้างไฟล์. คุณสมบัติในเวลาคอมไพล์ มันทำให้ 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();
}

ตอนนี้คุณสามารถกำหนด enums จำนวนเท่าใดก็ได้ที่ให้ข้อความ:

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

ตอนนี้คุณมีหลายทางเลือกในการเปลี่ยนสิ่งเหล่านั้นให้เป็นสตริง คุณสามารถคอมไพล์สตริงลงในโค้ดของคุณ (โดยใช้คำอธิบายประกอบหรือพารามิเตอร์ตัวสร้าง enum) หรือคุณสามารถอ่านได้จากไฟล์ config / คุณสมบัติหรือจากตารางฐานข้อมูลหรือส่วนผสม วิธีหลังเป็นแนวทางที่ฉันชอบเพราะคุณมักจะต้องการข้อความที่คุณสามารถเปลี่ยนเป็นข้อความได้เร็ว (เช่นในขณะที่คุณเชื่อมต่อกับฐานข้อมูลหรืออ่าน config)

ฉันใช้การทดสอบหน่วยและกรอบการสะท้อนเพื่อค้นหาทุกประเภทที่ใช้อินเทอร์เฟซของฉันเพื่อให้แน่ใจว่าแต่ละรหัสถูกใช้ที่ใดที่หนึ่งและไฟล์กำหนดค่ามีข้อความที่คาดไว้ทั้งหมดเป็นต้น

การใช้เฟรมเวิร์กที่สามารถแยกวิเคราะห์ Java เช่นhttps://github.com/javaparser/javaparserหรือจาก Eclipseคุณสามารถตรวจสอบได้ว่าจะใช้ enums ที่ใดและค้นหาสิ่งที่ไม่ได้ใช้


2

ฉัน (และทีมงานที่เหลือใน บริษัท ของฉัน) ต้องการเพิ่มข้อยกเว้นแทนที่จะส่งคืนรหัสข้อผิดพลาด ต้องตรวจสอบรหัสข้อผิดพลาดทุกที่ส่งผ่านและมีแนวโน้มที่จะทำให้รหัสไม่สามารถอ่านได้เมื่อจำนวนรหัสมีมากขึ้น

จากนั้นคลาสข้อผิดพลาดจะกำหนดข้อความ

PS: และยังดูแลความเป็นสากลด้วย!
PPS: คุณสามารถกำหนดวิธีการเพิ่มขึ้นใหม่และเพิ่มการบันทึกการกรองและอื่น ๆ ได้หากจำเป็น (อย่างน้อยในสภาพแวดล้อมที่คลาส Exception และเพื่อนสามารถขยาย / เปลี่ยนแปลงได้)


ขอโทษโรบิน แต่อย่างน้อย (อย่างน้อยจากตัวอย่างด้านบน) สิ่งเหล่านี้ควรเป็นข้อยกเว้นสองข้อ - "ข้อผิดพลาดของฐานข้อมูล" และ "ผู้ใช้ที่ซ้ำกัน" แตกต่างกันอย่างสิ้นเชิงดังนั้นควรสร้างคลาสย่อยข้อผิดพลาดสองคลาสแยกกันซึ่งสามารถตรวจจับได้ทีละรายการ ( หนึ่งเป็นระบบอีกอันเป็นข้อผิดพลาดของผู้ดูแลระบบ)
blabla999

และรหัสข้อผิดพลาดใช้สำหรับอะไรหากไม่ใช้เพื่อแยกความแตกต่างระหว่างข้อยกเว้นข้อหนึ่งหรือข้อยกเว้นอื่น ๆ อย่างน้อยก็เหนือตัวจัดการเขาก็ตรงอย่างนั้น: จัดการกับรหัสข้อผิดพลาดที่ถูกส่งไปรอบ ๆ และถ้า - สลับ
blabla999

ฉันคิดว่าชื่อของข้อยกเว้นน่าจะเป็นตัวอย่างและอธิบายตัวเองได้ดีกว่ารหัสข้อผิดพลาด ควรใช้ความคิดมากขึ้นในการค้นหาชื่อข้อยกเว้นที่ดี IMO
duffymo

@ blabla999 อาความคิดของฉันเป๊ะ เหตุใดจึงต้องจับข้อยกเว้นแบบหยาบแล้วทดสอบ "if errorcode == x, or y, or z" ความเจ็บปวดดังกล่าวเกิดขึ้นกับเมล็ดพืช จากนั้นคุณไม่สามารถจับข้อยกเว้นที่แตกต่างกันในระดับต่างๆในกองของคุณได้ คุณจะต้องตรวจสอบในทุกระดับและทดสอบรหัสข้อผิดพลาดในแต่ละระดับ มันทำให้รหัสไคลเอนต์มีรายละเอียดมากขึ้น ... +1 ขึ้นไปถ้าฉันทำได้ ที่กล่าวว่าฉันเดาว่าเราต้องตอบคำถาม OPs
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)");

แก้ไข: ตกลงการใช้ enum ที่นี่อาจเป็นอันตรายเล็กน้อยเนื่องจากคุณเปลี่ยน enum เฉพาะอย่างถาวร ฉันเดาว่าน่าจะดีกว่าที่จะเปลี่ยนเป็นคลาสและใช้ฟิลด์แบบคงที่ แต่คุณไม่สามารถใช้ '==' ได้อีกต่อไป ดังนั้นฉันเดาว่ามันเป็นตัวอย่างที่ดีในสิ่งที่ไม่ควรทำ (หรือทำเฉพาะในระหว่างการเริ่มต้น) :)


1
เห็นด้วยอย่างยิ่งกับการแก้ไขของคุณการแก้ไขฟิลด์ 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.