Câu hỏi Khi nào một IllegalArgumentException nên được ném?


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 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 đây:

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

Để làm cho nó trở thành một ngoại lệ được kiểm tra.

Được rồi, nhưng hãy đi với điều đó. Nếu bạn cung cấp cho đầu vào xấu, bạn nhận được một lỗi thời gian chạy. Vì vậy, trước hết đó thực sự là một chính sách khá khó khăn để thực hiện thống nhất, bởi vì bạn có thể phải thực hiện chuyển đổi rất 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 <= 100 mã máy khách có thể được mong đợi để làm tĩnh, điều này không phải như vậy đối với nhiều dữ liệu nâng cao hơn như địa chỉ email hoặc tệ hơn, cái gì đó phải được kiểm tra dựa vào cơ sở dữ liệu, do đó mã khách hàng chung không thể xác thực trước.

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


76
2018-03-04 18:34


gốc




Các câu trả lời:


Tài liệu api cho IllegalArgumentException là:

Bị ném để cho biết rằng một phương pháp đã được thông qua một đối số bất hợp pháp hoặc không phù hợp.

Từ nhìn vào cách nó được sử dụng trong các thư viện jdk, Tôi sẽ nói:

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

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

Tôi sẽ sử dụng IllegalArgumentException để thực hiện kiểm soát đối số phòng thủ cuối cùng cho các tiện ích thông thường (cố gắng duy trì sự nhất quán với việc sử dụng jdk), nơi mà kỳ vọng là một đối số xấu là lỗi lập trình, tương tự như một NPE. Tôi sẽ không sử dụng nó để thực hiện xác nhận trong mã kinh doanh. Tôi chắc chắn sẽ không sử dụng nó cho ví dụ email.


57
2018-03-04 19:48



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


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

Là đầu vào được nhập bởi người dùng hoặc 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ó. Nó hoàn toàn ok để ném một ngoại lệ kiểm tra trong trường hợp này. Ứng dụng của bạn nên 'khôi phục' từ 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ụ: 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 sẽ có thể dựa vào nó để có giá trị (nó phải được xác nhận trước khi nó đến đó). Trong trường hợp này, nó hoàn toàn ok để ném một ngoại lệ không được kiểm soát như một IllegalArgumentException, mà không nên bị bắt (nói chung bạn không bao giờ nên bắt được các ngoại lệ không được kiểm soát). Đó là lỗi của một lập trình viên mà giá trị không hợp lệ đã có ở nơi đầu tiên;) Bạn cần phải sửa chữa nó.


19
2018-03-04 20:06



Tại sao "bạn không bao giờ nên bắt được ngoại lệ không được kiểm soát"? - Koray Tugay
Bởi vì một ngoại lệ không được kiểm soát có nghĩa là được ném ra do lỗi lập trình. Người gọi của một phương thức ném ngoại lệ như vậy không thể được mong đợi một cách hợp lý để phục hồi từ nó, và do đó nó thường không có ý nghĩa để bắt chúng. - Tom
Because an unchecked exception is meant to be thrown as a result of a programming error đã giúp tôi xóa rất nhiều thứ trong đầu, cảm ơn bạn :) - svarog


Việc ném các ngoại lệ thời gian chạy "một cách tiết kiệm" không thực sự là một chính sách tốt - Hiệu quả Java 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 một cách hợp lý để phục hồi. (Lập trình lỗi là một ví dụ cụ thể: nếu một trường hợp cụ thể chỉ ra lỗi lập trình viên, thì bạn nên ném một ngoại lệ không được kiểm soát; bạn muốn lập trình viên có dấu vết ngăn xếp nơi xảy ra vấn đề logic, chứ không phải cố xử lý nó.)

Nếu không có hy vọng phục hồi, thì vui lòng sử dụng các ngoại lệ không được kiểm soát; không có điểm bắt chúng, vì vậy đó là hoàn toàn tốt đẹp.

Nó không phải là 100% rõ ràng từ ví dụ của bạn trong trường hợp này ví dụ này là trong mã của bạn, mặc dù.


10
2018-03-04 18:40



Tôi nghĩ rằng "hợp lý dự kiến ​​sẽ phục hồi" là weaselly. Bất kỳ hoạt động nào foo(data) có thể đã xảy ra như một phần của for(Data data : list) foo(data); trong đó người gọi có thể muốn nhiều 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 lỗi có lập trình quá, nếu ứng dụng của tôi thất bại có nghĩa là một giao dịch sẽ không đi qua đó có thể là tốt hơn, nếu nó có nghĩa là làm mát hạt nhân đi offline đó là xấu. - djechlin
StackOverflowError và đó là những trường hợp mà người gọi không thể được mong đợi để phục hồi hợp lý. Nhưng có vẻ như bất kỳ dữ liệu hoặc ứng dụng logic cấp trường hợp nên được kiểm tra. Điều đó có nghĩa là kiểm tra con trỏ null của bạn! - djechlin
Trong một ứng dụng làm mát hạt nhân, tôi thay vì thất bại trong các thử nghiệm hơn là cho phép một trường hợp lập trình viên nghĩ là không thể vượt qua được chú ý. - Louis Wasserman


Bất kỳ API nào cũng nên 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 vì nó yêu cầu các thao tác không thể vì vậy cuối cùng chúng là các lỗi sẽ làm hỏng ứng dụng (vì vậy nó là một lỗi không thể phục hồi được).

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


5
2017-12-17 11:47



Ngược lại, nếu một khách hàng API cho tôi một đầu vào xấu, tôi nên không phải làm hỏng toàn bộ máy chủ API của tôi. - djechlin
Tất nhiên, nó không nên sụp đổ 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 đó không nên làm hỏng bất cứ điều gì nhưng khách hàng. - Ignacio Soler Garcia
Những gì bạn đã viết trong bình luận không phải là những gì bạn đã viết trong câu trả lời. - djechlin
Hãy để tôi giải thích, nếu lệnh gọi API có thông số sai (lỗi) được thực hiện bởi ứng dụng khách của bên thứ ba thì khách hàng sẽ gặp sự cố. Nếu máy chủ API là máy chủ có lỗi gọi phương thức có thông số sai thì máy chủ API sẽ bị lỗi. Kiểm tra: en.wikipedia.org/wiki/Fail-fast - Ignacio Soler Garcia


Theo quy định trong hướng dẫn chính thức oracle, nó nói rằng:

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

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 pháp lấy đối số là int item và double price. Các price cho mục tương ứng được đọc từ bảng cơ sở dữ liệu. Tôi chỉ cần nhân tổng số item mua với price giá trị và trả về kết quả. Mặc dù tôi luôn luôn chắc chắn ở phần cuối của tôi (Ứng dụng kết thúc) rằng giá trị trường giá trong bảng không bao giờ có thể là tiêu cực. Nhưng nếu giá trị đi ra tiêu cực? 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ẽ việc nhập giá sai bởi nhà điều hành. Đây là loại vấn đề mà phần khác của ứng dụng gọi là phương pháp đó không thể dự đoán và không thể phục hồi từ nó. Nó là một BUG trong 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 the price can't be negative.
Tôi hy vọng rằng tôi đã bày tỏ quan điểm của tôi rõ ràng ..


4
2018-03-04 19:06



Tôi không thích lời khuyên này (oracle's) vì việc xử lý ngoại lệ là về cách hồi phục, không phải là có phục hồi hay không. Ví dụ: một yêu cầu 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


Đãi IllegalArgumentException như một điều kiện tiên quyết kiểm tra và xem xét nguyên tắc thiết kế: Một phương pháp công cộng nên biết và công khai ghi lại các điều kiện tiên quyết của riêng nó.

Tôi đồng ý ví dụ này là chính xác:

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

Nếu EmailUtil mờ, có nghĩa là có một số lý do các điều kiện tiên quyết không thể được mô tả cho người dùng cuối, sau đó một ngoại lệ đã kiểm tra là chính xác. Phiên bản thứ hai, được sử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, ví dụ có thể đó là phương pháp riêng tư thuộc sở hữu của lớp học được đặt câu hỏi, IllegalArgumentException là chính xác khi 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à 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 điều kiện tiên quyết đắt tiền để mô tả, hoặc nếu lớp học được dự định sẽ được sử dụng bởi khách hàng không biết liệu email của họ có hợp lệ hay không, hãy sử dụng ParseException. Phương thức cấp cao nhất ở đây được đặt tên scanEmail gợi ý người dùng cuối có ý định gửi email không được thông qua vì vậy điều này có thể đúng.
  • Nếu điều kiện tiên quyết có thể được mô tả trong tài liệu chức năng, và lớp không có ý định cho đầu vào không hợp lệ và do đó lỗi lập trình viên được chỉ định, sử dụng IllegalArgumentException. Mặc dù không được "kiểm tra", "kiểm tra" di chuyển đến Javadoc để ghi lại chức năng mà máy khách mong muốn tuân thủ. IllegalArgumentException nơi mà khách hàng không thể nói với lập luận của họ là bất hợp pháp trước là sai.

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


0
2018-03-27 16:15