Bài 3.4 Guard - Develop in Swift - Fundamentals

Hầu hết lỗi (bug) thường ẩn náu trong những đoạn code phức tạp. Code càng dễ đọc, càng dễ phát hiện ra lỗi tiềm ẩn.

Trong bài học này, bạn sẽ học cách sử dụng guard để quản lý luồng điều khiển (control flow) hiệu quả hơn. Bạn sẽ học cách xử lý các giá trị không hợp lệ và trường hợp đặc biệt ngay từ đầu, thay vì phải loay hoay xử lý chúng xuyên suốt logic chương trình.

What You'll Learn - Bạn sẽ học được gì?

  • Cách sử dụng guard để viết code dễ đọc hơn.
  • Cách xây dựng function sử dụng guard để ngăn chặn các argument không hợp lệ.

Vocabulary - Từ vựng

  • guard: Một câu lệnh trong Swift dùng để kiểm tra một điều kiện. Nếu điều kiện sai, code trong khối else sẽ được thực thi. Nếu điều kiện đúng, code sau câu lệnh guard sẽ được thực thi. Giống như một người gác cổng, guard đảm bảo chỉ những giá trị hợp lệ mới được phép đi qua.
  • guard-let: Một dạng đặc biệt của guard dùng để kiểm tra xem một optional có chứa giá trị hay không. Nếu optional chứa giá trị, giá trị đó sẽ được unwrap và gán cho một constant mới. Nếu optional là nil, code trong khối else sẽ được thực thi.

Giới thiệu về guard

Khi bạn bắt đầu xây dựng những ứng dụng phức tạp hơn, bạn sẽ cần viết các function phụ thuộc vào một chuỗi các điều kiện đúng (true) trước khi thực thi. Nhưng càng nhiều điều kiện trong code, code càng khó đọc và debug - đặc biệt là khi câu lệnh if là công cụ duy nhất bạn có để quản lý control flow.

Bạn đã học rằng mỗi câu lệnh if sẽ đánh giá một biểu thức Boolean. Nếu kết quả là true, code trong câu lệnh sẽ được thực thi; ngược lại, nó sẽ bị bỏ qua. Nhưng điều gì sẽ xảy ra nếu bạn có nhiều câu lệnh if lồng nhau? Code của bạn sẽ bắt đầu hình thành thứ mà các lập trình viên gọi là "kim tự tháp doom" (pyramid of doom):

func singHappyBirthday() {
    if birthdayIsToday {
        if !invitedGuests.isEmpty {
            if cakeCandlesLit {
                print("Happy Birthday to you!")
            } else {
                print("The cake's candles haven't been lit.")
            }
        } else {
            print("It's just a family party.")
        }
    } else {
        print("No one has a birthday today.")
    }
}

Vấn đề của ví dụ này là gì? Với mỗi câu lệnh if, code thụt vào sâu hơn, khiến code khó đọc. Và mỗi câu lệnh else cách xa câu lệnh if tương ứng, khiến việc theo dõi logic chương trình trở nên khó khăn. Bạn có thể viết lại logic này để giảm hiệu ứng kim tự tháp trong khi vẫn sử dụng câu lệnh if:

func singHappyBirthday() {
    if !birthdayIsToday {
        print("No one has a birthday today.")
        return
    }

    if invitedGuests.isEmpty {
        print("It's just a family party.")
        return
    }

    if cakeCandlesLit == false {
        print("The cake's candles haven't been lit.")
        return
    }

    print("Happy Birthday to you!")
}

Tuy nhiên, có một cách tốt hơn để làm điều này. Thay vì sử dụng câu lệnh if, bạn có thể sử dụng câu lệnh guard để truyền đạt rõ ràng cho người đọc code rằng các điều kiện cụ thể phải được đáp ứng trước khi tiếp tục.

Sử dụng guard

Khối else của guard chỉ được thực thi nếu biểu thức đánh giá là false. Điều này ngược lại với câu lệnh if, câu lệnh if thực thi khối code nếu biểu thức đánh giá là true.

guard condition else {
    // false: thực thi code
}

// true: thực thi code

Với thiết kế này, bạn có thể viết function thoát sớm (return early) nếu nó không thể hoàn thành nhiệm vụ. Câu lệnh guard yêu cầu bạn thoát khỏi scope của function bằng cách sử dụng từ khóa return trong trường hợp else.

Bằng cách loại bỏ tất cả các điều kiện không mong muốn bằng câu lệnh guard và gọi return, bạn có thể di chuyển các kiểm tra điều kiện lên đầu function và đặt code bạn muốn chạy ở cuối.

Dưới đây là hai phiên bản của function divide, một phiên bản sử dụng câu lệnh if và một phiên bản sử dụng câu lệnh guard. Vì bạn không thể chia cho 0, mỗi phiên bản của function sẽ in kết quả nếu số chia không phải là 0.

func divide(_ number: Double, by divisor: Double) {
    if divisor != 0.0 {
        let result = number / divisor
        print(result)
    }
}

func divide(_ number: Double, by divisor: Double) {
    guard divisor != 0.0 else {
        return
    }
    let result = number / divisor
    print(result)
}

Code sử dụng guard sẽ return sớm nếu số chia được truyền vào là 0. Khi code đến dòng thực hiện phép chia, nó đã loại bỏ bất kỳ tham số nào không hợp lệ.

Ví dụ if ở trên cũng có thể được viết tương tự như ví dụ sử dụng cú pháp guard bằng cách đảo ngược điều kiện trong kiểm tra. Tuy nhiên, việc sử dụng guard trong những trường hợp này được coi là cách thực hành tốt hơn và dễ đọc hơn.

func divide(_ number: Double, by divisor: Double) {
    if divisor == 0.0 { return }
    let result = number / divisor
    print(result)
}

Guard với Optional

Trong một bài học trước, bạn đã học về optional và function nên thực hiện công việc nếu optional chứa giá trị. Khi bạn unwrap optional bằng cú pháp if-let để gán nó cho một constant, constant đó chỉ hợp lệ trong khối code nằm trong cặp dấu ngoặc nhọn.

if let eggs = goose.eggs {
    print("The goose laid \(eggs.count) eggs.")
}

// `eggs` không thể truy cập ở đây

Thay vì sử dụng if-let, bạn có thể sử dụng guard let để gán giá trị trong optional cho một constant có thể truy cập bên ngoài khối code nằm trong cặp dấu ngoặc nhọn.

guard let eggs = goose.eggs else { return }
// `eggs` có thể truy cập từ đây trở đi
print("The goose laid \(eggs.count) eggs.")

Lưu ý vị trí của câu lệnh else sau điều kiện. Vị trí này biểu thị rằng guard đang kiểm tra điều kiện true hoặc non-optional. Khi giá trị bạn đang cố gắng unwrap là nil, code trong khối else sẽ được thực thi - ngược lại, việc thực thi sẽ tiếp tục đến dòng sau dấu ngoặc nhọn đóng và giá trị đã được unwrap có sẵn để sử dụng.

Cả if letguard let đều cho phép bạn unwrap nhiều optional trong một câu lệnh. Tuy nhiên, việc sử dụng guard let sẽ làm cho tất cả các giá trị đã được unwrap có sẵn trong phần còn lại của function, thay vì chỉ trong khối code nằm trong cặp dấu ngoặc nhọn.

Kết luận

Việc sử dụng câu lệnh guard để di chuyển code điều kiện là một cách để cải thiện khả năng đọc của chương trình. Xuyên suốt khóa học này, bạn đã xem qua rất nhiều code do người khác viết. Bạn có thể nhận thấy rằng việc hiểu những gì đang xảy ra sẽ dễ dàng hơn nhiều nếu bạn có thể nhanh chóng xác định chức năng cốt lõi - thay vì phải tìm kiếm nó trong một biển code xác thực.