Những đoạn code tốt không chỉ là những dòng code giải quyết được vấn đề với hiệu suất cao, mà nó còn phải là một đoạn code có thể được đọc hiểu như ngôn ngữ tự nhiên. Dựa vào đó, những người không viết ra những dòng code này hay thậm chí những người không có kiến thức về lập trình khi đọc vào vẫn có thể hiểu được nhiệm vụ của nó là gì.
Đoạn code này dùng để lọc ra những số chẵn, và trông nó cũng khá dễ hiểu. Tuy nhiên vấn đề chỉ phức tạp hơn khi ta cho thêm một số điều kiện lọc vào, ví dụ như:
Thì vô tình lại làm tăng số vòng lặp lên hơn hẳn so với trước đây, vì bây giờ nó phải lọc lại thêm hai lần nữa cho từ mảng kết quả của lần lọc đầu tiên. Hãy cùng xem qua kết quả dưới đây:
Case Study
Hãy cùng tìm hiểu sâu hơn bằng ví dụ sau, ta tạo ra một struct Person định nghĩa các thuộc tính như name, eye color, hair color. Và các enum chứa các loại eye và hair color như bên dưới:
enum EyeColor {
case dark, blue, green, brown
}
enum HairColor {
case brunette, blonde, ginger, dark
}
struct Person {
var name: String
var eyesColor: EyeColor
var hairColor: HairColor
}
Ta có thể dễ dàng lọc ra những người với những màu mắt và màu tóc nhất định bằng việc kết hợp các điều kiện cho filter:
let people = [ ... ]
let subset = people.filter { ($0.eyesColor == .green && $0.hairColor == .blonde) || ($0.eyesColor == .blue && $0.hairColor == .ginger) }
Nhưng nó hơi khó đọc và khó maintain, vì vậy ta sẽ cùng chỉnh lại đoạn code trên sao cho nó dễ đọc và dễ maintain hơn.
Bây giờ struct Filter sẽ chứa điều kiện lọc cho generic element, nhờ đó ta có thể sử dụng cho nhiều đối tượng khác nhau mà không cần phải tạo ra những struct khác. Sau đó, hãy extend Array để sử dụng Filter:
Và ta bọc các thành phần lại trong một closure, nhờ đó, việc filter sẽ chỉ được thực hiện khi nó được gọi đến, ta sẽ chỉnh lại extension của Array trên như sau:
let subset = people.filter { $0.eyesColor == .blue }
thành:
let hasBlueEyes = Filter<Person> { $0.eyesColor == .blue }
let subset = people.filtering(hasBlueEyes).matching
Trông đã gọn gàng và dễ đọc hơn hẳn rồi phải không, tuy nhiên nếu ta cần thêm nhiều điều kiện hơn để lọc thì thế nào?
let hasBlondeHair = Filter<Person> { $0.hairColor == .blonde }
let hasBlueEyes = Filter<Person> { $0.eyesColor == .blue }
let subset = people.filtering(hasBlueEyes).mathing
.filtering(hasBlondeHair).matching
Ta lại vướng phải vấn đề về performance như đã đề cập ở đầu bài. Tuy nhiên ta sẽ sửa nó trong phần tiếp theo dưới đây, hãy cùng tiếp tục đọc nhé.
Thêm các function bổ trợ
Để hỗ trợ việc kết hợp các điều kiện filter lại với nhau, ta có thể tạo thêm các function bổ trợ cho nó, đây là những function cơ bản cho các toán tử như and, or.
enum DogBreed {
case pug
case husky
case boxer
case bulldog
case chowChow
}
struct Dog {
var name: String
var breed: DogBreed
}
let dog = [
Dog(name: "Rudolph", breed: .husky),
Dog(name: "Hugo", breed: .boxer),
Dog(name: "Trinity", breed: .pug),
Dog(name: "Neo", breed: .pug),
Dog(name: "Sammuel", breed: .chowChow),
Dog(name: "Princess", breed: .bulldog)
]
extension Filter where Element == Dog {
static let pug = Filter { $0.breed == .pug }
static let husky = Filter { $0.breed == .husky }
static let boxer = Filter { $0.breed == .boxer }
static let bulldog = Filter { $0.breed == .bulldog }
static let chowChow = Filter { $0.breed == .chowChow }
}
dog.filtering(.boxer).matching // Hugo
dog.filtering(.not(.husky, .chowChow)).rest // Rudolph, Sammuel
dog.filtering(Filter.boxer.or(.chowChow)).matching // Hugo, Sammuel
Kết luận
Đối với lập trình viên, việc viết code không chỉ dừng lại ở giải quyết vấn đề mà nó còn phải dễ đọc, dễ hiểu và dễ maintain, và nó có thể được đọc như ngôn ngữ tự nhiên. Việc sử dụng generics cũng là một trong những cách giúp ta có thể dễ dàng reuse và maintain. Trong quá trình tạo ứng dụng, việc viết ra những đoạn code tốt hơn nữa là thử thách thú vị mà các bạn lập trình viên luôn phải cố gắng để phát triển.