Nghệ thuật viết chương trình Hello World bằng Swift

Tác giả: Trần Thiện Khiêm

Giả sử hôm thứ 2 vừa rồi sếp giao cho task viết chương trình in ra dòng chữ “Hello World!”, sau một ngày cân nhắc lựa chọn các ngôn ngữ lập trình, công nghệ, mình quyết định chọn Swift để viết, vì ngôn ngữ này có tên trùng với tên ca sĩ mình yêu thích Taylor Swift.

Sau 2 ngày làm việc, cuối cùng mình cũng hoàn thành chương trình của mình, chương trình có nội dung như sau:

print(“Hello World!”)

Thật ra một Junior Dev thì sẽ viết như vậy, mà viết như vậy thì bài viết của mình có gì hấp dẫn nữa. Với cương vị của một Senior Developer, mình sẽ phải ứng dụng các kiến thức lập trình hướng đối tượng, phân tích thiết kế hệ thống để viết ra chương trình Hello World hoàn hảo.

Xem xét các đối tượng trong chương trình.

Đối tượng thứ nhất là đối tượng chứa logic business của ứng dụng, mình đặt tên là HelloWorldApplication cho khoẻ. Đây là lớp chứa quyết định chính của chương trình, là in ra dòng chữ Hello World!

class HelloWorldApplication {
 func run() {
 }
}
let application = HelloWorldApplication()
application .run()

Mới bước đầu nhìn đã thấy hoành tráng rồi.

Yêu cầu của nhiệm vụ lần này là in ra màn hình console dòng chữ “Hello World!”, nên mình cần thiết kế một lớp in ra dòng chữ này, nghe thật đơn giản đúng không? Nhưng không, hãy suy nghĩ về tính mở rộng của chương trình, giả sử một ngày nào đó trong tương lai, người ta không muốn in ra console nữa, mà muốn in lên một cửa sổ, in ra một tờ giấy, in ra một máy tính khác qua mạng thì sao, thế là mình nghĩ ra lớp đối tượng trừu tượng thứ nhất, Printer. Printer là một protocol cho phép in ra một chuỗi, mình chỉ quan tâm vậy thôi. Đó là cách mình khái quát hoá một đối tượng trong chương trình.

protocol Printer {
 func print(message: String)
}

Dependency và Dependency Injection

Lớp HelloWorldApplication của mình là lớp high level chứa logic của chương trình, sẽ cần 1 printer để in ra thông điệp. Do đó mình cần truyền đối tượng printer này vào trong lớp HelloWorldApplication, gọi là inject dependency.

Mình inject quả constructor như sau:

class HelloWorldApplication {
 private let printer: Printer
 init(printer: Printer) {
   self.printer = printer
 }
}

Bây giờ mình có thể xài đối tượng printer ở trong lớp HelloWorldApplication mà không cần quan tâm nó được cài đặt ra sao.

Viết kiểm thử đơn vị (unit test)

Trước khi đi implement lớp HelloWorldApplication, mình sẽ viết test case để kiểm tra xem chương trình có chạy đúng, không, theo phương thức Kiểm Thử trước tiên.

Để đơn giản mình viết 1 lớp MockPrinter như sau

class MockPrinter: Printer {
 var printedMessage: String?
 func print(message: String) {
   printedMessage = message
 }
}

Và chương trình của mình cũng chỉ cần có 1 test case

final class HelloWorldApplicationTest: XCTestCase {
 func testRunShouldPrintHelloWorldMessage() throws {
   let printer = MockPrinter()
   let application = HelloWorldApplication(printer: printer)
   application .run()
   XCTAssertEqual("Hello World!", printer.printedMessage)
 }
}

Do mình chưa code hàm run nên test này sẽ fail.

Viết hàm run

Mình vào viết hàm chính như sau

func run() {
 printer.print(message: “Hello World!”)
}

Bây giờ test của mình đã chạy đúng.

Implement lớp console Printer

Mình cần viết 1 lớp console printer như sau:

let printAlias = { print($0) }
class ConsolePrinter: Printer {
func print(message: String) {
   printAlias(message)
 }
}

Vì hàm print của mình đặt trùng tên của hàm print của Swift nên mình hack 1 xíu như trên. (hảo hack)

Viết chương trình chính và test

Bây giờ mình có thể hoàn thiện chương trình:

let application = HelloWorldApplication(printer: ConsolePrinter())
application .run()

HORRAY!! Chương trình đã in ra dòng Hello World! đúng như yêu cầu, và còn sử dụng nhiều kỹ thuật hiện đại, dễ mở rộng và khá đơn giản dễ hiểu.

Bây giờ muốn in cái dòng Hello World lên 1 UILabel chẳng hạn, ta chỉ cần extend UILabel để cài đặt cái protocol là xong, và cần thay ConsolePrinter thành đối tượng UILabel là được.

extension UILabel: Printer {
 func print(message: String) {
   self.text = message
 }
}

Đó là Senior sẽ viết vậy, nhưng nếu là super Senior Dev thì sẽ thêm 1 bước.

Refactor – Xoá những chi tiết dư thừa trong chương trình

Đây là bước khá quan trọng, bây giờ chúng ta sẽ xoá những chi tiết dư thừa trong chương trình, để cuối cùng đạt được kết quả thành 1 chương trình ngắn gọn như sau:

print(“Hello World!”)

Thật tuyệt vời!

Các bạn đang ở trình nào rồi? Mà dù là Fresher, Junior, Senior hay super Senior thì TopDev đều có nhiều jobs hot cho các bạn phát triển sự nghiệp. Tham khảo tại đây nè!

Hoặc bạn có thể xem thêm: