+3

[Swift] - Write code as DIANA

VTV1 mới phát sóng chương trình Quốc gia Khởi nghiệp, khách mời là bác Đỗ Minh Phú, CT HĐQT Tập đoàn Vàng bạc đá quý DOJI, mà hồi trước đây là chủ của hãng DIANA 😃, mà nhắc đến DIANA là nhắc đến DRY 😃. Mà dân coder chúng ta thì viết 100 dòng code mỗi ngày, nhưng cũng sẽ xoá đi 50 dòng trong số đó. Vì sao thế nhỉ?. Là do chúng ta thường lặp lại những đoạn code có cùng tính năng. Áp dụng nguyên tắc DRY sẽ giúp code của chúng ta clean hơn, mà code tốt nhất là nothing at all 😃.

Lặp lại tính năng, không phải lặp code

Kịch bản chung trong phát triển phần mềm là đặt các tính năng tương tự nhau trong các phần của dự án. Hầu hết các ứng dụng iOS bao gồm các màn hình, phân tách nhau bắng cách sử dụng UIViewController. Thử tưởng tượng sản phẩm của chúng ta có tính năng đơn giản như sau:

Ứng dụng phải thông báo cho User biết lỗi về API! Bật lên một Cảnh báo khi mà thực hiện request API không thành công.

Rất đơn giản, gọi một function nhỏ trong UIViewController:

func presentError(title: String, message: String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let dismissAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertController.addAction(dismissAction)
        present(alertController, animated: true, completion: nil)
    }

OK, màn hình Login sẽ biết được khi nào thì hiện thị cảnh báo về lỗi API. Tuy nhiên, ứng dụng không chỉ phát sinh lỗi API không chỉ ở màn hình login mà còn ở nhiều màn hình khác, như màn hình Menu, List, Order ,v.v...Mỗi màn hình đều cần phải xử lý error. Với coder mới vào nghề có thói quen viết code trùng lặp ở khắp mọi nơi, vi phạm nguyên tắc DRY.

Làm thế nào để tránh duplicate code

Đầu tiên chúng ta nên gắn một extension cho UIViewController, như sau:

extension UIViewController {
    func presentError(title: String, message: String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let dismissAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertController.addAction(dismissAction)
        present(alertController, animated: true, completion: nil)
    }
}

Ưu điểm: Đơn giản là chúng ta đã thêm một function vào lớp UIViewController, bất kỳ lớp nào kế thừa lớp base này sẽ thực hiện được tính năng mong muốn. Nhược điểm: tính năng này sẽ tồn tại ở tất cả các view controller khác, mà có khi một trong số đó không cần đến tính năng này. Cách tốt hơn là chúng ta phải kiểm soát được phạm vi truy xuất.

Sử dụng subclassing?

Do chúng ta muốn chức năng presentError được sử dụng trong các screen nhất định, theo cấu trúc OOP, chúng ta nên tạo một subclass của UIViewController. Từ đó, sử dụng tính năng này ở screen chúng ta mong muốn.

class ErrorPresentingViewController: UIViewController {
  	func presentError(title: String, message: String) {
		let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
		let dismissAction = UIAlertAction(title: "OK", style: .default, handler: nil)
		alertController.addAction(dismissAction)
		present(alertController, animated: true)
	}
}

class LoginViewController: ErrorPresentingViewController {
}

Tốt hơn rồi, không những hạn chế được việc lặp code về tính năng chúng ta muốn thực sự thực thi. Tuy nhiên, có vấn đề là, giờ chúng ta muốn thực hiện thêm một UIActivityIndicator. Theo như cách trên, chúng ta sẽ tạo thêm một ActivityIndicatorViewController với function presentIndicator. Lưu ý là, Swift không hỗ trợ đa kế thừa, không thể tạo được subclass kế thừa từ cả ErrorPresentingViewControllerActivityIndicatorViewController. Hoặc là chúng ta có thể tạo ErrorPresentingAndActivityIndicatorViewController, nghe thật dở hơi. Rõ ràng, đã chạm đến giới hạn của OOP. Liệu có cách khác không?

Sử dụng Protocols!

Swift được xây dựng là một ngôn ngữ lập trình theo hướng thủ tục (Protocol Oriented Programming (POP)). Hãy xem lại định nghĩa của Swift về protocols:

Một protocol định nghĩa một design plan của các phương thức, thuộc tính và các yêu cầu khác mà nó phù hợp với nhiệm vụ cụ thể hoặc một phần chức năng nào đó. Protocol này có thể được thực thi (adopted) bởi một class, hay structure hoặc enumration cung cấp một thực thi thực sự của những yêu cầu nào đó. Bất kỳ kiểu nào mà đáp ứng yêu cầu của protocol được gọi là phù hợp (conform) với protocol đó.

Như vậy, chúng ta giải quyết vấn đề trên như thế nào. Đầu tiên, khai báo một protocol:

protocol ErrorPresenting {
	func presentError(title: String, message: String)
}

Theo như định nghĩa trên, thường thì chúng ta sẽ thực thi protocol bởi class, struct, hoặc là enum và thực thi tại tại chính body của class...Tuy nhiên, vẫn không giải quyết được vấn đề về duplication. Bởi vậy, chúng ta sẽ sử dụng thêm protocol extension:

Protocol có thể được mở rộng để đưa ra thực thi các phương thức và thuộc tính nhằm phù hợp với các types khác nhau. Điều này cho phép bạn định nghĩa thêm behavior trong bản thân protocol, hơn là trong từng đáp ứng trong từng type riêng rẽ trong cả chức năng chung.

Đây là chính xác những gì chúng ta cần. Thử đoạn code sau:

protocol ErrorPresenting {
	func presentError(title: String, message: String)
}

extension ErrorPresenting where Self: UIViewController {
	func presentError(title: String, message: String) {
		let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
		let dismissAction = UIAlertAction(title: "OK", style: .default, handler: nil)
		alertController.addAction(dismissAction)
		present(alertController, animated: true)
	}
}

Bằng cách tạo ra extension trong protocol ErrorPresenting, tất cả các type đáp ứng sẽ tự động thực thi phương thức presentError mà không cần sửa đổi gì thêm. Khi chúng ta định nghĩa mở rộng protocol, chúng ta cần chỉ rõ các ràng buộc mà các type này phải phù hợp trước khi các phương thức và thuộc tính của extension đã available. Trong trường hợp này, chúng ta muốn hiện thị thông báo lỗi mà nó chỉ available trong UIViewController.

Tiếp theo, các viewcontroller muốn yêu cầu xử lý lỗi chỉ cần thực hiện conform to ErrorPresenting:

class LoginViewController: ErrorPresenting { }
class MenuViewController: ErrorPresenting { }

Như vậy, chỉ những view controller mà đáp ứng được protocol mới có quyền truy xuất method, rõ ràng là đã kiểm soát được code. Bài học rút ra là, với những feature tương tự nhau, nên:

  • tạo protocol
  • khai báo function trong extension protocol
  • conform protocol trong class

Cảm ơn bạn đã đọc bài dịch, cố gắng happy CODING, happy DIANA!

========================================================== Nguồn: https://blog.daftmobile.com/keep-things-dry-e90c07e9d678 ===

==========================================================


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí