Cú pháp bắt-thử-bắt


162

Tôi cố gắng hiểu điều xử lý lỗi mới trong swift 2. Đây là những gì tôi đã làm: Lần đầu tiên tôi tuyên bố một lỗi enum:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

Và sau đó tôi đã khai báo một phương thức đưa ra một lỗi (không phải là một ngoại lệ. Đó là một lỗi.). Đây là phương pháp:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

Vấn đề là từ phía gọi. Đây là mã gọi phương thức này:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

Sau khi dotrình biên dịch dòng nói Errors thrown from here are not handled because the enclosing catch is not exhaustive. Nhưng theo tôi nó là toàn diện vì chỉ có hai trường hợp trong SandwichErrorenum.

Đối với các câu lệnh chuyển đổi thông thường, swift có thể hiểu nó là toàn diện khi mọi trường hợp được xử lý.


3
Bạn không chỉ định loại lỗi mà bạn ném, vì vậy Swift không thể xác định tất cả các tùy chọn có thể
Farlei Heinen

Có cách nào để xác định loại lỗi không?
mustafa

Tôi không thể tìm thấy bất cứ điều gì trong phiên bản mới của cuốn sách Swift - chỉ có từ khóa ném ngay bây giờ
Farlei Heinen

Làm việc cho tôi trong một sân chơi không có lỗi hoặc cảnh báo.
Fogmeister

2
Sân chơi dường như cho phép docác khối ở cấp cao nhất không bị cạn kiệt - nếu bạn bọc công việc trong một chức năng không ném, nó sẽ tạo ra lỗi.
Sam

Câu trả lời:


267

Có hai điểm quan trọng đối với mô hình xử lý lỗi Swift 2: tính toàn diện và khả năng phục hồi. Cùng nhau, họ thực hiện theo do/ catchtuyên bố của bạn cần nắm bắt mọi lỗi có thể xảy ra, không chỉ những lỗi bạn biết bạn có thể ném.

Lưu ý rằng bạn không khai báo loại lỗi nào mà hàm có thể đưa ra, chỉ cho dù nó có ném lỗi hay không. Đó là một loại vấn đề không có một không hai: khi ai đó xác định chức năng cho người khác (bao gồm cả bản thân tương lai của bạn) sử dụng, bạn không muốn làm cho mọi khách hàng của chức năng của mình thích ứng với mọi thay đổi trong quá trình thực hiện của bạn chức năng, bao gồm những lỗi nó có thể ném. Bạn muốn mã gọi chức năng của bạn có khả năng phục hồi với sự thay đổi đó.

Vì chức năng của bạn không thể nói loại lỗi nào sẽ ném (hoặc có thể ném trong tương lai), nên các catchkhối bắt lỗi đó không biết loại lỗi nào có thể gây ra. Vì vậy, ngoài việc xử lý các loại lỗi bạn biết, bạn cần xử lý các loại lỗi bạn không sử dụng bằng một catchcâu lệnh phổ quát - theo cách đó nếu chức năng của bạn thay đổi tập hợp các lỗi mà nó gây ra trong tương lai, người gọi vẫn sẽ bắt lỗi lỗi.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

Nhưng đừng dừng lại ở đó. Hãy suy nghĩ về ý tưởng kiên cường này thêm một số. Cách bạn thiết kế bánh sandwich của mình, bạn phải mô tả lỗi ở mọi nơi bạn sử dụng chúng. Điều đó có nghĩa là bất cứ khi nào bạn thay đổi tập hợp các trường hợp lỗi, bạn phải thay đổi mọi nơi sử dụng chúng ... không thú vị lắm.

Ý tưởng đằng sau việc xác định các loại lỗi của riêng bạn là để cho phép bạn tập trung hóa những thứ như thế. Bạn có thể xác định một descriptionphương pháp cho các lỗi của mình:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try sudo"
        }
    }
}

Và sau đó, mã xử lý lỗi của bạn có thể yêu cầu loại lỗi của bạn mô tả chính nó - bây giờ mọi nơi bạn xử lý lỗi đều có thể sử dụng cùng một mã và xử lý các trường hợp lỗi có thể xảy ra trong tương lai.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

Điều này cũng mở đường cho các loại lỗi (hoặc tiện ích mở rộng trên chúng) hỗ trợ các cách báo cáo lỗi khác - ví dụ: bạn có thể có tiện ích mở rộng về loại lỗi của mình để biết cách trình bày UIAlertControllerbáo cáo lỗi cho người dùng iOS.


1
@rickster: Bạn thực sự có thể tái tạo lỗi trình biên dịch? Mã ban đầu biên dịch mà không có lỗi hoặc cảnh báo cho tôi. Và nếu một ngoại lệ không thể được ném ra, chương trình sẽ hủy bỏ error caught in main().- Vì vậy, trong khi tất cả những gì bạn nói nghe có vẻ hợp lý, tôi không thể tái tạo hành vi đó.
Martin R

5
Yêu cách bạn tách các thông báo lỗi trong một phần mở rộng. Cách thực sự tốt đẹp để giữ mã của bạn sạch sẽ! Ví dụ tuyệt vời!
Konrad77

Rất khuyến khích bạn tránh sử dụng trybiểu thức bắt buộc trong mã sản xuất vì nó có thể gây ra lỗi thời gian chạy và khiến ứng dụng của bạn gặp sự cố
Otar

@Otar nói chung là tốt, nhưng đó là một chủ đề nhỏ - câu trả lời không giải quyết bằng cách sử dụng (hoặc không sử dụng) try!. Ngoài ra, có thể cho là hợp lệ, các trường hợp sử dụng, an toàn cho các hoạt động khác nhau trong các hoạt động khác nhau của Swift trong Swift (unsrap, thử, v.v.) ngay cả đối với mã sản xuất - nếu thông qua điều kiện tiên quyết hoặc cấu hình mà bạn đã loại bỏ đáng kể khả năng thất bại, nó có thể hợp lý hơn để ngắn mạch đến thất bại tức thì hơn là viết mã xử lý lỗi không thể kiểm chứng.
gà trống

Nếu tất cả những gì bạn cần là hiển thị thông báo lỗi, đặt logic đó vào trong SandwichErrorlớp có ý nghĩa. Tuy nhiên, tôi nghi ngờ đối với hầu hết các lỗi, logic xử lý lỗi không thể được gói gọn như vậy. Điều này là do nó thường đòi hỏi kiến ​​thức về bối cảnh của người gọi (có thể khôi phục hoặc thử lại hoặc báo cáo lỗi ngược dòng, v.v.). Nói cách khác, tôi nghi ngờ mô hình phổ biến nhất sẽ phải phù hợp với các loại lỗi cụ thể nào.
tối đa

29

Tôi nghi ngờ điều này chưa được thực hiện đúng. Các Swift Lập trình Hướng dẫn chắc chắn dường như ngụ ý rằng trình biên dịch có thể suy ra các trận đấu đầy đủ 'giống như một câu lệnh switch. Nó không làm cho bất kỳ đề cập đến việc cần một vị tướng catchđể được toàn diện.

Bạn cũng sẽ nhận thấy rằng lỗi nằm ở trydòng chứ không phải ở cuối khối, tức là đến một lúc nào đó, trình biên dịch sẽ có thể xác định trycâu lệnh nào trong khối có các loại ngoại lệ chưa được xử lý.

Các tài liệu là một chút mơ hồ mặc dù. Tôi đã lướt qua video 'Có gì mới trong Swift' và không thể tìm thấy bất kỳ manh mối nào; Tôi sẽ tiếp tục cố gắng.

Cập nhật:

Bây giờ chúng tôi đã lên đến Beta 3 mà không có gợi ý về suy luận ErrorType. Bây giờ tôi tin rằng nếu điều này đã được lên kế hoạch (và tôi vẫn nghĩ rằng nó đã đến một lúc nào đó), công văn động trên các phần mở rộng giao thức có thể đã giết chết nó.

Cập nhật bản Beta 4:

Xcode 7b4 đã thêm hỗ trợ nhận xét tài liệu cho Throws:, nên sử dụng loại dữ liệu nào để ghi lại những lỗi nào có thể được đưa ra và lý do tại sao. Tôi đoán điều này ít nhất cung cấp một số cơ chế để truyền đạt lỗi tới người tiêu dùng API. Ai cần một hệ thống loại khi bạn có tài liệu!

Một cập nhật khác:

Sau khi dành một chút thời gian để hy vọng ErrorTypesuy luận tự động và tìm ra những hạn chế của mô hình đó, tôi đã thay đổi quyết định - đây là điều tôi hy vọng Apple thực hiện thay thế. Bản chất:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

Một bản cập nhật khác

Lý do xử lý lỗi của Apple hiện có sẵn tại đây . Cũng đã có một số cuộc thảo luận thú vị về danh sách gửi thư tiến hóa nhanh chóng . Về cơ bản, John McCall phản đối các lỗi đánh máy vì anh ta tin rằng hầu hết các thư viện sẽ kết thúc bao gồm cả một trường hợp lỗi chung và dù sao các lỗi đánh máy không có khả năng thêm nhiều mã vào mã nguồn (anh ta đã sử dụng thuật ngữ 'vô tội vạ'). Chris Lattner cho biết anh sẵn sàng gõ các lỗi trong Swift 3 nếu nó có thể hoạt động với mô hình phục hồi.


Cảm ơn các liên kết. Mặc dù không bị John thuyết phục: "nhiều thư viện bao gồm loại 'lỗi khác'" không có nghĩa là mọi người đều cần loại "lỗi khác".
Franklin Yu

Một phản ứng rõ ràng là không có cách đơn giản nào để biết loại lỗi nào sẽ xảy ra, cho đến khi nó xảy ra, buộc nhà phát triển phải bắt tất cả các lỗi và cố gắng xử lý chúng tốt nhất có thể. Nó khá là khó chịu, khá thẳng thắn.
William T Froggard

4

Swift lo lắng rằng tuyên bố trường hợp của bạn không bao gồm tất cả các trường hợp, để khắc phục nó, bạn cần tạo một trường hợp mặc định:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}

2
Nhưng đó không phải là vụng về sao? Tôi chỉ có hai trường hợp và tất cả chúng được liệt kê trong các catchbáo cáo.
mustafa

2
Bây giờ là thời điểm tốt cho một yêu cầu nâng cao bổ sung func method() throws(YourErrorEnum), hoặc thậm chí throws(YourEnum.Error1, .Error2, .Error3)để bạn biết những gì có thể được ném
Matthias Bauch

8
Nhóm trình biên dịch Swift tại một trong các phiên WWDC cho thấy rõ rằng họ không muốn có danh sách phạm vi của tất cả các lỗi có thể xảy ra, như Java '.
Sam

4
Không có lỗi Mặc định / mặc định; chỉ để lại một cái
bẫy

1
@Icaro Điều đó không làm tôi an toàn; nếu tôi "thêm một mục mới trong mảng" trong tương lai, trình biên dịch sẽ mắng tôi vì không cập nhật tất cả các mệnh đề bắt bị ảnh hưởng.
Franklin Yu

3

Tôi cũng thất vọng vì thiếu loại chức năng có thể ném, nhưng giờ tôi đã có được nó nhờ @rickster và tôi sẽ tóm tắt như thế này: giả sử chúng ta có thể chỉ định loại chức năng ném, chúng ta sẽ có thứ như thế này:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

Vấn đề là ngay cả khi chúng tôi không thay đổi bất cứ điều gì trong myFunctionThatThrows, nếu chúng tôi chỉ thêm một trường hợp lỗi vào MyError:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

chúng tôi bị làm phiền bởi vì làm / thử / bắt của chúng tôi không còn toàn diện, cũng như bất kỳ nơi nào khác mà chúng tôi gọi là các hàm ném MyError


3
Không chắc chắn tôi làm theo lý do tại sao bạn sai lầm. Bạn sẽ gặp lỗi trình biên dịch, đó là những gì bạn muốn, phải không? Đó là những gì xảy ra để chuyển đổi các câu lệnh nếu bạn thêm một trường hợp enum.
Sam

Theo một nghĩa nào đó, dường như rất có thể với tôi rằng điều này sẽ xảy ra với enums / do case, nhưng nó chính xác như nó sẽ xảy ra trong enums / switch, bạn nói đúng. Tôi vẫn đang cố gắng thuyết phục bản thân rằng lựa chọn của Apple để không gõ những gì chúng tôi ném là tốt, nhưng bạn đừng giúp tôi về vấn đề này! ^^
greg3z

Gõ thủ công các lỗi ném sẽ kết thúc như một mớ hỗn độn lớn trong các trường hợp không tầm thường. Các loại là một tập hợp của tất cả các lỗi có thể từ tất cả các câu lệnh ném và thử trong hàm. Nếu bạn đang tự duy trì lỗi enums thì điều này sẽ gây đau đớn. A catch {}ở dưới cùng của mỗi khối được cho là tồi tệ hơn mặc dù. Tôi hy vọng trình biên dịch cuối cùng sẽ tự động suy ra các loại lỗi có thể nhưng tôi không thể xác nhận.
Sam

Về mặt lý thuyết, tôi đồng ý trình biên dịch có thể suy ra các loại lỗi mà hàm ném. Nhưng tôi nghĩ nó cũng có ý nghĩa đối với nhà phát triển để viết chúng rõ ràng. Trong các trường hợp không tầm thường mà bạn đang nói đến, việc liệt kê các loại lỗi khác nhau có vẻ ổn đối với tôi: func f () ném ErrorTypeA, ErrorTypeB {}
greg3z

Chắc chắn có một phần lớn bị thiếu trong đó là không có cơ chế để giao tiếp các loại lỗi (ngoài các nhận xét về tài liệu). Tuy nhiên, nhóm Swift đã nói rằng họ không muốn danh sách các loại lỗi rõ ràng. Tôi chắc chắn rằng hầu hết những người đã xử lý các ngoại lệ được kiểm tra Java trong quá khứ sẽ đồng ý
Sam

1
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

Bây giờ xác thực số:

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }

-2

Tạo enum như thế này:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

Tạo phương thức như:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

Bây giờ kiểm tra lỗi có hay không và xử lý nó:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}

Gần nhưng không có xì gà. Hãy thử sửa khoảng cách và làm vỏ lạc đà enum.
Alec
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.