Cleaner Architecture on iOS

Hôm nay, tôi sẽ không dạy bạn bất cứ điều gì mới hoặc đột phá. Thay vào đó, tôi sẽ chỉ nhắc bạn về một điều bạn đã biết: nguyên tắc trách nhiệm duy nhất (SRP - Single Responsibility Principle). Cụ thể hơn, tôi muốn thảo luận về cách sử dụng nó đúng cách với Clean Architecture và tôi sẽ cho rằng bạn đã biết một chút công bằng về nó (nếu không, tôi khuyên bạn nên đọc các nguồn bên dưới và sau đó quay lại). Vì vậy, đừng quên nhắc nhở bản thân xem xét rõ ràng SRP khi đưa ra quyết định và hy vọng điều đó sẽ giúp chúng ta thiết kế ứng dụng tốt hơn.

SRP là gì?

Ở đây, tôi định nghĩa [1]: " Nguyên tắc trách nhiệm duy nhất là nguyên tắc lập trnhf máy tính nói rằng mọi module hoặc lớp phải có trách nhiệm đối với một phần chức năng được cung cấp bởi phần mềm, và trách nhiệm đó phải được gói gọn trong class. Tất cả các dịch vụ của nó phải được liên kết chặt chẽ với trách nhiệm của nó."

Một class chỉ nên có một lý do để thay đổi. Làm theo điều này sẽ giúp thực hiện các thay đổi dễ dàng hơn, giảm khớp nối, cải thiện khả năng testing, tăng tốc độ phát triển và nhiều hơn nữa! SRP cũng là một ý tưởng cơ bản về Clean Architcture (tất nhiên, nó cũng có thể áp dụng cho các phương pháp khác như MVC, MVVM, Reactive,vv.)

Tại sao không phải là MVC?

Câu trả lời rõ ràng là: bởi vì Massive View Controller. Tất nhiên, đây chỉ là một trò đùa - nhưng nó thật hài hước vì nó đúng. Nó gần như đặt ra câu hỏi: Tại sao MVC là kiến trúc mặc định trên iOS khi nó dẫn đến các vấn đề lớn nhưViewController lớn? Câu trả lời là nó không phải là vấn đề ngay từ đầu. Nếu Controller đồ sộ, thì nó không phải là lỗi của kiến trúc, nó là developer không sử dụng đúng cách. Bạn có thể viết một ứng dụng hoàn toàn sạch với MVC trong khi vấn đề của ViewController lớn có thể dễ dàng khắc phục, ví dụ:

  1. Không sử dụng chỉ một Controller cho một cảnh
  2. Ủy thác công việc cho các lớp worker/service.

Nói cách khác, bằng cách áp dụng SRP. Vì vậy, nếu MVC không phải là vấn đề, vậy thì tại sao lại sử dụng Clean Architecture ? Nếu MVC, như nó được sử dụng trên iOS, có một vấn đề, thì nó có nghĩa là nó khá mơ hồ và để lại nhiều quyết định cho người lập trình. Trách nhiệm của Controller là gì? Nếu bạn không cẩn thận, nó có thể thu được quá nhiều. Nhưng chúng ta đặt tất cả các trách nhiệm khác mà chúng ta cần ở đâu? Trong model? Làm thế nào để chúng ta cấu trúc đó? Kiến trúc không thực sự cho chúng ta biết điều này. Chúng ta tự mình làm điều đó, có nghĩa là có nhiều cơ hội để giới thiệu các bugs!

Nếu bạn không muốn nghĩ về tất cả những điều đó, bạn chỉ cần sử dụng Clean Architecture . Clean Architecture sẽ phân chia rõ ràng một số trách nhiệm giữa các class của nó: Presenters kết nối sự phân chia giữa UIlogic , các Interactors xử lý các use-cases của chúng ta, các Routers giúp chúng ta có được các cảnh mới, v.v. . Các trách nhiệm là rõ ràng và codebase của chúng ta có một chút sạch sẽ.

Vì vậy, bằng cách đơn giản sử dụng Clean Architecture , chúng ta đã giải quyết vấn đề? Bây giờ chúng ta có SRP trong codebase không? Vâng, không nhất thiết. Kiến trúc là vô cùng hữu ích nhưng nó không giải quyết được tất cả các vấn đề của chúng ta. Chúng ta vẫn cần suy nghĩ, đưa ra lựa chọn và nỗ lực để làm cho mọi thứ trở nên sạch sẽ hơn.

MIP

Clean Architecture đã trở nên khá phổ biến trên nền tảng Apples và vì một lý do chính đáng. Thậm chí có một số cách tiếp cận chúng ta có thể chọn, như VIPER và Clean Swift [3]. Hãy cùng xem một số dự án thực tế sử dụng Clean Swift (hoặc gọi tắt là CS).

Tôi đã thấy các codebases với các tương tác lớn và phức tạp rõ ràng không follow theo SRP. Tôi gọi nó là vấn đề tương tác lớn (Massive interactor Problem MIP). MIP có thể không tệ như một ViewController khổng lồ, vì Interactors không quan tâm đến UI, nhưng nó vẫn cố gắng làm quá nhiều. Nếu một lập trình viên có khả năng viết một ViewController khổng lồ, anh ấy chắc chắn cũng sẽ viết một Interactors lớn. Vấn đề là mặc dù chúng ta nghĩ rằng chúng ta sử dụng Clean Architecture, các trách nhiệm không được phân tách hợp lý và do đó không sạch sẽ. Để tránh nguyên nhân gốc rễ của vấn đề MVC / MIP, chúng tôi sẽ cần áp dụng SRP không thương tiếc.

Interactor chứa logic của ứng dụng, nhưng chỉ có một Interactor trên mỗi ViewController. Điều đó tốt cho những scenes nhỏ nhưng trừ khi bạn kết hợp nó với "không sử dụng một ViewController cho mỗi scene" mà tôi đã đề cập trước đó, nó có thể thoát khỏi tầm tay một cách nhanh chóng. Theo tôi, trong những scenes phức tạp, Interactor không nên làm gì thú vị. Không có thuật toán, không có cơ sở dữ liệu hoặc phân tích cú pháp, không có gì đòi hỏi một câu lệnh if lồng nhau hoặc một vòng lặp. Điều này thuộc về các lớp Worker. Trách nhiệm của Interactor sẽ là giao việc cho Worker của mình và chuyển kết quả cho Presenter. Hướng dẫn ban đầu về CS chỉ chứa một phần nhỏ về Worker và nhờ vào sự ngắn gọn của nó, tầm quan trọng của nó có thể không quá rõ ràng. Tất nhiên, điều này có ý nghĩa bởi vì trách nhiệm của Worker là cụ thể đối với ứng dụng của bạn và chứa logic của nó, vì vậy hướng dẫn không thực sự có nhiều điều để thêm. Tuy nhiên, tôi nghĩ tầm quan trọng của Worker cần được nhấn mạnh hơn.

Trong Clean Architecture, một Interactor đại diện cho một trường hợp sử dụng duy nhất. Điều đó có nghĩa là một trách nhiệm duy nhất phải là mối quan tâm duy nhất của một lớp. Bởi vì CS chỉ có một Interactor cho mỗi scene và một scene thường chứa một số use-cases, Interactor sẽ thu được nhiều trách nhiệm. Chúng ta có thể chọn bỏ qua điều này, điều này có thể được chấp nhận khi mọi thứ đơn giản, nhưng khi Interactor phát triển, nó sẽ ngày càng khó xử lý hơn. Tùy chọn khác là ủy thác những trách nhiệm đó cho Worker. Trong thiết lập này, Interactor sẽ tạo ra một số Worker và chỉ đạo họ. Vì vậy, nếu một request đến từ đầu vào, Interactor sẽ chỉ ủy thác nó cho Worker và sau đó chuyển kết quả cho Presenter. Nghe có vẻ quá đơn giản nhưng nó có một cải tiến lớn trên MIP.

Còn VIPER thì sao?

Tôi nghe nhiều người nói rằng VIPER rất phức tạp. Việc thiết lập nó mất quá nhiều thời gian, có quá nhiều class nhỏ và nghĩ về các developers mới! VIPER là một ví dụ điển hình về SRP vì VIPER rất nghiêm túc về SRP. Nếu bạn nghiêm túc về SRP, và cố gắng hết sức để áp dụng nó cho dự án MVC của bạn, bạn có thể nhận được một cái gì đó tương tự.

Clean Swift và VIPER rất giống nhau, cả hai đều dựa trên Clean Architecture. Sự khác biệt chính là chu kỳ VIP và số lượng Interactors trên mỗi scene. Nhưng nếu chúng tôi áp dụng nghiêm ngặt SRP cho CS, phân chia trách nhiệm của Interactor CS và tạo lớp Worker cho mọi trường hợp sử dụng, về cơ bản chúng tôi sẽ nhận được VIPER (nếu chúng tôi bỏ qua một vài chi tiết, Worker CS sẽ tương ứng với Interactor VIPER và CSs Presenter-Interactor với Presenter VIPERs). Trên thực tế, CS thậm chí còn phức tạp hơn nhờ chu kỳ VIP. Vâng, bạn đã nghe đúng, Clean Swift thậm chí còn phức tạp hơn VIPER! (Ít nhất là đối với các dự án lớn nơi chúng ta muốn tránh MIP và áp dụng SRP đúng cách, v.v.) VIPER không phức tạp như danh tiếng của nó có thể khiến bạn tin tưởng. Vì vậy, nếu bạn chưa có, đừng ngại tìm hiểu những gì nó cung cấp. Ngay cả khi bạn không kết thúc việc sử dụng nó trong dự án tiếp theo của mình, hãy hiểu nó và hiểu về lý do tại sao, nó làm mọi thứ theo cách nó làm là một công cụ khác trong hộp công cụ của bạn. Sử dụng nó để làm cho cuộc sống dễ dàng hơn cho bản thân và cho người khác.

Thêm về SRP

Dưới đây là một vài khuyến nghị:

  • Đừng trộn các lớp trừu tượng. Một tính toán cấp thấp ở giữa logic cấp cao là dấu hiệu của vi phạm SRP. Rốt cuộc, chúng ta không muốn đối phó với sự phức tạp của một cell khi làm việc với các mô hoặc cơ quan và ngược lại.

  • Hãy rõ ràng về trách nhiệm của class của bạn. Viết nó trong phần comment documentation trên header. Mô tả nên ngắn gọn trong khi bao gồm tất cả các trách nhiệm. Nhưng hãy cẩn thận với những từ như người “manager”. Managers làm gì? Điều đó quá mơ hồ và có thể dễ dàng bao gồm một số trách nhiệm mà bạn không nhận thấy. Khi bạn thực hiện thay đổi cho class, hãy đảm bảo rằng nó vẫn chính xác, nếu không thì cấu trúc lại. Nếu bạn nghĩ rằng bạn không có thời gian cho việc đó, thì đừng lo lắng, điều đó sẽ tiết kiệm thời gian trong thời gian dài vì bạn đang giảm nợ kỹ thuật và giữ cho dự án sạch hơn một chút.

  • SRP có thể được áp dụng ở nhiều cấp độ, từ Architecture chung đến các phương thức riêng lẻ. Nếu một phương thức thực hiện nhiều việc, hãy chia nội dung của nó thành các phương thức mới để thực thi các tác vụ một phần và gọi chúng từ phương thức ban đầu. Hãy nhớ rằng một phương thức chỉ nên chiếm một lớp trừu tượng.

  • Số lượng dòng lớn là một điềm báo cho thấy SRP bị hỏng. Nếu bạn nhận thấy rằng bạn đã cuộn trong một thời gian dài, thì có lẽ bạn có vấn đề. SRP là một số liệu tốt hơn so với số lượng dòng, vì vậy thay vì phát minh ra một số ma thuật mà bạn không thể vượt qua, hãy liệt kê các trách nhiệm. Nếu nó nhiều hơn một, chia nó. Các lớp và phương thức ngắn hơn cũng dễ bảo trì hơn, vì vậy hãy cố gắng giữ mọi thứ càng ngắn càng hợp lý (nhưng không ngắn hơn).

  • Hãy thực dụng. Thay vì chính xác theo các thông số kỹ thuật và hướng dẫn, sẽ hữu ích hơn để hiểu lý do tại sao họ đưa ra lựa chọn thiết kế và những gì họ đạt được. Sau đó đưa ra quyết định sáng suốt (chỉ cần không ngụy trang sự lười biếng và bào chữa là chủ nghĩa thực dụng, bởi vì chúng không phải!)

Để kết luận, đừng viết Controllers lớn. Hoàn toàn xem xét trách nhiệm và đảm bảo bạn không có quá nhiều ở một nơi (lý tưởng nhất là quá nhiều phương tiện hai). Đừng chỉ thêm mã vào một lớp vì nó dễ dàng hơn ngay bây giờ, nếu nó mâu thuẫn với trách nhiệm của nó. Tuân thủ SRP rất có thể sẽ tiết kiệm thời gian trong thời gian dài - vì vậy đừng kiếm cớ. Có thể phấn đấu để viết mã sạch hơn, thực dụng dẫn đến bất cứ nơi nào khác ngoài phần mềm tốt hơn?

Nguồn