[Swift] Nắm vững Phạm vi Biến số (Scope) để Code Sạch và Dễ Debug

Chào các bạn, hôm nay chúng ta sẽ cùng tìm hiểu về một khái niệm quan trọng trong Swift: Phạm vi biến số (Scope). Hiểu rõ Scope sẽ giúp code của bạn trở nên sạch sẽ, dễ đọc, dễ bảo trì và đặc biệt là dễ debug hơn rất nhiều!

Scope là gì?

Scope của một biến số hoặc hằng số xác định nơi nó có thể được truy cập và sử dụng trong code. Nói đơn giản, một biến số chỉ "sống" và "hoạt động" trong một vùng nhất định.

Có 2 loại phạm vi:

  • Global scope (Phạm vi toàn cục): Biến số hoặc hằng số khai báo ở đây có thể được truy cập từ bất kỳ đâu trong chương trình.
  • Local scope (Phạm vi cục bộ): Biến số hoặc hằng số khai báo ở đây chỉ có thể được truy cập bên trong khối code mà nó được khai báo, ví dụ như trong hàm, vòng lặp, hoặc câu lệnh if.

Tại sao Scope lại quan trọng?

Hãy tưởng tượng bạn đang xây một ngôi nhà. Mỗi phòng có chức năng riêng, và bạn sẽ không muốn để đồ đạc lung tung khắp nơi, phải không? Scope cũng tương tự như vậy, nó giúp tổ chức code, tránh xung đột và tăng tính dễ đọc.

Ví dụ 1:

var globalName = "John" 

func sayHello() {
    print("Xin chào, \(globalName)!") // Truy cập được globalName
}

sayHello()

Trong ví dụ này, globalName được khai báo ngoài hàm, tức là trong global scope. Do đó, hàm sayHello() có thể truy cập và sử dụng nó.

Ví dụ 2:

func greet() {
    let localName = "Jane" // localName chỉ tồn tại trong hàm greet()
    print("Chào \(localName)!")
}

greet()
print(localName) // Lỗi! localName không tồn tại ở đây

Trong ví dụ này, localName được khai báo bên trong hàm greet(), tức là trong local scope của hàm. Vì vậy, code bên ngoài hàm không thể truy cập được localName.

Variable Shadowing: Che khuất biến số

Điều gì xảy ra khi bạn khai báo 2 biến số có cùng tên nhưng khác phạm vi?

let score = 100 // global scope

func updateScore() {
    let score = 200 // local scope, che khuất score ở global scope
    print("Score trong hàm: \(score)")
}

updateScore()
print("Score toàn cục: \(score)")

Kết quả:

Score trong hàm: 200
Score toàn cục: 100

Trong ví dụ này, score trong hàm updateScore() che khuất biến score ở global scope. Khi code trong hàm tham chiếu đến score, nó sẽ sử dụng score được khai báo bên trong hàm.

Variable Shadowing trong Initializer

Một trường hợp đặc biệt của Variable Shadowing xảy ra trong initializer của struct hoặc class. Khi bạn khai báo tham số của initializer trùng tên với property của struct hoặc class, tham số đó sẽ "che khuất" property.

Để truy cập property trong trường hợp này, bạn cần sử dụng từ khóa self.

Ví dụ:

struct Product {
    var name: String
    var price: Double

    init(name: String, price: Double) {
        self.name = name // self.name là property của Product
                           // name là tham số của initializer
        self.price = price
    }
}

Trong ví dụ này, self.nameself.price tham chiếu đến property của struct Product, còn namepricetham số của initializer.

Sử dụng self trong trường hợp này giúp:

  • Trình biên dịch Swift hiểu rõ code: self phân biệt rõ ràng property và tham số, tránh nhầm lẫn cho trình biên dịch.
  • Code dễ đọc hơn: self giúp người đọc code dễ dàng nhận biết bạn đang thao tác với property của struct hoặc class.

Variable Shadowing: Khi nào nên sử dụng?

Mặc dù Variable Shadowing có thể gây nhầm lẫn nếu lạm dụng, nó cũng có thể hữu ích trong một số trường hợp, giúp code gọn gàng và dễ đọc hơn:

1. Unwrap Optionals:

var userName: String? = "Anna"

if let userName = userName { // Sử dụng lại tên userName
    print("Tên người dùng: \(userName)") // userName đã được unwrap
} 

Trong trường hợp này, userName bên trong if let là một String bình thường, không phải String?, và nó "che khuất" optional userName ở bên ngoài.

2. Câu lệnh Guard:

func processOrder(itemId: Int?) {
    guard let itemId = itemId else { return } // Sử dụng lại tên itemId
    // itemId đã được unwrap, có thể sử dụng trực tiếp ở đây
    print("Xử lý đơn hàng \(itemId)")
}

Tương tự như if let, guard let kết hợp với variable shadowing giúp code gọn gàng và dễ hiểu hơn.

3. Phạm vi hẹp:

Nếu một biến số chỉ được sử dụng trong một phạm vi rất hẹp, ví dụ như trong một vòng lặp for, bạn có thể sử dụng variable shadowing để tránh phải nghĩ ra một cái tên khác cho biến số đó. Tuy nhiên, hãy cân nhắc kỹ trước khi sử dụng trong trường hợp này.

Các lưu ý

  • Khai báo biến số trong phạm vi hẹp nhất có thể.
  • Sử dụng tên biến rõ ràng và dễ hiểu.
  • Sử dụng Variable Shadowing một cách cẩn thận, chỉ khi nó thực sự giúp code rõ ràng hơn. Bên cạnh việc sử dụng self trong initializer, bạn cũng cần chú ý đến Variable Shadowing trong các trường hợp khác như unwrap optional, câu lệnh guard, và phạm vi hẹp. Luôn ưu tiên code rõ ràng, dễ hiểu và tránh gây nhầm lẫn cho người đọc.

Nắm vững Phạm vi biến số (Scope)Variable Shadowing là bước quan trọng để viết code Swift sạch, dễ bảo trì và ít lỗi. Hãy luyện tập và áp dụng vào các dự án của bạn nhé!