Early Exit (빠른 종료)

양진모·2023년 5월 7일
1
post-thumbnail

guard (Feat. if)

조건문

if statement

if Bool 타입 값 {
	// Bool 타입 값이 true일 경우
} else if Bool 타입 값 {
	// 첫번째 Bool 타입 값이 false면서
	// 두번째 Bool 타입 값이 true인 경우
} else {
	// 두 Bool 타입 값이 모두 false인 경우
}
  1. 구문의 블록 안의 값이 true, 블록 밖의 값이 false.
  2. else 구문을 반드시 사용하지 않아도 된다.
  3. 블록 종료 시 제어문 전환 명령어(return, break, continue, throw )를 사용하지 않아도 된다.
  4. else if 구문을 사용할 수 있다.

guard statement

guard Bool 타입 값 else {
	// Bool 타입 값이 false일 경우
	return // return, break, continue, throw 등의 제어문 전환 명령
}
// Bool 타입 값이 true일 경우
  1. 구문의 블록 안의 값이 false, 블록 밖의 값이 true.
  2. 구문의 용도가 Early Exit 이므로 else 구문을 반드시 사용해야 한다.
  3. else 블록 종료 시 제어문 전환 명령어(return, break, continue, throw )를 반드시 사용해야 한다. 그래서 제어문 전환 명령어를 쓸 수 없는 상황이라면 사용이 불가능하고 함수, 메서드, 반복문특정 블록 내부에 위치하지 않는다면 사용이 불가능히다.

반복문

if statement

for i in 0 ... 3 {
    if i == 1 {
        print(i)
        break
    } else {
        continue
    }
}
// 1

guard statement

for i in 0 ... 3 {
    guard i == 1 else { continue }
    print(i)
    break
}
// 1
  • if 구문은 i가 1이랑 같으면 i를 출력하고 나가고 아니면 계속해! 이런 느낌이고 guard 구문은 i가 1이랑 같으면 다음 코드로 가는데 아니면 계속해! 이런 느낌이다. 제목처럼 Early Exit (빠른 종료) 느낌이다.
  • guard 구문이 블록이 하나 더 적고, 들여쓰기를 하지 않아 조금 더 간결하게 사용할 수 있다.
  • if 구문은 continue를 사용하지 않아도 같은 결과를 도출하기 때문에 else 구문을 지우면 더 간결할 수 있다.
  • else if가 필요하면 if 구문을 사용해야 한다.

옵셔널 바인딩 (optional binding)

옵셔널 바인딩이란 강제로 옵셔널을 여는 방식(Force unwrapping)이 아닌 안전하게 확인을 해보고 unwrapping하는 방법입니다. 즉, if문을 이용하여 옵셔널에 할당된 값을 임시 변수 또는 상수에 할당을 해주는 방식입니다.

Force unwrapping: !를 써서 강제로 옵셔널 추출
Optional Binding: if let, guard let을 써서 옵셔널 추출

if let

func greet(_ person: [String: String]) {
    if let name: String = person["name"] {
        print("Hello \\(name)")
    } else {
        return
    }

    if let location: String = person["location"] {
        print("I hope the weather is nice in \\(location)")
    } else {
        print("I hope the weather is nice near you")
        return
    }
}

var personInfo: [String: String] = [String: String]()
personInfo["name"] = "eunseo"

greet(personInfo)
// Hello eunseo
// I hope the weather is nice near you

personInfo["location"] = "Korea"

greet(personInfo)
// Hello eunseo
// I hope the weather is nice in Korea

guard let

func greet(_ person: [String: String]) {
    guard let name: String = person["name"] else {
        return
    }
    
    print("Hello \(name)")
    
    guard let location: String = person["location"] else {
        print("I hope the weather is nice near you")
        return
    }
    
    print("I hope the weather is nice in \(location)")
}

var personInfo: [String: String] = [String: String]()
personInfo["name"] = "eunseo"

greet(personInfo)
// Hello eunseo
// I hope the weather is nice near you

personInfo["location"] = "Korea"

greet(personInfo)
// Hello eunseo
// I hope the weather is nice in Korea

공통점

  1. 조건문이다. Bool 타입 값으로 분기를 만들 수 있다.
  2. 옵셔널 바인딩 용도로 사용할 수 있다. 옵셔널 바인딩 된 상수지역상수처럼 사용 가능합니다.
  3. ,(쉼표)로 추가조건을 나열할 수 있다. AND 논리연산자와 같은 결과이고, && 로 치환할 수 있다.

차이점

  1. if 구문은 else 구문 선택, guard 구문은 else 구문 필수.
  2. if 구문은 else if 사용 가능.
  3. guard 구문은 else의 블록 종료 시 제어문 전환 명령어 사용 필수.
  4. if 구문은 블록 안, guard 구문은 블록 밖이 true로 서로 반대이다.

추가조건

func enterClub(name: String?, age: Int?) {
    guard let name: String = name, let age: Int = age, age > 19, name.isEmpty == false else {
        print("You are too young to enter the club")
        return
    }

    print("Welcome \(name)!")
}

enterClub(name: "jenny", age: 15) // You are too young to enter the club
enterClub(name: "eunseo", age: 25) // Welcome eunseo!

공통점에서 설명한 ,(쉼표)로 추가조건을 나열하는 모습. AND 논리연산자와 같은 결과이고, && 로 치환할 수 있다.

guard의 뜻인 보호

func print(text: String) {
    if text.isEmpty { return }
    print(text)
}
func print(text: String) {
    guard !text.isEmpty else { return }
    print(text)
}

이 경우 if 구문은 !가 없어 guard 구문보다 읽기는 쉽지만, return을 하는, Early Exit(빠른 종료)를 하는 경우이기 때문에 guard 구문을 쓰는 것이 더 알맞을 수 있다. !에 거부감이 든다면 String extension(예: isNotEmpty)을 만드는 것도 방법일 수 있다.

성공과 실패

func icon() -> UIImage {
    if let image = UIImage(named: "Photo") {
        return image
    } else {
        return UIImage(named: "Default")!
    }
}
func icon() -> UIImage {
    guard let image = UIImage(named: "Photo") else {
        return UIImage(named: "Default")!
    }
    return image
}

if-else 구문의 경우 이것과 저것, 50:50의 느낌을 주지만,
guard 구문의 경우 성공과 실패, 99:1의 느낌을 주기 때문에 이 경우 guard 구문이 더 알맞을 수 있다.

실패라는 것을 명확하게 전달

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

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

guard 구문에서 else 부분에  assertionFailure을 추가하기를 원하거나 필요로 할 가능성 이 더 높아집니다. 이는 가독성을 향상시키고 예상했던 것을 다른 개발자에게 명확하게 합니다.

최적화

일부 (최적화되지 않은) 코드를 사용하여 보호 문의 유용성을 설명하려고 합니다.

이름, 성, 이메일, 전화번호 및 비밀번호를 사용하여 사용자 등록을 위한 텍스트 필드의 유효성을 검사하는 UI가 있습니다.

textField에 유효한 텍스트가 없으면 해당 필드를 firstResponder로 만들어야 합니다.

최적화되지 않은 코드는 다음과 같습니다.

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

참고

https://stackoverflow.com/questions/32256834/swift-guard-let-vs-if-let

0개의 댓글