Viết Code Dễ Hiểu Và Dễ Maintain - Viblo
Có thể bạn quan tâm
Giới thiệu
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ì.
Vậy thì chúng ta hãy cùng nhìn vào đoạn code sau:
let array = Array(1...100) array.filter { $0 % 2 == 0}Đ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ư:
let array = Array(1...100) array.filter { $0 % 2 == 0 && $0 < 40 && $0 > 10}Trông cũng rườm rà nhỉ? Nếu chỉnh nó lại như sau để trông rõ ràng hơn:
let array = Array(1...100) array.filter { $0 % 2 == 0 } .filter { $0 < 40 } .filter{ $0 > 10}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.
Filter
Hãy bọc điều kiện lọc vào một object như sau:
struct Filter<Element> { typealias Condition = (Element) -> Bool var condition: Condition }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:
extension Array { func filtering(_ filter: Filter<Element>) -> Array { self.filter(filter.condition) } }Ta có thể thêm vào các thành phần matching và không matching với các điều kiện được đưa vào, và kết quả trả về sẽ bao gồm cả hai loại output:
extension Filter { struct Result { private var matchingBlock: () -> Array<Element> private var restBlock: () -> Array<Element> var matching: Array<Element> { matchingBlock() } var rest: Array<Element> { restBlock() } init(matching: @escaping @autoclosure () -> Array<Element>, rest: @escaping @autoclosure () -> Array<Element>) { self.matchingBlock = matching self.restBlock = rest } } }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:
extension Array { func filtering(_ filter: Filter<Element>) -> Filter<Element>.Result { Filter.Result(matching: self.filter(filter.condition), rest: self.filter((!filter).condition)) } }Giờ ta có thể viết:
let subset = people.filter { $0.eyesColor == .blue }
thành:
let hasBlueEyes = Filter<Person> { $0.eyesColor == .blue } let subset = people.filtering(hasBlueEyes).matchingTrô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).matchingTa 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.
var inverted: Self { .init { !self.condition($0) } } func and(_ filter: Self) -> Self { .init { filter.condition($0) && self.condition($0) } } func or(_ filter: Self) -> Self { .init { filter.condition($0) || self.condition($0) } }Ta có thể viết lại đoạn code trên sử dụng các toán tử mới như sau:
let subset = people.filtering(hasBlueEyes.and(hasBlondeHair)).matching
Thậm chí có thể tạo thêm nhiều hơn nữa các function kết hợp:
struct Filter<Element> { typealias Condition = (Element) -> Bool var condition: Condition } extension Filter { static var all: Self { .init { _ in true } } static var none: Self { .init { _ in false } } var inverted: Self { .init { !self.condition($0) } } func and(_ filter: Self) -> Self { .init { filter.condition($0) && self.condition($0) } } func or(_ filter: Self) -> Self { .init { filter.condition($0) || self.condition($0) } } static prefix func ! (_ filter: Self) -> Self { filter.inverted } static func & (_ lhs: Self, _ rhs: Self) -> Self { lhs.and(rhs) } static func | (_ lhs: Self, _ rhs: Self) -> Self { lhs.or(rhs) } static func any(of filters: Self...) -> Self { Self.any(of: filters) } static func any(of filters: [Self]) -> Self { filters.reduce(.none, |) } static func not(_ filters: Self...) -> Self { Self.combine(filters.map { !$0 }) } static func combine(_ filters: [Self]) -> Self { filters.reduce(.all, &) } static func combine(_ filters: Self...) -> Self { Self.combine(filters) } }Việc kết hợp điều kiện filter bây giờ sẽ rất dễ dàng, ta có thể viết nó như sau:
let hasBlondeHair = Filter<Person> { $0.hairColor == .blonde } let hasBlueEyes = Filter<Person> { $0.eyesColor == .blue } let result1 = people.filtering(!hasBlueEyes) let result2 = people.filtering(hasBlueEyes & hasBlondeHair) let result3 = people.filtering(hasBlueEyes | !hasBlondeHair)Final Result
Để tạo ra những filter có thể tái sử dụng lại, ta có thể extend Filter và thêm vào các trường hợp phổ biến như sau:
extension Filter where Element == Person { static let brownEyes = Filter { $0.eyesColor == .brown } static let blueEyes = Filter { $0.eyesColor == .blue } static let darkEyes = Filter { $0.eyesColor == .dark } static let greenEyes = Filter { $0.eyesColor == .green } static let brunette = Filter { $0.hairColor == .brunette } static let blonde = Filter { $0.hairColor == .blonde } static let ginger = Filter { $0.hairColor == .ginger } static let darkHair = Filter { $0.hairColor == .dark } static func name(startingWith letter: String) -> Filter { Filter { $0.name.starts(with: letter) } } }Hãy quay lại đoạn code từ đầu mà ta muốn refactor:
let subset = people.filter { ($0.eyesColor == .green && $0.hairColor == .blonde) || ($0.eyesColor == .blue && $0.hairColor == .ginger) }Nó được viết lại thành:
let subset = people.filtering(Filter.greenEyes.and(.blonde) .or(Filter.ginger.and(.blueEyes))).matchingThậm chí filter những trường hợp phức tạp hơn:
let people = [ Person(name: "Eliott", eyesColor: .brown, hairColor: .blonde), Person(name: "Eoin", eyesColor: .brown, hairColor: .brunette), Person(name: "Michelle", eyesColor: .brown, hairColor: .brunette), Person(name: "Kevin", eyesColor: .blue, hairColor: .brunette), Person(name: "Jessica", eyesColor: .green, hairColor: .brunette), Person(name: "Thomas", eyesColor: .dark, hairColor: .dark), Person(name: "Oliver", eyesColor: .dark, hairColor: .blonde), Person(name: "Jane", eyesColor: .blue, hairColor: .ginger), Person(name: "Justine", eyesColor: .brown, hairColor: .dark), Person(name: "Joseph", eyesColor: .brown, hairColor: .brunette), Person(name: "Michael", eyesColor: .blue, hairColor: .dark) ] people.filtering(.combine(.name(startingWith: "E"), .brownEyes, .blonde)).matching // Eliott people.filtering(.any(of: .ginger, .blonde, .greenEyes)).matching // Eliott, Jessica, Oliver, Jane people.filtering(Filter.not(.name(startingWith: "J"), .brownEyes).and(.brunette)).matching // Kevin people.filtering(Filter.greenEyes.and(.blonde).or(Filter.ginger.and(.blueEyes))).matching // JaneHoặc filter cả Dog...
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, SammuelKế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.
Từ khóa » Code Là Gì Trong It
-
Viết Code Là Gì? Giải đáp Về Code Trong Lập Trình Là Gì? - Teky
-
Code Là Gì? Cần Kỹ Năng Gì để Trở Thành Coder Chuyên Nghiệp
-
Viết Code Là Gì? Cách Học Viết Code Online Hiệu Quả - Tino Group
-
Coder Là Gì – Nghề Nghiệp Tuy Khổ Nhưng đem Lại Thu Nhập Cao
-
Công Việc IT Có Nhất Thiết Phải Code - OneTerrace
-
Viết Code Là Gì? 6 Bước để Trở Thành Coder Cho Người Mới Bắt đầu
-
Viết Code Là Gì? Coder Là Gì Và Cách Học Code Online Tốt - Vietnix
-
Coder Là Gì? Triển Vọng Nghề Coder Trong Thời Công Nghệ 4.0
-
Code Là Gì? 5 Bước Quan Trọng để Viết Code Thành Công - Cloudify
-
Ngành Công Nghệ Thông Tin Có Gì Ngoài Code ?
-
Hướng Dẫn đọc Code để Trở Thành Software Engineer Giỏi | TopDev
-
Data Code Là Gì? - Từ điển CNTT
-
Program Code Là Gì? - Từ điển CNTT - Dictionary4it
-
Nghề Code Là Gì - Thả Rông