Refactoring - How to do with Large Class?

Như phần trước tôi đã trình bày Refactoring là gì, các khái niệm liên quan và khi nào cần phải Refactoring. Trong bài viết này tôi sẽ trình bày các phương pháp có thể áp dụng để refactor.

Tôi sẽ trình bày trong phần này những thứ mà khiến cho code trở nên không được clean và các phương pháp mà chúng ta làm để loại bỏ chúng. Trước tiên tôi sẽ trình bày về Bloaters.

Bloaters (tạm dịch là những thứ cồng kềnh) là những đoạn code, các phương thức và lớp mà được tăng lên, dài ra và làm cho đoạn code trở nên khó hiểu, khó sử dụng lại. Thông thường, những cái này không xuất hiện ngay lập tức mà nó tích lũy theo thời gian trong quá trình phát triển phần mềm, khi nhiều người cùng thêm vào nhưng không ai cố refactor chúng.

Dấu hiệu và triệu chứng

Một class mà chứng nhiều trường/phương thức hay quá nhiều dòng code.

Lý do

Các class bắt đầu thì thường nhỏ, không có vấn đề gì. Nhưng theo thời gian, chúng trở nên cồng kềnh cùng với sự phát triển của phần mềm.

Cũng giống như vấn đề về Long Methods, các lập trình viên thường nghĩ rằng cái giá, công sức bỏ ra cho việc thêm tính năng (thêm code) mới cho class hiện tại ít hơn là việc tạo ra một lớp mới cho tính năng đó, đương nhiên là nó đúng trong một số trường hợp.

Xử lý

Khi một class chứa quá nhiều chức năng, hãy nghĩ đến việc chia nhỏ nó ra:

Extract Class

Phương pháp này có thể giúp khi mà một phần hành vi của class có thể được tách nhỏ ra thành một thành phần riêng biệt. Problem Khi một class làm công việc đáng nhẽ ra là của lớp khác. Solution Thay vào đó, ta tạo một class mới và thêm vào những chức năng, thuộc tính liên quan đến chức năng của lớp đó. How to do it? Trước khi ta bắt đầu tiến hành tách class, hãy quyết định chính xác cách bạn muốn chia nhỏ chức năng của lớp.

  • Tạo một lớp mới chứa những chức năng có liên quan.
  • Tạo quan hệ giữa class cũ và class vừa mới tạo. Thường thường, quan hệ này là một chiều, tức là việc tái sự dụng class thứ hai không có bất cứ vấn đề gì mà ảnh hưởng đến class ban đầu. Tuy nhiên nếu bạn nghĩ việc tạo một quan hệ hai chiều là thực sự cần thiết, just do it 😃.
  • Sử dụng các phương pháp như Move FieldMove Method cho mỗi trường và phương thức mà bạn quyết định chuyển sang class mới. Với phương thức, hãy bắt đầu với những phương thức private trước để làm giảm rủi ro gây ra lỗi. Cố gắng làm từng tí một và kiểm tra kết quả sau mỗi lần di chuyển để đảm bảo chương trình đang chạy đúng tránh trường hợp lúc chuyển xong tạo ra một loạt các lỗi, chắc chắn sẽ làm bạn bối rối.
  • Sau khi bạn di chuyển xong, hãy nhìn lại một lần nữa class ban đầu và các class được tạo ra. Class cũ với chức năng đã thay đổi có thể được đổi tên để làm rõ hơn. Kiểm tra lại một lần nữa nếu bạn đã tạo mối quan hệ hai chiều cho những class đó thì liệu có thể xóa bỏ được nó không.
  • Ta cũng nên nhìn nhận việc truy cập class mới từ bên ngoài. Ta có thể ẩn toàn bộ class bằng việc set private cho nó, quản lý nó thông qua những methods từ class cũ. Ngoài ra, ta có thể set public cho nó bằng việc cho phép thay đổi giá trị trực tiếp. Quyết định set public hay private dựa vào độ an toàn cho những hành vi của class cũ khi những trường hợp không lường trước được xảy ra khi tạo class mới.

Extract Subclass

Phương pháp này có thể giúp chúng ta khi một phần của class lớn có thể được thực hiện theo một cách khác hoặc được sử dụng ở những trường hợp hiếm hoi. Problem Một class có những tính năng mà chỉ được sử dụng ở một số trường hợp nhất định. Solution Tạo ra một subclass và sử dụng trong những trường hợp đó. How to do it?

  • Tạo một subclass mới từ class cũ.
  • Nếu cần thêm dữ liệu gì để tạo object từ subclass thì ta tạo 1 hàm khởi tạo và thêm những dữ liệu cần thiết vào trong đó. Đừng quên gọi hàm constructor của class cha.
  • Tìm tất cả lời gọi hàm khởi tạo tới class cha. Nếu chức năng của subclass cần thiết thì thay thế hàm khởi tạo của class cha bằng hàm khởi tạo của class con.
  • Chuyển những hàm và các trường cần thiết từ class cha tới class con. Làm việc này thông qua Push Down MethodPush Down Field. Để đơn giản thì ta chuyển những hàm trước.
  • Sau khi tạo subclass xong, tìm tất cả những trường cũ mà không còn cần thiết ở class cha. Một ví dụ đơn giản: ta có 1 class Car, có trường là isElectricCar và có hàm refuel() dùng để sạc ga hay là điện cho ô tô phụ thuộc vào đó là loại xe gì. Sau khi refactor, trường isElectricCar bị xóa đi khỏi class Car và class ElectricCar được tạo ra sẽ có hàm refuel() riêng.

Extract Interface

Cách này được sử dụng khi có một tập hợp những hành vi, thuộc tính mà có thể được sử dụng tùy vào mục đích của từng chỗ sử dụng nó. Problem Khi mà nhiều class cùng sử dụng chung 1 số các hàm, thuộc tính. Solution Chuyển những phần chung vào một interface mới. How to do it?

  • Tạo một interface rỗng.
  • Khai báo các theo tác thường dùng trong interface đó.
  • Khai báo những class cần thiết sử dụng interface.
  • Đổi khai báo kiểu để sử dụng interface mới.

Duplicate Observed Data

Nếu một class được sử dụng cho giao diện, ta có thể chuyển một số hành vi, thuộc tính tới một object khác. Problem Dữ liệu ở mức cao hơn được lưu ở class phục vụ cho việc giao diện. Solution Có một cách tốt để chia dữ liệu vào nhiều class tách biệt, phải đảm bảo kết nối giữa class cha và class giao diện hoạt động.

Lợi ích

  • Việc tách những class to thành class nhỏ hơn giúp cho lập trình viên không phải nhớ một số lượng lớn các thuộc tính, phương thức của class đó.
  • Trong nhiều trường hợp, tách class giúp tránh việc lặp code.

Tham khảo: