ควรโยน IllegalArgumentException เมื่อใด


98

ฉันกังวลว่านี่เป็นข้อยกเว้นรันไทม์ดังนั้นจึงควรใช้เท่าที่จำเป็น
กรณีการใช้งานมาตรฐาน:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

แต่ดูเหมือนว่าจะบังคับให้ออกแบบต่อไปนี้:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

เพื่อให้กลับมาเป็นข้อยกเว้นที่ถูกตรวจสอบ

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

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

และที่แย่กว่านั้น - ในขณะที่การตรวจสอบ0 <= pct && pct <= 100รหัสไคลเอ็นต์อาจคาดว่าจะทำแบบคงที่ แต่ไม่ได้เป็นเช่นนั้นสำหรับข้อมูลขั้นสูงเช่นที่อยู่อีเมลหรือแย่กว่านั้นคือมีบางอย่างที่ต้องตรวจสอบกับฐานข้อมูลดังนั้นในรหัสไคลเอนต์ทั่วไปจึงไม่สามารถทำได้ล่วงหน้า ตรวจสอบความถูกต้อง

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

คำตอบ:


81

เอกสาร API สำหรับIllegalArgumentException:

โยนเพื่อระบุว่ามีการส่งผ่านวิธีการที่ผิดกฎหมายหรือไม่เหมาะสม

จากการดูวิธีใช้ในไลบรารี JDKฉันจะพูดว่า:

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

  • ใช้สำหรับกรณีที่น่ารำคาญเกินกว่าที่จะโยนข้อยกเว้นที่ตรวจสอบแล้ว (แม้ว่าจะปรากฏในโค้ด java.lang.reflect ซึ่งความกังวลเกี่ยวกับระดับที่ไร้สาระของการตรวจสอบข้อยกเว้น - ขว้างไม่ชัดเจน)

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


8
ฉันคิดว่าคำแนะนำ "โดยที่ความคาดหวังคือการโต้แย้งที่ไม่ดีเป็นข้อผิดพลาดของโปรแกรมเมอร์" นั้นสอดคล้องกับวิธีที่ฉันเคยเห็นมากที่สุดดังนั้นจึงยอมรับคำตอบนี้
djechlin

23

เมื่อพูดถึง "อินพุตที่ไม่ถูกต้อง" คุณควรพิจารณาว่าข้อมูลนั้นมาจากที่ใด

อินพุตที่ป้อนโดยผู้ใช้หรือระบบภายนอกอื่นที่คุณไม่ได้ควบคุมคุณควรคาดหวังว่าอินพุตจะไม่ถูกต้องและตรวจสอบความถูกต้องเสมอ เป็นเรื่องดีที่จะโยนข้อยกเว้นที่ตรวจสอบแล้วในกรณีนี้ แอปพลิเคชันของคุณควร "กู้คืน" จากข้อยกเว้นนี้โดยการให้ข้อความแสดงข้อผิดพลาดแก่ผู้ใช้

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


2
ทำไม "คุณไม่ควรจับข้อยกเว้นที่ไม่ได้ตรวจสอบ"?
Koray Tugay

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

1
Because an unchecked exception is meant to be thrown as a result of a programming errorช่วยฉันเคลียร์หลาย ๆ เรื่องในหัวขอบคุณ :)
svarog

15

การโยนข้อยกเว้นรันไทม์ "เท่าที่จำเป็น" ไม่ใช่นโยบายที่ดีจริงๆ - Java ที่มีประสิทธิภาพแนะนำให้คุณใช้ข้อยกเว้นที่ตรวจสอบแล้วเมื่อผู้โทรคาดว่าจะกู้คืนได้อย่างสมเหตุสมผลโทรสมควรได้รับการคาดว่าจะฟื้นตัว(ข้อผิดพลาดของโปรแกรมเมอร์เป็นตัวอย่างเฉพาะ: หากกรณีใดกรณีหนึ่งบ่งชี้ข้อผิดพลาดของโปรแกรมเมอร์คุณควรโยนข้อยกเว้นที่ไม่ได้เลือกไว้คุณต้องการให้โปรแกรมเมอร์มีการติดตามสแต็กที่เกิดปัญหาตรรกะไม่ใช่เพื่อพยายามจัดการด้วยตัวเอง)

หากไม่มีความหวังในการฟื้นตัวคุณสามารถใช้ข้อยกเว้นที่ไม่ได้ตรวจสอบได้ ไม่มีจุดที่จะจับพวกมันได้ดังนั้นมันก็เป็นเรื่องปกติ

ยังไม่ชัดเจน 100% จากตัวอย่างของคุณซึ่งในกรณีนี้ตัวอย่างนี้อยู่ในโค้ดของคุณ


ฉันคิดว่า "คาดว่าจะฟื้นตัวอย่างมีเหตุผล" เป็นสิ่งที่ไม่ดี การดำเนินการใด ๆfoo(data)อาจเกิดขึ้นโดยเป็นส่วนหนึ่งของการfor(Data data : list) foo(data);ที่ผู้โทรอาจต้องการให้หลายคนประสบความสำเร็จมากที่สุดแม้ว่าข้อมูลบางส่วนจะผิดรูปแบบก็ตาม รวมถึงข้อผิดพลาดทางโปรแกรมด้วยเช่นกันหากแอปพลิเคชันของฉันล้มเหลวหมายความว่าธุรกรรมจะไม่ผ่านซึ่งน่าจะดีกว่าหากหมายความว่าการระบายความร้อนด้วยพลังงานนิวเคลียร์จะออฟไลน์ซึ่งไม่ดี
djechlin

StackOverflowErrorและเป็นกรณีที่ผู้โทรไม่สามารถคาดหวังได้อย่างสมเหตุสมผลว่าจะฟื้นตัวได้ แต่ดูเหมือนว่าควรตรวจสอบกรณีระดับตรรกะของข้อมูลหรือแอปพลิเคชัน นั่นหมายถึงทำการตรวจสอบตัวชี้ว่างของคุณ!
djechlin

4
ในแอปพลิเคชั่นระบายความร้อนด้วยนิวเคลียร์ฉันค่อนข้างจะล้มเหลวในการทดสอบมากกว่าที่จะยอมให้มีกรณีที่โปรแกรมเมอร์คิดว่าเป็นไปไม่ได้ที่จะไม่มีใครสังเกตเห็น
Louis Wasserman

Boolean.parseBoolean (.. ) พ่น IllegalArugmentException แม้ว่า "ผู้โทรสามารถคาดว่าจะกู้คืนได้อย่างสมเหตุสมผล" ดังนั้น ..... มันขึ้นอยู่กับรหัสของคุณที่จะจัดการหรือไม่กลับไปหาผู้โทร
Jeryl Cook

6

ตามที่ระบุไว้ในบทช่วยสอนอย่างเป็นทางการของ Oracle ระบุว่า:

หากลูกค้าสามารถกู้คืนจากข้อยกเว้นได้อย่างสมเหตุสมผลให้ทำการตรวจสอบข้อยกเว้น หากไคลเอ็นต์ไม่สามารถดำเนินการใด ๆ เพื่อกู้คืนจากข้อยกเว้นให้เป็นข้อยกเว้นที่ไม่เลือก

หากฉันมีแอปพลิเคชันที่โต้ตอบกับฐานข้อมูลโดยใช้JDBCและฉันมีวิธีการที่ใช้อาร์กิวเมนต์เป็นint itemและdouble price. priceสำหรับรายการที่สอดคล้องกันคือการอ่านจากตารางฐานข้อมูล ฉันเพียงแค่คูณจำนวนทั้งหมดที่itemซื้อด้วยpriceมูลค่าและส่งคืนผลลัพธ์ ถึงแม้ว่าผมจะเสมอแน่ใจว่าในตอนท้ายของฉัน (end โปรแกรม) ว่าราคาค่าเขตข้อมูลในตารางไม่เคยเป็นลบ .But สิ่งที่ถ้าค่าราคาออกมาในเชิงลบ ? แสดงว่ามีปัญหาร้ายแรงกับด้านฐานข้อมูล ผู้ประกอบการอาจป้อนราคาผิด นี่เป็นปัญหาที่ส่วนอื่น ๆ ของแอปพลิเคชันที่เรียกวิธีการนั้นไม่สามารถคาดการณ์ได้และไม่สามารถกู้คืนได้ มันเป็นสิ่งที่ หวังว่าคงได้แสดงประเด็นชัดเจน ..BUGในฐานข้อมูลของคุณ ดังนั้นและIllegalArguementException() the price can't be negative


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

6

API ใด ๆ ควรตรวจสอบความถูกต้องของทุกพารามิเตอร์ของวิธีการสาธารณะก่อนที่จะดำเนินการ:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

ข้อผิดพลาดเหล่านี้แสดงถึง 99.9% ของข้อผิดพลาดในแอปพลิเคชันเนื่องจากกำลังร้องขอการดำเนินการที่เป็นไปไม่ได้ดังนั้นในที่สุดพวกเขาจึงเป็นจุดบกพร่องที่ทำให้แอปพลิเคชันขัดข้อง

ในกรณีนี้และปฏิบัติตามแนวทางของการล้มเหลวอย่างรวดเร็วคุณควรปล่อยให้แอปพลิเคชันเสร็จสิ้นเพื่อหลีกเลี่ยงไม่ให้สถานะแอปพลิเคชันเสียหาย


ในทางตรงกันข้ามหากไคลเอนต์ API ให้อินพุตที่ไม่ดีฉันไม่ควรทำให้เซิร์ฟเวอร์ API ของฉันเสียหายทั้งหมด
djechlin

2
แน่นอนว่าเซิร์ฟเวอร์ API ของคุณไม่ควรขัดข้อง แต่จะส่งกลับข้อยกเว้นให้กับผู้โทรซึ่งไม่ควรขัดข้อง แต่อย่างใด
Ignacio Soler Garcia

สิ่งที่คุณเขียนในความคิดเห็นไม่ใช่สิ่งที่คุณเขียนในคำตอบ
djechlin

1
ให้ฉันอธิบายถ้าการเรียก API ด้วยพารามิเตอร์ที่ไม่ถูกต้อง (ข้อผิดพลาด) เกิดขึ้นโดยไคลเอนต์บุคคลที่สามไคลเอ็นต์ควรหยุดทำงาน หากเป็นเซิร์ฟเวอร์ API เซิร์ฟเวอร์ที่มีบั๊กที่เรียกเมธอดโดยมีพารามิเตอร์ที่ไม่ถูกต้องเซิร์ฟเวอร์ API ควรหยุดทำงาน ตรวจสอบ: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia

3

Treat IllegalArgumentExceptionเป็นปัจจัยพื้นฐานการตรวจสอบและพิจารณาหลักการออกแบบ: วิธีการของประชาชนทั้งความรู้และสาธารณชนควรเอกสารปัจจัยพื้นฐานของตัวเอง

ฉันยอมรับว่าตัวอย่างนี้ถูกต้อง:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

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

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

หาก EmailUtil มีความโปร่งใสตัวอย่างเช่นอาจเป็นวิธีการส่วนตัวที่เป็นของคลาสภายใต้คำถามIllegalArgumentExceptionนั้นถูกต้องก็ต่อเมื่อสามารถอธิบายเงื่อนไขเบื้องต้นได้ในเอกสารของฟังก์ชัน นี่เป็นเวอร์ชันที่ถูกต้องเช่นกัน:

/** @param String email An email with an address in the form abc@xyz.com
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

การออกแบบนี้อาจไปทางใดทางหนึ่ง

  • ParseExceptionหากปัจจัยพื้นฐานที่มีราคาแพงเพื่ออธิบายหรือถ้าชั้นมีวัตถุประสงค์เพื่อนำมาใช้โดยลูกค้าที่ไม่ทราบว่าอีเมลของพวกเขาที่ถูกต้องแล้วการใช้งาน วิธีการระดับบนสุดที่นี่มีการตั้งชื่อscanEmailซึ่งบ่งบอกถึงผู้ใช้ปลายทางที่ตั้งใจจะส่งอีเมลที่ไม่ได้ตรวจสอบผ่านดังนั้นจึงน่าจะถูกต้อง
  • IllegalArgumentExceptionหากปัจจัยพื้นฐานที่สามารถอธิบายไว้ในเอกสารฟังก์ชั่นและระดับชั้นไม่ได้เจตนาสำหรับการป้อนข้อมูลที่ไม่ถูกต้องและดังนั้นจึงมีข้อผิดพลาดโปรแกรมเมอร์จะแสดงการใช้งาน แม้ว่าจะไม่ "ตรวจสอบ" แต่ "เช็ค" จะย้ายไปยัง Javadoc ที่บันทึกฟังก์ชันซึ่งคาดว่าไคลเอ็นต์จะปฏิบัติตาม IllegalArgumentExceptionในกรณีที่ลูกค้าไม่สามารถบอกได้ว่าข้อโต้แย้งของตนผิดกฎหมายล่วงหน้าถือว่าผิด

หมายเหตุเกี่ยวกับ IllegalStateException : ซึ่งหมายความว่า "สถานะภายในของอ็อบเจ็กต์นี้ (ตัวแปรอินสแตนซ์ส่วนตัว) ไม่สามารถดำเนินการนี้ได้" ผู้ใช้ปลายทางไม่สามารถมองเห็นสถานะส่วนตัวได้ดังนั้นการพูดอย่างหลวม ๆ จึงมีความสำคัญเหนือกว่าIllegalArgumentExceptionในกรณีที่การเรียกของไคลเอ็นต์ไม่มีทางรู้ว่าสถานะของวัตถุไม่สอดคล้องกัน ฉันไม่มีคำอธิบายที่ดีเมื่อต้องการมากกว่าข้อยกเว้นที่ตรวจสอบแม้ว่าจะเป็นตัวอย่างเช่นการเริ่มต้นสองครั้งหรือสูญเสียการเชื่อมต่อฐานข้อมูลที่ไม่ได้รับการกู้คืน

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