Khi nào nên ném IllegalArgumentException?


98

Tôi lo lắng rằng đây là một ngoại lệ thời gian chạy vì vậy nó có lẽ nên được sử dụng một cách tiết kiệm.
Trường hợp sử dụng tiêu chuẩn:

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

Nhưng điều đó có vẻ như nó sẽ buộc thiết kế sau:

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

Để đưa nó trở lại là một ngoại lệ đã kiểm tra.

Được rồi, nhưng hãy làm với điều đó. Nếu bạn nhập sai, bạn sẽ gặp lỗi thời gian chạy. Vì vậy, trước tiên đó thực sự là một chính sách khá khó thực hiện một cách thống nhất, bởi vì bạn có thể phải thực hiện chuyển đổi ngược lại:

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

Và tệ hơn - trong khi kiểm tra 0 <= pct && pct <= 100mã máy khách có thể được thực hiện tĩnh, điều này không đúng đối với dữ liệu nâng cao hơn như địa chỉ email, hoặc tệ hơn, một cái gì đó phải được kiểm tra dựa trên cơ sở dữ liệu, do đó nói chung mã máy khách không thể trước xác thực.

Vì vậy, về cơ bản những gì tôi đang nói là tôi không thấy một chính sách nhất quán có ý nghĩa để sử dụng IllegalArgumentException. Có vẻ như nó không nên được sử dụng và chúng ta nên tuân theo các ngoại lệ đã kiểm tra của riêng mình. Một trường hợp sử dụng tốt để ném cái này là gì?

Câu trả lời:


80

Tài liệu API cho IllegalArgumentException:

Ném để chỉ ra rằng một phương thức đã được thông qua một đối số bất hợp pháp hoặc không phù hợp.

Từ việc xem xét cách nó được sử dụng trong các thư viện JDK , tôi sẽ nói:

  • Nó có vẻ như là một biện pháp phòng thủ để phàn nàn về đầu vào rõ ràng là không tốt trước khi đầu vào có thể đi vào hoạt động và khiến một cái gì đó bị lỗi giữa chừng với một thông báo lỗi vô nghĩa.

  • Nó được sử dụng cho những trường hợp quá khó chịu khi ném một ngoại lệ đã kiểm tra (mặc dù nó xuất hiện trong mã java.lang.reflect, nơi mà mối quan tâm về mức độ vô lý của việc ném ngoại lệ đã kiểm tra không rõ ràng).

Tôi sẽ sử dụng IllegalArgumentExceptionđể kiểm tra đối số phòng thủ rãnh cuối cùng cho các tiện ích phổ biến (cố gắng duy trì sự nhất quán với cách sử dụng JDK). Hoặc trường hợp kỳ vọng rằng một đối số xấu là lỗi của lập trình viên, tương tự như một NullPointerException. Tôi sẽ không sử dụng nó để triển khai xác thực trong mã doanh nghiệp. Tôi chắc chắn sẽ không sử dụng nó cho ví dụ email.


8
Tôi nghĩ rằng lời khuyên "nơi mà kỳ vọng là một đối số không tốt là lỗi của lập trình viên" là phù hợp nhất với cách tôi đã thấy điều này được sử dụng, vì vậy hãy chấp nhận câu trả lời này.
djechlin

22

Khi nói về "đầu vào xấu", bạn nên xem xét đầu vào đến từ đâu.

Là đầu vào được nhập bởi người dùng hoặc một hệ thống bên ngoài khác mà bạn không kiểm soát, bạn nên mong đợi đầu vào không hợp lệ và luôn xác thực nó. Hoàn toàn ổn nếu ném một ngoại lệ đã kiểm tra trong trường hợp này. Ứng dụng của bạn sẽ 'phục hồi' khỏi ngoại lệ này bằng cách cung cấp thông báo lỗi cho người dùng.

Nếu đầu vào bắt nguồn từ hệ thống của riêng bạn, ví dụ như cơ sở dữ liệu của bạn hoặc một số phần khác của ứng dụng của bạn, bạn có thể dựa vào nó để hợp lệ (nó phải được xác thực trước khi đến đó). Trong trường hợp này, bạn hoàn toàn có thể sử dụng ngoại lệ không được kiểm tra như IllegalArgumentException, ngoại lệ này sẽ không bị bắt (nói chung bạn không bao giờ nên bắt các ngoại lệ không được kiểm tra). Đó là lỗi của lập trình viên mà giá trị không hợp lệ đã ở đó ngay từ đầu;) Bạn cần phải sửa nó.


2
Tại sao "bạn không bao giờ nên bắt các trường hợp ngoại lệ không được kiểm tra"?
Koray Tugay

9
Bởi vì một ngoại lệ không được kiểm tra có nghĩa là được ném ra do lỗi lập trình. Trình gọi của một phương thức ném các ngoại lệ như vậy không thể được mong đợi một cách hợp lý để khôi phục từ nó, và do đó, thường không có ý nghĩa gì khi bắt chúng.
Tom

1
Because an unchecked exception is meant to be thrown as a result of a programming errorgiúp tôi xóa rất nhiều thứ trong đầu tôi, cảm ơn bạn :)
Svarog

14

Việc ném các ngoại lệ thời gian chạy "một cách tiết kiệm" thực sự không phải là một chính sách tốt - Java hiệu quả khuyên bạn nên sử dụng các ngoại lệ đã kiểm tra khi người gọi có thể được mong đợi khôi phục một cách hợp lý . (Lỗi của lập trình viên là một ví dụ cụ thể: nếu một trường hợp cụ thể chỉ ra lỗi của lập trình viên, thì bạn nên đưa ra một ngoại lệ không được kiểm tra; bạn muốn lập trình viên có một dấu vết ngăn xếp về nơi xảy ra sự cố logic, không phải cố gắng tự xử lý nó.)

Nếu không có hy vọng phục hồi, hãy thoải mái sử dụng các ngoại lệ không được kiểm tra; chẳng ích gì khi bắt chúng, vì vậy điều đó hoàn toàn ổn.

Tuy nhiên, nó không rõ ràng 100% từ ví dụ của bạn mà ví dụ này nằm trong mã của bạn.


Tôi nghĩ rằng "dự kiến ​​một cách hợp lý để phục hồi" là rất tệ. Bất kỳ hoạt động foo(data)nào cũng có thể xảy ra như một phần for(Data data : list) foo(data);trong đó người gọi có thể muốn nhiều người thành công nhất có thể mặc dù một số dữ liệu không đúng định dạng. Bao gồm cả các lỗi lập trình nữa, nếu ứng dụng của tôi không thành công có nghĩa là một giao dịch sẽ không diễn ra, điều đó có lẽ tốt hơn, nếu nó có nghĩa là làm mát hạt nhân ngoại tuyến thì điều đó không tốt.
djechlin

StackOverflowErrorvà đó là những trường hợp mà người gọi không thể được mong đợi một cách hợp lý để phục hồi. Nhưng có vẻ như bất kỳ trường hợp mức logic ứng dụng hoặc dữ liệu nào cũng nên được kiểm tra. Điều đó có nghĩa là hãy kiểm tra con trỏ rỗng của bạn!
djechlin

4
Trong một ứng dụng làm mát hạt nhân, tôi thà thất bại trong các thử nghiệm còn hơn để xảy ra một trường hợp mà lập trình viên nghĩ là không thể vượt qua mà không bị chú ý.
Louis Wasserman

Boolean.parseBoolean (..), ném IllegalArugmentException mặc dù "người gọi có thể phục hồi một cách hợp lý." vì vậy ..... tùy thuộc vào mã của bạn để xử lý nó hoặc không trả lại cho người gọi.
Jeryl Cook

5

Như đã nêu trong hướng dẫn chính thức của oracle, nó nói rằng:

Nếu một khách hàng có thể được mong đợi một cách hợp lý để phục hồi sau một ngoại lệ, hãy đặt nó thành một ngoại lệ đã được kiểm tra. Nếu khách hàng không thể làm bất cứ điều gì để khôi phục ngoại lệ, hãy đặt nó thành ngoại lệ không được chọn.

Nếu tôi có một Ứng dụng tương tác với cơ sở dữ liệu bằng cách sử dụng JDBC, Và tôi có một phương thức lấy đối số là int itemdouble price. Mục pricetương ứng được đọc từ bảng cơ sở dữ liệu. Tôi chỉ cần nhân tổng số lượng itemđã mua với pricegiá trị và trả về kết quả. Mặc dù tôi luôn chắc chắn ở phần cuối của mình (Kết thúc ứng dụng) rằng giá trị trường giá trong bảng không bao giờ có thể âm. Nhưng nếu giá trị giá xuất hiện là số âm thì sao? Nó cho thấy rằng có một vấn đề nghiêm trọng với phía cơ sở dữ liệu. Có lẽ do nhà điều hành nhập sai giá. Đây là loại sự cố mà phần khác của ứng dụng gọi phương thức đó không thể lường trước được và không thể khắc phục được. Nó là một BUGtrong cơ sở dữ liệu của bạn. Vậy sau đóIllegalArguementException()nên được ném trong trường hợp này sẽ nêu rõ điều đó the price can't be negative.
Tôi hy vọng rằng tôi đã bày tỏ quan điểm của mình một cách rõ ràng ..


Tôi không thích lời khuyên này (của oracle) vì xử lý ngoại lệ là về cách khôi phục, không phải là có khôi phục hay không. Ví dụ: một yêu cầu của người dùng không đúng định dạng không đáng để làm hỏng toàn bộ máy chủ web.
djechlin

5

Bất kỳ API nào cũng phải kiểm tra tính hợp lệ của mọi tham số của bất kỳ phương thức công khai nào trước khi thực thi nó:

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

Chúng đại diện cho 99,9% số lần lỗi trong ứng dụng bởi vì nó yêu cầu các hoạt động không thể thực hiện được nên cuối cùng chúng là lỗi có thể làm sập ứng dụng (vì vậy đây là lỗi không thể khôi phục).

Trong trường hợp này và theo cách tiếp cận của fail fast, bạn nên để ứng dụng kết thúc để tránh làm hỏng trạng thái ứng dụng.


Ngược lại, nếu một ứng dụng khách API cung cấp cho tôi thông tin đầu vào không tốt, tôi không nên làm hỏng toàn bộ máy chủ API của mình.
djechlin 29/09/17

2
Tất nhiên, nó sẽ không làm hỏng máy chủ API của bạn nhưng trả lại một ngoại lệ cho người gọi. Điều đó sẽ không làm hỏng bất kỳ thứ gì ngoại trừ máy khách.
Ignacio Soler Garcia

Những gì bạn đã viết trong nhận xét không phải là những gì bạn đã viết trong câu trả lời.
djechlin

1
Hãy để tôi giải thích, nếu lệnh gọi tới API với các tham số sai (lỗi) được thực hiện bởi một ứng dụng khách bên thứ ba thì ứng dụng khách sẽ gặp sự cố. Nếu đó là máy chủ API, máy chủ có lỗi gọi phương thức với các thông số sai thì máy chủ API sẽ gặp sự cố. Kiểm tra: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia

1

Coi IllegalArgumentExceptionnhư một kiểm tra các điều kiện tiên quyết và xem xét nguyên tắc thiết kế: Một phương pháp công khai phải biết và công khai các điều kiện tiên quyết của chính nó.

Tôi đồng ý rằng ví dụ này là đúng:

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

Nếu EmailUtil không rõ ràng , có nghĩa là có một số lý do không thể mô tả các điều kiện tiên quyết cho người dùng cuối, thì một ngoại lệ đã chọn là đúng. Phiên bản thứ hai, được sửa chữa cho thiết kế này:

import com.someoneelse.EmailUtil;

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

Nếu EmailUtil trong suốt , chẳng hạn như có thể đó là một phương thức riêng thuộc sở hữu của lớp đang được đề cập, IllegalArgumentExceptionlà đúng nếu và chỉ khi các điều kiện tiên quyết của nó có thể được mô tả trong tài liệu hàm. Đây cũng là một phiên bản chính xác:

/** @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");
  }
}

Thiết kế này có thể đi theo một trong hai cách.

  • Nếu các điều kiện tiên quyết là tốn kém để mô tả, hoặc nếu lớp học được sử dụng bởi những khách hàng không biết liệu email của họ có hợp lệ hay không, thì hãy sử dụng ParseException. Phương thức cấp cao nhất ở đây được đặt tên scanEmailcho biết người dùng cuối dự định gửi email chưa được kiểm tra thông qua để điều này có thể chính xác.
  • Nếu các điều kiện tiên quyết có thể được mô tả trong tài liệu hàm và lớp không có ý định cho đầu vào không hợp lệ và do đó lỗi của lập trình viên được chỉ ra, hãy sử dụng IllegalArgumentException. Mặc dù không được "kiểm tra", "kiểm tra" sẽ chuyển đến chức năng ghi lại chức năng của Javadoc, mà khách hàng dự kiến ​​sẽ tuân theo. IllegalArgumentExceptionnơi mà khách hàng không thể nói trước lập luận của họ là bất hợp pháp là sai.

Lưu ý về IllegalStateException : Điều này có nghĩa là "trạng thái bên trong của đối tượng này (các biến cá thể riêng tư) không thể thực hiện hành động này." Người dùng cuối không thể thấy trạng thái riêng tư vì vậy nói một cách lỏng lẻo nó được ưu tiên hơn IllegalArgumentExceptiontrong trường hợp cuộc gọi khách hàng không có cách nào để biết trạng thái của đối tượng là không nhất quán. Tôi không có lời giải thích tốt khi nó được ưu tiên hơn các ngoại lệ đã kiểm tra, mặc dù những thứ như khởi tạo hai lần hoặc mất kết nối cơ sở dữ liệu không được phục hồi, là những ví dụ.

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.