10/12/2018, 19:20

Đặt tên function trong Swift

Xin chào các bạn, Trong bài viết này, chúng ta sẽ cùng tìm hiểu về một vấn đề mà mình nghĩ chúng ta hay tự hỏi hàng ngày. Đó là: Function này đặt tên thế nào? Mặc dù câu hỏi này có vẻ đơn giản, nhưng việc trả lời đúng khá là khó với mình, nhất là với vốn tiêngs anh không tốt của mình. Tuy ...

Xin chào các bạn,

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về một vấn đề mà mình nghĩ chúng ta hay tự hỏi hàng ngày. Đó là:

Function này đặt tên thế nào?

Mặc dù câu hỏi này có vẻ đơn giản, nhưng việc trả lời đúng khá là khó với mình, nhất là với vốn tiêngs anh không tốt của mình. Tuy nhiên hiệu quả mang lại nếu chúng ta trả lời đúng thì rất lơn, nó làm cho codebases của chúng ta sạch hơn và dễ sử dụng hơn, như chúng ta sẽ thấy.

Tầm quan trọng của APIs rõ ràng

Nếu bạn đã sử dụng một function từ thư viện của bên thứ ba, hãy giả sử để tạo Label ưa thích và bạn tìm thấy hai tùy chọn sau:

// A
static func makeLabel(withTitle title: String) -> FancyLabel
// B
static func configure(_ text: String) -> FancyLabel

Cái nào bạn sẽ cảm thấy thoải mái hơn? A hay B?

Trong trường hợp bạn vẫn còn do dự, tôi sẽ cung cấp cho bạn một gợi ý: lẽ thường cũng khiến tôi nghĩ đến lựa chọn A.

Tại sao lại chọn A?

Trước tiên, hãy phân tích lý do tại sao B không tốt như vậy: Tùy chọn B không cho chúng ta biết chính xác những gì chúng ta đang định cấu hình. Nó có phải là một instance có sẵn? Nó có tạo ra một cái mới không? Nó không nói cho chúng ta biết ý nghĩa của String mà nó mong đợi. Tất cả những gì chúng ta có thể nói trước là nó nhận được String và trả về FancyLabel, nhưng chúng ta không thể biết chính xác những gì nó làm. Có sự mơ hồ và thiếu thông tin, và đó là điều chúng ta nên tránh.

Option A, ngược lại thì rõ ràng về 3 điều sau:

  • Những gì nó cần để làm việc - một tiêu đề
  • Những gì nó làm - nó tạo một Label
  • Kết quả là gì - Label được tạo

Chúng tôi quan tâm đến những gì nó làm (hoặc được cho là phải làm), chứ không phải về cách nó làm điều đó. Chúng ta không quan tâm đến các hoạt động bên trong của chức năng. Đó là cách thức đóng gói hoạt động.

Việc sử dụng chức năng này trở nên rất tự nhiên khi chỉ cần có ba điều này rõ ràng, bởi vì theo cách này chúng ta có thể biết chức năng này diễn ra như thế nào mà không có sự hiểu lầm. Đơn giản phải không nào?

Chúng ta sẽ thấy rõ hơn khi chúng ta xem cách các function này được gọi:

let labelA = FancyLabel.makeLabel(withTitle: "Hello world") // clear

let labelB = FancyLabel.configure("Hello world") // not so clear

Bây giờ, điều gì sẽ xảy ra nếu chúng ta dịch ví dụ đó sang một dự án thực tế với nhiều nhà phát triển làm việc trên nó? Bạn muốn đặt tên function thế nào? A hay B? Bạn muốn đồng đội của mình đặt tên cho function của họ như thế nào? A hay B?

Bạn có thể nghĩ về nó như thế này: Mỗi lần bạn xác định một function, bạn sẽ xác định một interface mà người khác sẽ sử dụng trong tương lai. Ngay cả khi nó riêng tư và thậm chí nếu bạn nghĩ rằng sẽ không có ai sử dụng nó, điều quan trọng là nó phải rõ ràng. Bởi vì bất kỳ nhà phát triển tại bất kỳ thời điểm nào cũng có thể cần phải xử lý mã nội bộ của bất kỳ lớp nào trong dự án, ngay cả chính bạn.

Tóm lại, nếu bạn bỏ công sức đặt tên cho các chức năng của mình sao cho không có sự mơ hồ về đầu vào và đầu ra của chúng là gì và các function đấy định làm gì, thì bạn có thể đỡ được rất nhiều effort khi mà người khác dùng function của bạn. Họ sẽ không cần phải mở function đó và đọc để biết được những điều trên. Bằng cách này, chúng ta đã tiết kiệm thời gian và tránh những hiểu lầm dẫn tới bug. Việc xác định các interface rõ ràng trong mã của chúng ta đi kèm với lợi ích to lớn của một codebase dễ xử lý, sử dụng, định hình và duy trì.

Function Signatures trong Swift

Một trong những thay đổi căn bản nhất trong Swift, nếu chúng ta so sánh nó với Objective-C, và đặc biệt kể từ Swift 3.0, là cách thức hoạt động của Function Signature. Kể từ phiên bản đó, Swift không chỉ ít dài dòng hơn người tiền nhiệm mà còn rõ ràng hơn.

Hãy xem một ví dụ:

dateLabel.text = [formatter stringWithDate: [[Date alloc] init]]];

dateLabel.text = formatter.string(with: Date())

Chúng tôi thấy hai sự khác biệt chính ở đây. Đầu tiên, việc tạo ra instance của ngày dài hơn trong trường hợp đầu tiên, mặc dù điều đó chỉ liên quan đến tính dài dòng của Objective-C. Sau đó, chúng ta có thể quan sát rằng phiên bản Objective-C, bên cạnh rất nhiều dấu ngoặc phụ, bao gồm một từ bổ sung (Ngày) mà phiên bản Swift không có.

Sự thay đổi tinh tế - nhưng hiệu quả này thực sự có thể nhờ vào tính chất tĩnh của các loại trong Swift. Trong phiên bản Swift của hàm, bạn không thể truyền vào một đối tượng khác không phải là Ngày, vì trình biên dịch sẽ không cho phép bạn làm điều đó.

Thay đổi này là đơn giản, nhưng nó đã mở ra cánh cửa đến một thế giới mới về Function Signature. Đoạn code nói chung đã trở nên dễ chịu hơn nhiều để đọc, vì nó ngắn gọn hơn và giống với ngôn ngữ nói hơn.

Như đã đề cập, bước tiếp theo của chúng ta là nhằm mục đích làm cho Funtion ngắn nhất có thể, nhưng vẫn đủ rõ ràng để tránh sự mơ hồ.

Làm gọn

Cuối cùng, hãy phân tích một số ví dụ về các Function Signature khác nhau và xem làm thế nào chúng ta có thể làm cho chúng trông đẹp hơn bằng cách cắt tỉa, tức là bằng cách loại bỏ một số từ.

// Signature
func moveView(view: UIView, toPoint point: CGPoint) { ... }
// Usage
moveView(view: headerView, toPoint: .zero) // ⚠️ long and redundant

Có thể làm gọn thành:

// Signature
func move(_ view: UIView, to point: CGPoint) { ... }
// Usage
move(headerView, to: CGPoint.zero) //             
0