Hàm trả về true / false so với void khi thành công và đưa ra một ngoại lệ khi thất bại


22

Tôi đang xây dựng một API, một chức năng tải lên một tệp. Hàm này sẽ trả về nothing / void nếu tệp được tải lên chính xác và đưa ra một ngoại lệ khi có sự cố.

Tại sao một ngoại lệ và không chỉ sai? Bởi vì bên trong một ngoại lệ tôi có thể chỉ định lý do lỗi (không có kết nối, thiếu tên tệp, mật khẩu sai, mô tả tệp bị thiếu, v.v.). Tôi muốn xây dựng một ngoại lệ tùy chỉnh (với một số enum để giúp người dùng API xử lý tất cả các lỗi).

Đây có phải là một cách thực hành tốt hay tốt hơn là trả lại một đối tượng (bên trong có boolean, thông báo lỗi tùy chọn và enum cho lỗi)?



59
Đúng và sai đi cùng nhau. Vô vi và ngoại lệ đi cùng nhau. Đúng và ngoại lệ đi cùng nhau như mèo và thuế. Một ngày nào đó tôi có thể đọc mã của bạn. Xin đừng làm điều này với tôi.
candied_orange

4
Tôi khá thích các mẫu mà một đối tượng được trả về, gói gọn thành công hoặc thất bại. Ví dụ, Rust có Result<T, E>nơi Tlà kết quả nếu thành công và Elà lỗi nếu không thành công. Việc ném một ngoại lệ (có thể - không chắc chắn như vậy trong Java) có thể tốn kém vì nó liên quan đến việc giải phóng ngăn xếp cuộc gọi, nhưng tạo một đối tượng Exception thì rẻ. Rõ ràng, hãy tuân theo các mẫu đã thiết lập của ngôn ngữ bạn đang sử dụng, nhưng không kết hợp các booleans và ngoại lệ như thế này.
Dan Pantry

4
Hãy cẩn thận ở đây. Bạn có thể đang đi xuống một lỗ thỏ. Khi chương trình của bạn có khả năng xử lý sự cố, việc phát hiện và xử lý vấn đề đó thường dễ dàng hơn bằng cách kiểm tra các điều kiện tiên quyết. Bạn hiếm khi bắt gặp một ngoại lệ, kiểm tra dữ liệu của nó trong mã và thử lại sau khi khắc phục sự cố. Vì vậy, hiếm khi có nhiều loại ngoại lệ. Nếu bạn đang cố gắng ghi nhật ký các vấn đề, điều này có ý nghĩa hơn nhiều, nhưng đừng hy vọng sẽ viết một loạt các khối bắt với số lượng mã đáng kể trong đó.
jpmc26

4
@DanPantry Trong Java, ngăn xếp cuộc gọi được kiểm tra khi tạo ngoại lệ, không phải khi ném nó. fillInStackTrace();được gọi trong siêu kiến ​​trúc sư, trong lớpThrowable
Simon Forsberg

Câu trả lời:


35

Ném một ngoại lệ chỉ đơn giản là một cách bổ sung để làm cho một phương thức trả về một giá trị. Người gọi có thể kiểm tra giá trị trả về dễ dàng như bắt ngoại lệ và kiểm tra xem. Do đó, quyết định giữa throwreturnyêu cầu các tiêu chí khác.

Nên tránh các ngoại lệ nếu nó gây nguy hiểm cho hiệu quả của chương trình của bạn (xây dựng một đối tượng ngoại lệ và tháo gỡ ngăn xếp cuộc gọi sẽ giúp ích cho máy tính nhiều hơn là chỉ đẩy giá trị lên nó). Nhưng nếu mục đích của phương pháp của bạn là tải lên một tệp, thì nút cổ chai sẽ luôn là I / O của mạng và hệ thống tệp, do đó, việc tối ưu hóa phương thức trả về là vô nghĩa.

Đây cũng là một ý tưởng tồi để đưa ra các ngoại lệ cho luồng điều khiển đơn giản (ví dụ: khi tìm kiếm thành công bằng cách tìm giá trị của nó), vì điều đó vi phạm kỳ vọng của người dùng API. Nhưng một phương pháp không hoàn thành mục đích của nó một trường hợp đặc biệt (hoặc ít nhất là nó phải như vậy), vì vậy tôi thấy không có lý do gì để không ném ngoại lệ. Và nếu bạn làm điều đó, bạn cũng có thể biến nó thành một ngoại lệ tùy chỉnh, nhiều thông tin hơn (nhưng đó là một ý tưởng tốt để biến nó thành một lớp con của một ngoại lệ chuẩn, giống như chung hơn IOException).


7
Nếu đó là kết quả mong đợi cho yêu cầu tải lên tệp không thành công, tôi sẽ ngạc nhiên bởi một ngoại lệ được ném vào mặt tôi (đặc biệt là nếu có giá trị trả về trong chữ ký phương thức).
hoffmale

1
Lưu ý rằng lý do khiến nó mở rộng một ngoại lệ tiêu chuẩn là nhiều người gọi (thậm chí là hầu hết) sẽ không viết mã để cố gắng khắc phục sự cố. Kết quả là, hầu hết người gọi sẽ muốn một cách đơn giản, "nắm bắt nhóm ngoại lệ lớn này" mà không cần phải liệt kê tất cả chúng một cách rõ ràng.
jpmc26

4
@gbjbaanb Đó là lý do tại sao nên sử dụng ngoại lệ khi thực sự có một số tình huống không thể giải quyết được. Khi bạn đang cố mở một tệp và tệp không thể đọc được, đây là một trường hợp tốt cho một ngoại lệ. Khi bạn kiểm tra sự tồn tại của tệp, một boolean cần được sử dụng vì rất mong đợi rằng tệp có thể không ở đó.
Malcolm

21
Kilian đang nói, trong khẩu hiệu đệ quy "ngoại lệ dành cho các tình huống đặc biệt", các tình huống đặc biệt là khi chức năng không thể thực hiện được mục đích của nó. Nếu chức năng được cho là đọc một tệp và tệp không thể được đọc là ngoại lệ . Nếu chức năng được cho là cho bạn biết liệu một tệp có tồn tại hay không (đừng bận tâm đến TOCTOU tiềm năng), thì tệp không tồn tại không phải là ngoại lệ vì chức năng vẫn có thể làm những gì nó phải làm. Nếu chức năng được cho là để xác nhận rằng một tệp tồn tại, thì nó không tồn tại là ngoại lệ vì đó là "khẳng định" nghĩa là gì.
Steve Jessop

10
Lưu ý rằng sự khác biệt duy nhất giữa một chức năng cho bạn biết liệu một tệp có tồn tại hay không và một chức năng khẳng định rằng một tệp tồn tại, là liệu trường hợp của tệp không tồn tại có phải là ngoại lệ hay không. Mọi người đôi khi nói như thể đó là một đặc tính của tình trạng lỗi cho dù đó có phải là "ngoại lệ" hay không. Nó không phải là một thuộc tính của điều kiện lỗi, nó là một thuộc tính kết hợp của điều kiện lỗi và mục đích của chức năng mà nó xảy ra. Nếu không, chúng tôi sẽ không bao giờ bắt ngoại lệ.
Steve Jessop

31

Hoàn toàn không có lý do để trở lại truethành công nếu bạn không trở lại falsethất bại. Mã khách hàng nên trông như thế nào?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

Trong trường hợp này, người gọi cần một khối chặn thử dù sao, nhưng sau đó anh ta có thể viết tốt hơn:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Vì vậy, truegiá trị trả lại là hoàn toàn không phù hợp cho người gọi. Vì vậy, chỉ cần giữ phương pháp void.


Nói chung, có ba cách để thiết kế chế độ failre.

  1. Trả về đúng / sai
  2. Sử dụng void, ném (kiểm tra) ngoại lệ
  3. trả về một đối tượng kết quả trung gian .

Trả lại true/false

Điều này được sử dụng trong một số API kiểu cũ, chủ yếu là kiểu c. Nhược điểm là obviuos, bạn không biết điều gì đã xảy ra. PHP làm điều này khá thường xuyên, dẫn đến mã như thế này:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

Trong bối cảnh đa luồng, điều này thậm chí còn tồi tệ hơn.

Ném (kiểm tra) ngoại lệ

Đây là một cách tốt đẹp để làm điều đó. Tại một số điểm, bạn có thể mong đợi thất bại. Java thực hiện điều này với các socket. Giả định cơ bản là một cuộc gọi sẽ thành công, nhưng mọi người đều biết rằng một số thao tác có thể thất bại. Kết nối ổ cắm là một trong số đó. Vì vậy, người gọi bị buộc phải xử lý các thất bại. Đây là một thiết kế đẹp, bởi vì nó đảm bảo người gọi thực sự xử lý được lỗi và cung cấp cho người gọi một cách thanh lịch để đối phó với thất bại.

Trả về đối tượng kết quả

Đây là một cách tốt đẹp khác để xử lý này. Nó thường được sử dụng để phân tích cú pháp hoặc đơn giản là những thứ cần được xác nhận.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Đẹp, logic sạch cho người gọi là tốt.

Có một chút tranh luận khi nào nên sử dụng trường hợp thứ hai và khi nào nên sử dụng trường hợp thứ ba. Một số người tin rằng các ngoại lệ nên là ngoại lệ và bạn không nên thiết kế với khả năng ngoại lệ trong tâm trí, và sẽ luôn luôn sử dụng các tùy chọn thứ ba. Điều đó tốt Nhưng chúng tôi đã kiểm tra các ngoại lệ trong Java, vì vậy tôi thấy không có lý do gì để không sử dụng chúng. Tôi sử dụng các mức mở rộng đã kiểm tra khi giả định cơ bản là cuộc gọi sẽ thành công (như sử dụng ổ cắm), nhưng không thể thực hiện được và tôi sử dụng tùy chọn thứ ba khi cuộc gọi không rõ ràng nên sẽ thực hiện (như xác thực dữ liệu). Nhưng có nhiều ý kiến ​​khác nhau về điều này.

Trong trường hợp của bạn, tôi sẽ đi với void+ Exception. Bạn mong đợi tệp tải lên thành công, và khi không, nó đặc biệt. Nhưng người gọi buộc phải xử lý chế độ thất bại đó và bạn có thể trả về Ngoại lệ mô tả chính xác loại lỗi nào đã xảy ra.


5

Điều này thực sự đi đến việc một thất bại là đặc biệt hay dự kiến .

Nếu một lỗi không phải là điều bạn thường mong đợi, thì rất có thể người dùng API sẽ chỉ gọi phương thức của bạn mà không có bất kỳ xử lý lỗi đặc biệt nào. Ném một ngoại lệ cho phép nó làm nổi bong bóng lên một nơi mà nó được chú ý.

Nếu mặt khác, lỗi là phổ biến, bạn nên tối ưu hóa cho nhà phát triển đang kiểm tra chúng và try-catchcác mệnh đề hơi phức tạp hơn một chút so với một if/elifhoặc một switchloạt.

Luôn thiết kế API theo suy nghĩ của người đang sử dụng nó.


1
Trong trường hợp của tôi, như ai đó đã chỉ ra, sẽ là một thất bại đặc biệt mà người dùng API không thể tải lên tệp.
Accollativo

4

Đừng trả lại boolean nếu bạn không bao giờ có ý định quay lại false. Chỉ cần thực hiện phương pháp voidvà tài liệu là nó sẽ đưa ra một ngoại lệ ( IOExceptionphù hợp) khi thất bại.

Lý do cho điều này là nếu bạn trả về boolean, người dùng API của bạn có thể kết luận rằng anh ấy / cô ấy có thể làm điều này:

if (fileUpload(file) == false) {
    // Handle failure.
}

Điều đó sẽ không hoạt động tất nhiên; đó là, có một sự không thống nhất giữa hợp đồng phương thức của bạn và hành vi của nó. Nếu bạn ném một ngoại lệ được kiểm tra, người dùng phương thức phải xử lý lỗi:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

Nếu phương thức của bạn có giá trị trả về, việc ném ngoại lệ có thể gây ngạc nhiên cho người dùng. Nếu tôi thấy một phương thức trả về boolean, tôi khá tin rằng nó sẽ trả về true nếu thành công và sai nếu không, và tôi sẽ cấu trúc mã của mình bằng mệnh đề if-other. Nếu ngoại lệ của bạn sẽ dựa trên enum, bạn cũng có thể trả về giá trị enum, tương tự như windows HResults và giữ giá trị enum khi phương thức thành công.

Nó cũng gây phiền nhiễu khi có một ngoại lệ ném phương thức khi nó không phải là bước cuối cùng trong một thủ tục. Phải viết một luồng kiểm soát bắt và chuyển hướng đến khối bắt là một công thức tốt cho spaghetti, và nên tránh.

Nếu bạn đang tiếp tục với các ngoại lệ, thay vào đó hãy thử trả về void, người dùng sẽ tìm ra nó thành công nếu không có ngoại lệ nào được ném và không ai sẽ cố gắng sử dụng mệnh đề if-other để chuyển hướng điều khiển.


1
Enum chỉ là một ý tưởng để giúp người dùng API xử lý các loại lỗi khác nhau mà không cần phân tích thông báo lỗi. Vì vậy, theo quan điểm của bạn trong trường hợp của tôi, tốt hơn là trả lại enum và thêm enum cho "OK".
Accollativo
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.