Trả lại một boolean khi thành công hay thất bại là mối quan tâm duy nhất


15

Tôi thường thấy mình trả lại một boolean từ một phương thức, được sử dụng ở nhiều vị trí, để chứa tất cả logic xung quanh phương thức đó ở một nơi duy nhất. Tất cả các phương thức gọi (nội bộ) cần biết là liệu thao tác có thành công hay không.

Tôi đang sử dụng Python nhưng câu hỏi không nhất thiết phải dành riêng cho ngôn ngữ đó. Chỉ có hai lựa chọn tôi có thể nghĩ ra

  1. Tăng một ngoại lệ, mặc dù các trường hợp không phải là ngoại lệ, và hãy nhớ bắt ngoại lệ đó ở mọi nơi mà hàm được gọi
  2. Trả lại một boolean như tôi đang làm.

Đây là một ví dụ thực sự đơn giản thể hiện những gì tôi đang nói.

import os

class DoSomething(object):

    def remove_file(self, filename):

        try:
            os.remove(filename)
        except OSError:
            return False

        return True

    def process_file(self, filename):

        do_something()

        if remove_file(filename):
            do_something_else()

Mặc dù nó hoạt động, tôi thực sự không thích cách làm việc này, nó "có mùi", và đôi khi có thể dẫn đến rất nhiều ifs lồng nhau. Nhưng, tôi không thể nghĩ ra một cách đơn giản hơn.

Tôi có thể chuyển sang triết lý LBYL hơn và sử dụng os.path.exists(filename)trước khi thử xóa nhưng không có gì đảm bảo tệp sẽ không bị khóa trong thời gian đó (không thể nhưng có thể) và tôi vẫn phải xác định xem việc xóa có thành công hay không.

Đây có phải là một thiết kế "chấp nhận được" và nếu không thì cách nào tốt hơn để thiết kế nó?

Câu trả lời:


11

Bạn nên quay lại booleankhi phương thức / hàm hữu ích trong việc đưa ra các quyết định hợp lý.

Bạn nên đưa ra exceptionkhi phương thức / hàm không có khả năng được sử dụng trong các quyết định logic.

Bạn phải đưa ra quyết định về mức độ quan trọng của sự thất bại, và liệu nó có nên được xử lý hay không. Nếu bạn có thể phân loại thất bại là một cảnh báo, sau đó quay trở lại boolean. Nếu đối tượng rơi vào trạng thái xấu khiến các cuộc gọi trong tương lai không ổn định, thì hãy ném exception.

Một thực hành khác là trở về objectsthay vì kết quả. Nếu bạn gọi open, thì nó sẽ trả về một Fileđối tượng hoặc nullnếu không thể mở. Điều này đảm bảo các lập trình viên có một thể hiện đối tượng ở trạng thái hợp lệ có thể được sử dụng.

BIÊN TẬP:

Hãy nhớ rằng hầu hết các ngôn ngữ sẽ loại bỏ kết quả của hàm khi kiểu của nó là boolean hoặc số nguyên. Vì vậy, có thể gọi hàm khi không có gán tay trái cho kết quả. Khi làm việc với các kết quả boolean, luôn giả sử rằng lập trình viên đang bỏ qua giá trị được trả về và sử dụng giá trị đó để quyết định xem nó có phải là một ngoại lệ hay không.


Đó là một xác nhận về những gì tôi đang làm vì vậy tôi thích câu trả lời :-). Trên các đối tượng, mặc dù tôi hiểu bạn đến từ đâu nhưng tôi không thấy điều này giúp ích như thế nào trong hầu hết các trường hợp tôi sẽ sử dụng nó. Tôi muốn trở thành DRY vì vậy tôi sẽ trả lại đối tượng cho một phương thức duy nhất vì tôi chỉ muốn làm một điều với nó. Bây giờ tôi còn lại với cùng một mã mà tôi có bây giờ, lưu với một phương thức bổ sung. (cũng là ví dụ được đưa ra Tôi đang xóa tệp để một đối tượng tệp null không nói gì nhiều :-)
Ben

Xóa là khó khăn, bởi vì nó không được bảo đảm. Tôi chưa bao giờ thấy một phương pháp xóa tập tin ném một ngoại lệ, nhưng lập trình viên có thể làm gì nếu thất bại? Liên tục thử lại vòng lặp? Không, đó là một vấn đề hệ điều hành. Mã sẽ đăng nhập kết quả và di chuyển trên.
Phản ứng

4

Trực giác của bạn về điều này là chính xác, có một cách tốt hơn để làm điều này: monads .

Monads là gì?

Các đơn vị là (để diễn giải Wikipedia) một cách kết nối các hoạt động với nhau trong khi ẩn cơ chế xâu chuỗi; trong trường hợp của bạn, cơ chế xích là ifs lồng nhau . Ẩn điều đó và mã của bạn sẽ có mùi nhiều hơn .

Có một vài đơn vị sẽ làm điều đó ("Có thể" và "Hoặc") và may mắn cho bạn là một phần của một thư viện đơn vị trăn thực sự tốt đẹp!

Họ có thể làm gì cho mã của bạn

Dưới đây là một ví dụ sử dụng đơn vị "Hoặc" ("Có sẵn" trong thư viện được liên kết đến), trong đó một chức năng có thể trả về Thành công hoặc Thất bại, tùy thuộc vào những gì đã xảy ra:

import os

class DoSomething(object):

    def remove_file(self, filename):
        try:
            os.remove(filename)
            return Success(None)
        except OSError:
            return Failure("There was an OS Error.")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        do_something_else()
        mreturn(Success("All ok."))

Bây giờ, điều này có thể trông không khác nhiều so với những gì bạn có bây giờ, nhưng hãy xem xét mọi thứ sẽ như thế nào nếu bạn có nhiều hoạt động có thể dẫn đến Thất bại:

    def action_that_might_fail_and_returns_something(self):
        # get some random value between 0 and 1 here
        if value < 0.5:
            return Success(value)
        else:
            return Failure("Bad value! Bad! Go to your room!")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        yield action_that_might_fail(somearg)
        yield another_action_that_might_fail(someotherarg)
        some_val = yield action_that_might_fail_and_returns_something()
        yield something_that_used_the_return_value(some_val)
        do_something_else()
        mreturn(Success("All ok."))

Tại mỗi yields trong process_filehàm, nếu lệnh gọi hàm trả về Lỗi thì process_filehàm sẽ thoát ra, tại thời điểm đó , trả về giá trị Thất bại từ hàm bị lỗi, thay vì tiếp tục qua phần còn lại và trả vềSuccess("All ok.")

Bây giờ, hãy tưởng tượng làm những điều trên với ifs lồng nhau ! (Làm thế nào bạn sẽ xử lý giá trị trả lại!?)

Phần kết luận

Monads rất hay :)


Ghi chú:

Tôi không phải là lập trình viên Python - Tôi đã sử dụng thư viện đơn nguyên được liên kết ở trên trong tập lệnh tôi ninja cho một số tự động hóa dự án. Tuy nhiên, tôi tập hợp rằng nói chung, cách tiếp cận thành ngữ được ưa thích là sử dụng các ngoại lệ.

IIRC có một lỗi đánh máy trong tập lệnh lib trên trang được liên kết mặc dù tôi quên nơi đó là ATM. Tôi sẽ cập nhật nếu tôi nhớ. Tôi khác phiên bản của mình so với trang và tìm thấy: def failable_monad_examle():-> def failable_monad_example():- ptrongexample đã mất tích.

Để có được kết quả của một chức năng trang trí có sẵn (chẳng hạn như process_file), bạn phải nắm bắt kết quả trong một variablevà thực hiện variable.valueđể có được nó.


2

Một chức năng là một hợp đồng, và tên của nó sẽ gợi ý hợp đồng nào sẽ thực hiện. IMHO, nếu bạn đặt tên cho nó remove_fileđể xóa tệp và không làm như vậy sẽ gây ra ngoại lệ. Mặt khác, nếu bạn đặt tên cho nó try_remove_file, nó nên "thử" xóa và trả về boolean để biết liệu tệp đã bị xóa hay chưa.

Điều này sẽ dẫn đến một câu hỏi khác - nó nên remove_filehay try_remove_file? Nó phụ thuộc vào trang web cuộc gọi của bạn. Trên thực tế, bạn có thể có cả hai phương pháp và sử dụng chúng trong các kịch bản khác nhau, nhưng tôi nghĩ việc xóa tệp per-se có cơ hội thành công cao vì vậy tôi thích chỉ remove_filecó ngoại lệ đó khi thất bại.


0

Trong trường hợp cụ thể này có thể hữu ích để suy nghĩ về lý do tại sao bạn không thể xóa tệp. Giả sử vấn đề là tệp có thể tồn tại hoặc không tồn tại. Sau đó, bạn nên có một hàm doesFileExist()trả về đúng hoặc sai và một hàm removeFile()chỉ xóa tệp.

Trong mã của bạn, trước tiên bạn sẽ kiểm tra nếu tệp tồn tại. Nếu có, hãy gọi removeFile. Nếu không, sau đó làm những thứ khác.

Trong trường hợp này, bạn vẫn có thể muốn removeFileném ngoại lệ nếu tệp không thể bị xóa vì một số lý do khác, chẳng hạn như quyền.

Để tóm tắt, các ngoại lệ nên được ném cho những thứ tốt, đặc biệt. Vì vậy, nếu nó hoàn toàn bình thường mà tệp bạn đang cố xóa có thể không tồn tại, thì đó không phải là ngoại lệ. Viết một vị từ boolean để kiểm tra điều đó. Mặt khác, nếu bạn không có quyền ghi cho tệp hoặc nếu nó nằm trên một hệ thống tệp từ xa đột nhiên không thể truy cập, thì đó có thể là những điều kiện đặc biệt.


Điều đó rất cụ thể với ví dụ tôi đã đưa ra, điều mà tôi muốn tránh. Tôi chưa viết điều này, nó sẽ lưu trữ các tệp và ghi lại thực tế rằng điều này đã xảy ra trong cơ sở dữ liệu. Các tệp có thể được tải lại bất cứ lúc nào (mặc dù một khi các tệp được tải ít có khả năng được tải lại) thì có thể một tệp có thể bị khóa bởi một quá trình khác giữa kiểm tra và xóa. Không có gì đặc biệt về một thất bại. Python là tiêu chuẩn để không bận tâm kiểm tra trước và bắt ngoại lệ khi được nâng lên (nếu cần), tôi chỉ đơn giản là không muốn làm gì với nó lần này.
Bến

Nếu không có gì đặc biệt về sự thất bại, thì việc kiểm tra xem bạn có thể xóa tệp không là một phần hợp pháp trong logic chương trình của bạn. Nguyên tắc trách nhiệm duy nhất cho rằng bạn nên có chức năng kiểm tra và chức năng removeFile.
Dima
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.