Swift: Guard let vs if let


133

ฉันได้อ่านเกี่ยวกับ Optionals ใน Swift และฉันได้เห็นตัวอย่างที่if letใช้ตรวจสอบว่าตัวเลือกนั้นมีค่าหรือไม่และในกรณีที่เป็นเช่นนั้นให้ทำบางอย่างกับค่าที่ไม่ได้ใส่

อย่างไรก็ตามฉันได้เห็นว่าใน Swift 2.0 guard letมักใช้คำหลักเป็นส่วนใหญ่ ฉันสงสัยว่าif letถูกลบออกจาก Swift 2.0 หรือยังคงสามารถใช้งานได้

ฉันควรเปลี่ยนโปรแกรมของฉันที่มีif letถึงguard let?

คำตอบ:


165

if letและguard letตอบสนองวัตถุประสงค์ที่คล้ายกัน แต่แตกต่างกัน

กรณี "else" guardต้องออกจากขอบเขตปัจจุบัน โดยทั่วไปหมายความว่าจะต้องเรียกreturnหรือยกเลิกโปรแกรม guardใช้เพื่อให้ส่งคืนก่อนกำหนดโดยไม่ต้องมีการซ้อนฟังก์ชันที่เหลือ

if letรังขอบเขตและไม่ต้องการอะไรเป็นพิเศษ สามารถทำได้returnหรือไม่

โดยทั่วไปถ้าif-letบล็อกจะเป็นส่วนที่เหลือของฟังก์ชันหรือelseส่วนคำสั่งของมันจะมีreturnหรือยกเลิกคุณควรใช้guardแทน ซึ่งมักหมายความว่า (อย่างน้อยก็ในประสบการณ์ของฉัน) เมื่อมีข้อสงสัยguardมักจะเป็นคำตอบที่ดีกว่า แต่มีหลายสถานการณ์ที่if letยังคงเหมาะสม


39
ใช้if letเมื่อnon-nilเคสถูกต้อง ใช้guardเมื่อnilกรณีนี้แสดงถึงข้อผิดพลาดบางประเภท
BallpointBen

4
@BallpointBen ฉันไม่เห็นด้วยกับสิ่งนั้น มีหลายกรณีที่guardเหมาะสมแม้ว่าจะไม่มีข้อผิดพลาดก็ตาม บางครั้งมันก็หมายความว่าไม่มีอะไรจะทำ ตัวอย่างเช่นวิธีการอาจpositionTitle guard if let title = title else {return}ชื่ออาจเป็นทางเลือกซึ่งในกรณีนี้ไม่ใช่ข้อผิดพลาด แต่guard letก็ยังเหมาะสม.
Rob Napier

1
ใช่; ฉันหมายถึงยามให้ความคิดเห็น
Rob Napier

1
กล่าวอีกนัยหนึ่งคือใช้ "guard let" เมื่อรหัสมีความแน่ใจ 99% ว่าจะไม่ใช้เงื่อนไขอื่น ในทางกลับกัน "if let" เมื่อรหัสเป็น 50-50 (ตัวอย่าง) เพื่อใช้เงื่อนไขอื่น
Chino Pan

1
ตัวแปรที่ถูกผูกไว้if letจะมองเห็นได้ภายใน if letขอบเขตเท่านั้น ตัวแปรที่ถูกผูกไว้guard letจะมองเห็นได้ในภายหลัง ดังนั้นจึงควรใช้ยามเพื่อผูกค่าที่เป็นทางเลือกด้วย
boweidmann

106

ยามสามารถปรับปรุงความชัดเจน

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

func icon() -> UIImage {
    guard let image = UIImage(named: "Photo") else {
        return UIImage(named: "Default")! //This is your fallback
    }
    return image //-----------------you're always expecting/hoping this to happen
}

หากคุณเขียนโค้ดด้านบนด้วย if-let มันบ่งบอกถึงนักพัฒนาการอ่านว่ามันมากกว่า 50-50 แต่ถ้าคุณใช้ยามคุณจะเพิ่มความชัดเจนให้กับรหัสของคุณและมันก็บอกเป็นนัยว่าฉันคาดหวังว่าสิ่งนี้จะทำงานได้ 95% ของเวลา ... ถ้ามันล้มเหลวฉันไม่รู้ว่าทำไมมันถึง; มันไม่น่าเป็นไปได้มาก ... แต่จากนั้นให้ใช้ภาพเริ่มต้นนี้แทนหรืออาจแค่ยืนยันด้วยข้อความที่มีความหมายซึ่งอธิบายถึงสิ่งที่ผิดพลาด!

  • หลีกเลี่ยงguardเมื่อพวกเขาสร้างผลข้างเคียงยามที่จะนำมาใช้เป็นธรรมชาติไหล หลีกเลี่ยงยามเมื่อelseประโยคแนะนำผลข้างเคียง ยามกำหนดเงื่อนไขที่จำเป็นสำหรับรหัสเพื่อดำเนินการอย่างถูกต้องโดยเสนอทางออกก่อนกำหนด

  • เมื่อคุณทำการคำนวณที่มีนัยสำคัญในสาขาบวกให้ refactor จากifเป็นguardคำสั่งและส่งกลับค่าทางเลือกในelseประโยค

จาก: หนังสือ Swift Style ของ Erica Sadun

นอกจากนี้จากคำแนะนำข้างต้นและรหัสที่สะอาดจึงมีแนวโน้มที่คุณจะต้องการ / จำเป็นต้องเพิ่มคำยืนยันลงในคำสั่งการป้องกันที่ล้มเหลวซึ่งจะช่วยเพิ่มความสามารถในการอ่านและทำให้ชัดเจนสำหรับนักพัฒนาคนอื่น ๆ ว่าคุณคาดหวังอะไร

guard​ ​let​ image =UIImage(named: selectedImageName) else { // YESSSSSS
     assertionFailure("Missing ​​\(​selectedImageName​)​​ asset") 
     return
} 

guard​ ​let​ image =UIImage(named: selectedImageName) else { // NOOOOOOO
​     ​return 
}

จาก: หนังสือ Swift Style ของ Erica Sadun + ดัดแปลงบางส่วน

(คุณจะไม่ใช้คำยืนยัน / เงื่อนไขเบื้องต้นสำหรับif-lets ดูเหมือนจะไม่ถูกต้อง)

การใช้ยามยังช่วยปรับปรุงความชัดเจนโดยหลีกเลี่ยงปิรามิดแห่งการลงโทษ ดูคำตอบของ Nitin


Guard สร้างตัวแปรใหม่

มีความแตกต่างที่สำคัญอย่างหนึ่งที่ฉันเชื่อว่าไม่มีใครอธิบายได้ดี

อย่างไรก็ตามทั้งสองguard letและif let แกะตัวแปร

ด้วยguard letคุณกำลังสร้างตัวแปรใหม่ที่จะอยู่นอกelseคำสั่ง

เมื่อif letคุณไม่ได้สร้างตัวแปรใหม่ใด ๆ หลังจากคำสั่ง else คุณจะต้องป้อนรหัสบล็อกเท่านั้นหากตัวเลือกนั้นไม่ใช่ศูนย์ ตัวแปรที่สร้างขึ้นใหม่มีอยู่เฉพาะในบล็อกโค้ดเท่านั้นหลังจากนั้นไม่ได้!

guard let:

func someFunc(blog: String?) {

    guard let blogName = blog else {
        print("some ErrorMessage")
        print(blogName) // will create an error Because blogName isn't defined yet
        return
    }
    print(blogName) // You can access it here ie AFTER the guard statement!!

    //And if I decided to do 'another' guard let with the same name ie 'blogName' then I would create an error!
    guard let blogName = blog else { // errorLine: Definition Conflicts with previous value.
        print(" Some errorMessage")
        return
    }
    print(blogName)
}

if-let:

func someFunc(blog: String?) {


    if let blogName1 = blog {
        print(blogName1) // You can only access it inside the code block. Outside code block it doesn't exist!
    }
    if let blogName1 = blog { // No Error at this line! Because blogName only exists inside the code block ie {}
        print(blogName1)
    }
}

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับif letdo โปรดดู: เหตุใดการประกาศซ้ำการเชื่อมโยงที่เป็นทางเลือกจึงไม่สร้างข้อผิดพลาด


Guard ต้องการการออกจากขอบเขต

(กล่าวถึงในคำตอบของ Rob Napier ด้วย):

คุณต้องguardกำหนดไว้ใน func จุดประสงค์หลักคือการยกเลิก / ส่งคืน / ออกจากขอบเขตหากไม่เป็นไปตามเงื่อนไข:

var str : String?

guard let blogName1 = str else {
    print("some error")
    return // Error: Return invalid outside of a func
}
print (blogName1)

สำหรับif letคุณไม่จำเป็นต้องมีไว้ใน func ใด ๆ :

var str : String?    
if let blogName1 = str {
   print(blogName1) // You don't get any errors!
}

guard VS if

มันเป็นมูลค่า noting ว่ามันเป็นที่เหมาะสมมากขึ้นเพื่อดูคำถามนี้เป็นguard letVS if letและVSguardif

แบบสแตนด์อโลนifไม่ได้ทำแกะใด ๆ guardไม่ไม่สแตนด์อโลน ดูตัวอย่างด้านล่าง nilมันไม่ได้ออกจากต้นถ้ามีค่าเป็น ไม่มีค่าที่เป็นทางเลือก มันเพิ่งออกก่อนเวลาหากไม่เป็นไปตามเงื่อนไข

let array = ["a", "b", "c"]
func subscript(at index: Int) -> String?{
   guard index > 0, index < array.count  else { return nil} // exit early with bad index
   return array[index]
}

46

ควรใช้if-letเมื่อใดและควรใช้เมื่อguardใดมักเป็นคำถามเกี่ยวกับสไตล์

สมมติว่าคุณมีfunc collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Intอาร์เรย์ของไอเท็มที่เป็นทางเลือก ( var optionalArray: [SomeType]?) และคุณต้องส่งคืน0หากอาร์เรย์เป็นnil(ไม่ได้ตั้งค่า) หรือcountถ้าอาร์เรย์มีค่า (ถูกตั้งค่า)

คุณสามารถใช้งานได้เช่นนี้โดยใช้if-let:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        if let array = optionalArray {
            return array.count
        }
        return 0
    }

หรือเช่นนี้โดยใช้guard:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        guard let array = optionalArray else {
            return 0
        }
        return array.count
    }

ตัวอย่างมีหน้าที่เหมือนกัน

ไหนguardจริงๆส่องคือเมื่อคุณมีงานเช่นการตรวจสอบข้อมูลและคุณต้องการฟังก์ชั่นที่จะล้มเหลวในช่วงต้นถ้ามีอะไรที่ไม่ถูกต้อง

แทนที่จะวางซ้อนif-letกันเมื่อคุณเข้าใกล้การตรวจสอบความถูกต้องมากขึ้น "เส้นทางความสำเร็จ" และตัวเลือกที่ถูกผูกไว้ที่ประสบความสำเร็จในตอนนี้ล้วนอยู่ในขอบเขตหลักของวิธีการนี้เนื่องจากเส้นทางความล้มเหลวได้ส่งคืนทั้งหมดแล้ว


30

ฉันจะพยายามอธิบายประโยชน์ของคำสั่งยามด้วยรหัสบางส่วน (ไม่ได้เพิ่มประสิทธิภาพ)

คุณมี UI ที่คุณกำลังตรวจสอบความถูกต้องของช่องข้อความสำหรับการลงทะเบียนผู้ใช้ด้วยชื่อนามสกุลอีเมลโทรศัพท์และรหัสผ่าน

หาก textField ใด ๆ ไม่มีข้อความที่ถูกต้องควรกำหนดให้ฟิลด์นั้นก่อนตอบกลับ

นี่คือรหัสที่ไม่ได้เพิ่มประสิทธิภาพ:

//pyramid of doom

func validateFieldsAndContinueRegistration() {
    if let firstNameString = firstName.text where firstNameString.characters.count > 0{
        if let lastNameString = lastName.text where lastNameString.characters.count > 0{
            if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") {
                if let passwordString = password.text where passwordString.characters.count > 7{
                    // all text fields have valid text
                    let accountModel = AccountModel()
                    accountModel.firstName = firstNameString
                    accountModel.lastName = lastNameString
                    accountModel.email = emailString
                    accountModel.password = passwordString
                    APIHandler.sharedInstance.registerUser(accountModel)
                } else {
                    password.becomeFirstResponder()
                }
            } else {
                email.becomeFirstResponder()
            }
        } else {
            lastName.becomeFirstResponder()
        }
    } else {
        firstName.becomeFirstResponder()
    }
}

คุณสามารถดูด้านบนว่าสตริงทั้งหมด (firstNameString, lastNameString และอื่น ๆ ) สามารถเข้าถึงได้ภายในขอบเขตของคำสั่ง if เท่านั้น ดังนั้นจึงสร้าง "พีระมิดแห่งการลงโทษ" ขึ้นมาและมีปัญหามากมายรวมถึงความสามารถในการอ่านและความสะดวกในการเคลื่อนย้ายสิ่งต่างๆ (หากลำดับของฟิลด์มีการเปลี่ยนแปลงคุณจะต้องเขียนโค้ดนี้ใหม่เกือบทั้งหมด)

ด้วยคำสั่งยาม (ในรหัสด้านล่าง) คุณจะเห็นได้ว่าสตริงเหล่านี้มีให้ใช้งานภายนอก{}และใช้งานได้หากฟิลด์ทั้งหมดถูกต้อง

// guard let no pyramid of doom
func validateFieldsAndContinueRegistration() {

guard let firstNameString = firstName.text where firstNameString.characters.count > 0 else {
            firstName.becomeFirstResponder()
            return
        }
guard let lastNameString = lastName.text where lastNameString.characters.count > 0 else {
            lastName.becomeFirstResponder()
            return
        }
guard let emailString = email.text where 
        emailString.characters.count > 3 &&
        emailString.containsString("@") && 
        emailString.containsString(".") else {
            email.becomeFirstResponder()
            return
        }
guard let passwordString = password.text where passwordString.characters.count > 7 else {
            password.becomeFirstResponder()
            return
        }

// all text fields have valid text
    let accountModel = AccountModel()
    accountModel.firstName = firstNameString
    accountModel.lastName = lastNameString
    accountModel.email = emailString
    accountModel.password = passwordString
    APIHandler.sharedInstance.registerUser(accountModel)
}

หากลำดับของฟิลด์เปลี่ยนไปเพียงแค่เลื่อนบรรทัดของโค้ดขึ้นหรือลงเท่านี้ก็เรียบร้อย

นี่เป็นคำอธิบายที่ง่ายมากและกรณีการใช้งาน หวังว่านี่จะช่วยได้!


14

ความแตกต่างพื้นฐาน

ยามให้

  1. กระบวนการมีอยู่ในช่วงต้นจากขอบเขต
  2. ต้องการคะแนนที่มีอยู่เช่นผลตอบแทนโยนเป็นต้น
  3. สร้างตัวแปรใหม่ที่สามารถเข้าถึงขอบเขตได้

ถ้าปล่อยให้

  1. ไม่สามารถเข้าถึงขอบเขต
  2. ไม่จำเป็นต้องคืนคำสั่ง แต่เราเขียนได้

หมายเหตุ: ทั้งสองใช้เพื่อคลายตัวแปรเสริม


2

คำอธิบายที่ชัดเจนที่สุดที่ฉันเห็นอยู่ในGithub Swift Style Guide :

if เพิ่มระดับความลึก:

if n.isNumber {
    // Use n here
} else {
    return
}

guard ไม่ได้:

guard n.isNumber else {
    return
}
// Use n here

2

ยาม

  • guardคำสั่งที่ใช้ในการถ่ายโอนออกโปรแกรมควบคุมขอบเขตหากหนึ่งหรือมากกว่าเงื่อนไขที่ไม่ได้พบกัน

  • ค่าของเงื่อนไขใด ๆ ในguardคำสั่งต้องเป็นประเภทBool หรือประเภทที่เชื่อมโยงกับBoolไป เงื่อนไขนี้ยังสามารถเป็นการประกาศการผูกมัดที่เป็นทางเลือก

คำสั่งยามมีรูปแบบต่อไปนี้:

guard condition else {
    //Generally return
}

ถ้าปล่อยให้

  • เป็นที่นิยมเช่นกัน ตัวเลือกที่มีผลผูกพัน
  • if letสำหรับการเข้าถึงการใช้งานเราวัตถุที่เป็นตัวเลือก
if let roomCount = optionalValue {
    print("roomCount available")
} else {
    print("roomCount is nil")
}

1

ฉันเรียนรู้สิ่งนี้จากสวิฟต์กับบ๊อบ ..

ปกติอื่น - ถ้า

 func checkDrinkingAge() {
      let canDrink = true

     if canDrink {
        print("You may enter")
       // More Code
        // More Code
      // More Code

         } else {
         // More Code
    // More Code
    // More Code
    print("Let me take you to the jail")
          }
     }

ปัญหาเกี่ยวกับ Else-If

  1. วงเล็บที่ซ้อนกัน
  2. ต้องอ่านทุกบรรทัดเพื่อระบุข้อความแสดงข้อผิดพลาด

คำชี้แจง การป้องกันบล็อกป้องกันจะทำงานก็ต่อเมื่อเงื่อนไขเป็นเท็จและจะออกจากฟังก์ชันผ่านการส่งคืน หากเงื่อนไขเป็นจริง Swift จะละเว้นบล็อกป้องกัน ให้ทางออกก่อนเวลาและวงเล็บน้อยลง

func checkDrinkProgram() {
       let iCanDrink = true

           guard iCanDrink else {
        // if iCanDrink == false, run this block
         print("Let's me take you to the jail")
          return
        }

         print("You may drink")
           // You may move on
                  // Come on.
                 // You may leave
                // You don't need to read this.
                 // Only one bracket on the bottom: feeling zen.
       }

แกะตัวเลือกด้วย Else-If

คำสั่งยามไม่เพียงมีประโยชน์สำหรับการแทนที่บล็อกเงื่อนไขทั่วไปด้วยคำสั่ง else-if เท่านั้น แต่ยังเหมาะสำหรับการคลายตัวเลือกโดยการลดจำนวนวงเล็บ ในการเปรียบเทียบก่อนอื่นเรามาเริ่มวิธีแกะตัวเลือกหลายตัวด้วย else-if ขั้นแรกให้เราสร้างตัวเลือกสามตัวที่จะถูกแกะออก

var publicName: String? = "Bob Lee"
var publicPhoto: String? = "Bob's Face"
var publicAge: Int? = nil

ฝันร้ายที่เลวร้ายที่สุด

func unwrapOneByOne() {
         if let name = publicName {
              if let photo = publicPhoto {
                     if let age = publicAge {
                        print("Bob: \(name), \(photo), \(age)")
                                  } else {
                          print("age is mising")
                           }
                  } else {
                      print("photo is missing")
                         }
                  } else {
                        print("name is missing")
                         }
                  }

รหัสด้านบนใช้งานได้อย่างแน่นอน แต่ละเมิดหลักการ DRY มันเลวร้าย ให้เราทำลายมันลง +

ดีกว่าเล็กน้อย โค้ดด้านล่างสามารถอ่านได้มากกว่าด้านบน

func unwrapBetter() {
         if let name = publicName {
       print("Yes name")
                   } else {
               print("No name")
        return
      }

         if let photo = publicPhoto {
             print("Yes photo")
            } else {
           print("No photo")
       return
             }

        if let age = publicAge {
            print("Yes age")
                      } else {
                print("No age")
            return
                           }
     }

Unwrap with Guard คำสั่ง else-if สามารถแทนที่ด้วย guard ได้

 func unwrapOneByOneWithGuard() {
             guard let name = publicName else {
                  print("Name missing")
              return
                                        }

              guard let photo = publicPhoto else {
              print("Photo missing")
                return
                                            }

                  guard let age = publicAge else {
                   print("Age missing")
                                     return
                                                 }
                 print(name)
                 print(photo)
                 print(age)
         }

แกะตัวเลือกหลายตัวด้วย Else-If จนถึงตอนนี้คุณได้แกะตัวเลือกทีละรายการ Swift ช่วยให้เราสามารถแกะตัวเลือกหลาย ๆ ตัวพร้อมกันได้ หากหนึ่งในนั้นมีศูนย์ก็จะดำเนินการบล็อกอื่น

func unwrap() {
  if let name = publicName, let photo = publicPhoto, let age = publicAge {
    print("Your name is \(name). I see your face right here, \(photo), you are \(age)")
  } else {
    // if any one of those is missing
    print("Something is missing")
  }
}

โปรดทราบว่าเมื่อคุณแกะตัวเลือกหลายตัวพร้อมกันคุณจะไม่สามารถระบุได้ว่ามีศูนย์ใดบ้าง

Unwrap Multiple Optionals ด้วย Guard แน่นอนว่าเราควรใช้ guard over else-if +

func unwrapWithGuard() {
  guard let name = publicName, let photo = publicPhoto, let age = publicAge else {
    // if one or two of the variables contain "nil"
    print("Something is missing")
    return
  }

  print("Your name is \(name). I see your, \(photo). You are \(age).")
  // Animation Logic
  // Networking
  // More Code, but still zen
}

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