ไวยากรณ์ของ do-catch-catch


162

ฉันลองทำความเข้าใจข้อผิดพลาดใหม่ในการจัดการสิ่งที่รวดเร็ว 2 นี่คือสิ่งที่ฉันทำ: ฉันแรกประกาศข้อผิดพลาด enum:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

แล้วฉันก็ประกาศวิธีที่จะพ่นข้อผิดพลาด (ไม่ใช่คนยกเว้นมันเป็นข้อผิดพลาด) นี่คือวิธีการ:

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

    return sandwich
}

ปัญหามาจากด้านการโทร นี่คือรหัสที่เรียกใช้วิธีนี้:

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

หลังจากที่คอมไพเลอร์กล่าวว่าสายdo Errors thrown from here are not handled because the enclosing catch is not exhaustiveแต่ในความคิดของฉันมันครบถ้วนสมบูรณ์เพราะมีเพียงสองกรณีในSandwichErrorenum

สำหรับงบสวิทช์ปกตินั้นรวดเร็วสามารถเข้าใจได้ว่าหมดแรงเมื่อจัดการทุกกรณี


3
คุณไม่ได้ระบุประเภทของข้อผิดพลาดที่คุณโยนดังนั้น Swift จึงไม่สามารถระบุตัวเลือกที่เป็นไปได้ทั้งหมด
Farlei Heinen

มีวิธีการระบุประเภทของข้อผิดพลาดหรือไม่?
mustafa

ฉันไม่พบสิ่งใดในหนังสือ Swift เวอร์ชันใหม่ - เฉพาะคำสำคัญที่พ่นตอนนี้
Farlei Heinen

เหมาะสำหรับฉันในสนามเด็กเล่นที่ไม่มีข้อผิดพลาดหรือคำเตือน
Fogmeister

2
สนามเด็กเล่นปรากฏขึ้นเพื่ออนุญาตให้doบล็อกที่ระดับบนสุดที่ไม่หมดจด - ถ้าคุณห่อสิ่งที่ต้องทำในฟังก์ชั่นไม่โยนมันจะสร้างข้อผิดพลาด
Sam

คำตอบ:


267

มีจุดสำคัญสองประการสำหรับโมเดลการจัดการข้อผิดพลาด Swift 2: ความหมดจดและความยืดหยุ่น เมื่อรวมเข้าด้วยกันพวกเขาจะต้มลงไปในdo/ catchแถลงการณ์ของคุณซึ่งจำเป็นต้องตรวจสอบข้อผิดพลาดที่เป็นไปได้ทุกอย่าง

โปรดสังเกตว่าคุณไม่ได้ประกาศประเภทของข้อผิดพลาดที่ฟังก์ชั่นสามารถทำได้ มันเป็นปัญหาที่ไม่มีศูนย์เลย: เมื่อมีคนกำหนดฟังก์ชั่นให้ผู้อื่น (รวมถึงตัวเองในอนาคตของคุณ) ให้ใช้คุณไม่ต้องการให้ไคลเอนต์ของฟังก์ชั่นของคุณปรับตัวเข้ากับการเปลี่ยนแปลงทุกอย่างในการนำไปปฏิบัติ ฟังก์ชั่นรวมถึงข้อผิดพลาดที่มันสามารถโยน คุณต้องการโค้ดที่เรียกใช้ฟังก์ชั่นของคุณให้ยืดหยุ่นต่อการเปลี่ยนแปลงดังกล่าว

เนื่องจากฟังก์ชั่นของคุณไม่สามารถบอกได้ว่าเกิดข้อผิดพลาดแบบใด (หรืออาจจะเกิดขึ้นในอนาคต) catchบล็อกที่ตรวจจับข้อผิดพลาดจะไม่ทราบว่าเกิดข้อผิดพลาดประเภทใด ดังนั้นนอกเหนือจากการจัดการประเภทข้อผิดพลาดที่คุณทราบแล้วคุณจะต้องจัดการกับข้อผิดพลาดที่ไม่ได้มีcatchคำสั่งที่เป็นสากล- หากฟังก์ชันของคุณเปลี่ยนชุดข้อผิดพลาดที่จะเกิดขึ้นในอนาคตผู้โทรจะยังคงจับ ข้อผิดพลาด

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

แต่อย่าหยุดเพียงแค่นั้น ลองคิดเกี่ยวกับแนวคิดความยืดหยุ่นนี้อีกแล้ว วิธีที่คุณออกแบบแซนวิชคุณต้องอธิบายข้อผิดพลาดในทุก ๆ ที่ที่คุณใช้แซนวิช นั่นหมายความว่าเมื่อใดก็ตามที่คุณเปลี่ยนชุดกรณีความผิดพลาดคุณต้องเปลี่ยนทุกที่ที่ใช้มัน ... ไม่สนุกมาก

แนวคิดเบื้องหลังการกำหนดประเภทข้อผิดพลาดของคุณคือการให้คุณรวมศูนย์สิ่งต่าง ๆ เช่นนั้น คุณสามารถกำหนดdescriptionวิธีการสำหรับข้อผิดพลาดของคุณ:

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

จากนั้นรหัสการจัดการข้อผิดพลาดของคุณสามารถขอให้ประเภทข้อผิดพลาดของคุณอธิบายตัวเอง - ตอนนี้ทุกที่ที่คุณจัดการข้อผิดพลาดสามารถใช้รหัสเดียวกันและจัดการกรณีข้อผิดพลาดในอนาคตได้เช่นกัน

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

สิ่งนี้ยังเป็นการปูทางสำหรับประเภทข้อผิดพลาด (หรือส่วนขยาย) เพื่อสนับสนุนวิธีการรายงานข้อผิดพลาดอื่น ๆ - ตัวอย่างเช่นคุณสามารถมีส่วนขยายในประเภทข้อผิดพลาดของคุณที่รู้วิธีการนำเสนอUIAlertControllerสำหรับรายงานข้อผิดพลาดแก่ผู้ใช้ iOS


1
@ ทริคเตอร์: คุณสามารถสร้างข้อผิดพลาดของคอมไพเลอร์ใหม่ได้หรือไม่? รหัสต้นฉบับรวบรวมโดยไม่มีข้อผิดพลาดหรือคำเตือนสำหรับฉัน และถ้ามีการโยนข้อยกเว้นที่ไม่ตรงกันโปรแกรมจะยกเลิกด้วยerror caught in main()- ดังนั้นเมื่อสิ่งที่คุณพูดฟังดูสมเหตุสมผล
Martin R

5
รักที่คุณแยกข้อความผิดพลาดในส่วนขยาย เป็นวิธีที่ดีจริงๆในการรักษารหัสของคุณให้สะอาด! เยี่ยมมาก!
Konrad77

ขอแนะนำอย่างยิ่งให้คุณหลีกเลี่ยงการใช้การบังคับ - tryนิพจน์ในรหัสการผลิตเนื่องจากอาจทำให้เกิดข้อผิดพลาดรันไทม์และทำให้แอปพลิเคชันของคุณเกิดข้อผิดพลาด
Otar

ดี @Otar คิดว่าโดยทั่วไป แต่มันเป็นเรื่องเล็ก ๆ น้อย ๆ ปิดหัวข้อ - คำตอบไม่ได้อยู่ที่การใช้ try!(หรือไม่ได้ใช้) นอกจากนี้ยังมีเนื้อหาที่ถูกต้อง "ปลอดภัย" กรณีการใช้งานสำหรับการดำเนินการ "บังคับ" ต่างๆใน Swift (แกะ, ลอง, ฯลฯ ) แม้สำหรับรหัสการผลิต - หากผ่านเงื่อนไขหรือการกำหนดค่าคุณสามารถขจัดความเป็นไปได้ของความล้มเหลว มีเหตุผลมากขึ้นในการลัดวงจรสู่ความล้มเหลวในทันทีกว่าเขียนรหัสการจัดการข้อผิดพลาดที่ไม่สามารถทดสอบได้
rickster

หากคุณต้องการแสดงข้อความแสดงข้อผิดพลาดการใส่ตรรกะนั้นไว้ในSandwichErrorชั้นเรียน อย่างไรก็ตามฉันสงสัยว่ามีข้อผิดพลาดส่วนใหญ่ตรรกะการจัดการข้อผิดพลาดจึงไม่สามารถสรุปได้ นี่เป็นเพราะมันมักจะต้องมีความรู้เกี่ยวกับบริบทของผู้โทร (ไม่ว่าจะกู้คืนหรือลองใหม่หรือรายงานความล้มเหลวต้นน้ำ ฯลฯ ) ในคำอื่น ๆ ฉันสงสัยว่ารูปแบบที่พบบ่อยที่สุดจะต้องตรงกับประเภทข้อผิดพลาดเฉพาะอย่างไรก็ตาม
สูงสุด

29

ฉันสงสัยว่าสิ่งนี้เพิ่งดำเนินการไม่ถูกต้อง การเขียนโปรแกรมสวิฟท์คู่มือแน่นอนดูเหมือนจะบ่งบอกว่าคอมไพเลอร์สามารถสรุปการแข่งขันหมดจดเหมือนคำสั่งสวิทช์ ' มันไม่ได้พูดถึงการต้องการคนทั่วไปcatchเพื่อให้ครบถ้วนสมบูรณ์

คุณจะสังเกตเห็นว่าข้อผิดพลาดอยู่บนtryบรรทัดไม่ใช่จุดสิ้นสุดของบล็อกนั่นคือในบางจุดคอมไพเลอร์จะสามารถระบุได้ว่าtryคำสั่งใดในบล็อกมีประเภทข้อยกเว้นที่ไม่สามารถจัดการได้

เอกสารประกอบนั้นค่อนข้างคลุมเครือ ฉันอ่านผ่านวิดีโอ 'มีอะไรใหม่ในสวิฟท์' และไม่พบเบาะแสใด ๆ ฉันจะพยายามต่อไป

ปรับปรุง:

ขณะนี้เรามีเบต้าสูงถึง 3 รุ่นโดยไม่มีข้อบ่งชี้อนุมาน ErrorType ตอนนี้ฉันเชื่อว่านี่เป็นสิ่งที่วางแผนไว้ (และฉันยังคิดว่ามันเป็นบางจุด) การกระจายแบบไดนามิกในส่วนขยายของโพรโทคอลน่าจะฆ่าได้

อัปเดต Beta 4:

Xcode 7b4 เพิ่มการรองรับความคิดเห็นของ doc สำหรับThrows:ซึ่ง“ ควรใช้เพื่อทำเอกสารข้อผิดพลาดที่สามารถโยนทิ้งได้และทำไม” ผมคิดว่าอย่างน้อยยังมีบางกลไกในการสื่อสารกับผู้บริโภคข้อผิดพลาด API ใครต้องการระบบพิมพ์เมื่อคุณมีเอกสาร!

การปรับปรุงอื่น:

หลังจากใช้เวลาสักครู่เพื่อหวังการErrorTypeอนุมานอัตโนมัติและหาข้อ จำกัด ที่จะเป็นของรุ่นนั้นฉันเปลี่ยนใจ - นี่คือสิ่งที่ฉันหวังว่า Apple จะใช้แทน เป็นหลัก:

// 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

อีกหนึ่งการปรับปรุง

เหตุผลจัดการข้อผิดพลาดของแอปเปิ้ลอยู่ในขณะนี้ที่นี่ นอกจากนี้ยังมีการพูดคุยที่น่าสนใจในรายชื่อผู้รับจดหมายอย่างรวดเร็ว เป็นหลักจอห์นคอลตรงกันข้ามกับข้อผิดพลาดที่พิมพ์เพราะเขาเชื่อว่าห้องสมุดส่วนใหญ่จะจบลงรวมถึงกรณีข้อผิดพลาดทั่วไปต่อไปและข้อผิดพลาดที่พิมพ์ไม่น่าจะเพิ่มมากไปยังรหัสนอกเหนือจากสำเร็จรูป (เขาใช้คำว่า Chris Lattner กล่าวว่าเขาเปิดให้พิมพ์ข้อผิดพลาดใน Swift 3 หากสามารถทำงานกับโมเดลความยืดหยุ่นได้


ขอบคุณสำหรับลิงค์ ไม่ได้ชักชวนจาก John แม้ว่า: "ห้องสมุดหลายแห่งรวมถึงประเภท 'ข้อผิดพลาดอื่น ๆ ' ไม่ได้หมายความว่าทุกคนต้องการประเภท" ข้อผิดพลาดอื่น ๆ "
Franklin Yu

ตัวนับที่ชัดเจนคือไม่มีวิธีง่ายๆที่จะทราบว่าข้อผิดพลาดชนิดใดที่ฟังก์ชันจะส่งออกไปจนกระทั่งบังคับให้นักพัฒนาจับข้อผิดพลาดทั้งหมดและพยายามจัดการให้ดีที่สุดเท่าที่จะทำได้ มันค่อนข้างน่ารำคาญที่พูดตรงไปตรงมา
William T Froggard

4

Swift เป็นกังวลว่าคำสั่ง case ของคุณไม่ครอบคลุมทุกกรณีเพื่อแก้ไขคุณจำเป็นต้องสร้างเคสเริ่มต้น:

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
แต่นั่นไม่ใช่เรื่องที่น่าอึดอัดใจใช่ไหม ฉันมีสองกรณีเท่านั้นและทั้งหมดนี้อยู่ในcatchงบ
mustafa

2
ตอนนี้เป็นเวลาที่ดีสำหรับการร้องขอการปรับปรุงที่เพิ่มfunc method() throws(YourErrorEnum)หรือแม้กระทั่งthrows(YourEnum.Error1, .Error2, .Error3)เพื่อให้คุณรู้ว่าสิ่งที่สามารถโยน
Matthias Bauch

8
ทีมคอมไพเลอร์ Swift ที่หนึ่งในเซสชัน WWDC ทำให้ชัดเจนว่าพวกเขาไม่ต้องการรายการข้อผิดพลาดที่เป็นไปได้ทั้งหมดเช่น 'Java'
Sam

4
ไม่มีข้อผิดพลาดเริ่มต้น / เริ่มต้นคือ; เพิ่งปล่อยให้จับว่างเปล่า {} ตามที่ผู้โพสต์คนอื่นได้ชี้ไป
Opus1217

1
@Icaro นั่นไม่ทำให้ฉันปลอดภัย ถ้าฉัน "เพิ่มรายการใหม่ในอาเรย์" ในอนาคตคอมไพเลอร์ควรตะโกนใส่ฉันเพื่อไม่อัปเดตคำสั่ง catch ที่ได้รับผลกระทบทั้งหมด
Franklin Yu

3

ฉันก็รู้สึกผิดหวังกับการขาดฟังก์ชั่นที่สามารถโยนได้ แต่ตอนนี้ฉันต้องขอขอบคุณ @rickster และฉันจะสรุปแบบนี้: สมมติว่าเราสามารถระบุประเภทที่ฟังก์ชั่นการโยนเราจะมีดังนี้:

enum MyError: ErrorType { case ErrorA, ErrorB }

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

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

ปัญหาคือแม้ว่าเราจะไม่เปลี่ยนแปลงอะไรใน myFunctionThrows ถ้าเราเพิ่งเพิ่มกรณีข้อผิดพลาดไปยัง MyError:

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

เราเมาเพราะเราทำ / ลอง / จับไม่หมดแรงเช่นเดียวกับที่อื่น ๆ ที่เราเรียกว่าฟังก์ชั่นที่ทำให้ MyError


3
ไม่แน่ใจว่าฉันทำตามทำไมคุณเมา คุณต้องการรับข้อผิดพลาดของคอมไพเลอร์ซึ่งเป็นสิ่งที่คุณต้องการใช่ไหม นั่นคือสิ่งที่เกิดขึ้นในการสลับคำสั่งหากคุณเพิ่มเคส enum
Sam

ในแง่หนึ่งดูเหมือนว่าเป็นไปได้มากที่สุดสำหรับฉันที่จะเกิดเหตุการณ์นี้ขึ้นกับข้อผิดพลาด enums / do แต่มันเป็นสิ่งที่เกิดขึ้นใน enums / switch คุณพูดถูก ฉันยังคงพยายามโน้มน้าวใจตัวเองว่าทางเลือกของ Apple ที่จะไม่พิมพ์สิ่งที่เราโยนเป็นสิ่งที่ดี แต่คุณไม่ได้ช่วยฉันในเรื่องนี้! ^^
greg3z

การพิมพ์ข้อผิดพลาดที่โยนด้วยตนเองจะกลายเป็นความยุ่งเหยิงครั้งใหญ่ในกรณีที่ไม่สำคัญ ประเภทคือการรวมกันของข้อผิดพลาดที่เป็นไปได้ทั้งหมดจากการโยนและลองคำสั่งภายในฟังก์ชั่น หากคุณจัดการข้อผิดพลาดด้วยตนเอง enums สิ่งนี้จะเจ็บปวด catch {}ที่ด้านล่างของทุกบล็อกเป็น arguably แย่ลงแม้ว่า ฉันหวังว่าคอมไพเลอร์จะสรุปประเภทข้อผิดพลาดโดยอัตโนมัติซึ่งสามารถทำได้ แต่ฉันไม่สามารถยืนยันได้
Sam

ฉันเห็นด้วยกับคอมไพเลอร์ควรในทางทฤษฎีสามารถสรุปข้อผิดพลาดประเภทที่ฟังก์ชั่นพ่น แต่ฉันคิดว่ามันสมเหตุสมผลด้วยที่ dev จะเขียนลงไปเพื่อความชัดเจน ในกรณีที่ไม่สำคัญที่คุณกำลังพูดถึงการระบุประเภทข้อผิดพลาดที่แตกต่างกันดูเหมือนว่าเป็นเรื่องง่ายสำหรับฉัน: func f () พ่น ErrorTypeA, ErrorTypeB {}
greg3z

มีส่วนใหญ่ที่ขาดหายไปซึ่งไม่มีกลไกในการสื่อสารประเภทข้อผิดพลาด (นอกเหนือจากความคิดเห็น doc) อย่างไรก็ตามทีม Swift กล่าวว่าพวกเขาไม่ต้องการรายการประเภทข้อผิดพลาดอย่างชัดเจน ฉันแน่ใจว่าคนส่วนใหญ่ที่ได้จัดการกับข้อยกเว้นการตรวจสอบ Java ในอดีตจะเห็นด้วย😀
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
}

ตรวจสอบหมายเลข:

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

-2

สร้าง enum เช่นนี้:

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

สร้างวิธีการเช่น:

 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
}

ตอนนี้ตรวจสอบข้อผิดพลาดที่นั่นหรือไม่และจัดการกับมัน:

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

ปิด แต่ไม่มีซิการ์ ลองแก้ไขระยะห่างและสร้างเคสอูฐ
อเล็กซ์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.