Giải pháp loại bỏ Legacy code trong một phần mềm lâu năm

Giải pháp loại bỏ Legacy code trong một phần mềm lâu năm

Xem nhanh

Spaghetti Code - "di sản" từ các thế hệ phát triển sản phẩm

​Spaghetti Code là một cụm từ ám chỉ những dòng code phi cấu trúc, rối rắm và rất khó bảo trì. Chúng ta có thể tưởng tượng nó rối như mấy cọng mì spaghetti vậy. Chúng có thể là những dòng code móc nối nhiều module/class với nhau, flow đi vòng vèo, cực kì khó đọc và khó hiểu, làm mất rất nhiều thời gian để đội ngũ phát triển có thể tiến hành bảo trì, nâng cấp hay phát triển tính năng.

Spaghetti code được xem là một anti pattern mà các developer nên tránh.

468c5cba 0c23 4794 8bb9 11e5162120fe

Nguyên nhân sản sinh ra Spaghetti code

  • Nguyên nhân chủ quan

    • Code được viết bởi những developer còn thiếu kinh nghiệm, chưa nắm các nguyên lý - quy tắc lập trình.
    • Code được implement nhưng không được review, hoặc có review nhưng chưa có tiêu chí rõ ràng.
  • Nguyên nhân khách quan

    • Requirement bị thay đổi liên tục, áp lực code chạy đua với deadline release.
    • Sau nhiều năm phát triển, kiến trúc code base không còn phù hợp để đáp ứng được sự phát triển sản phẩm ngày càng lớn và phức tạp hơn.

Ví dụ về Spaghetti code

Minh họa spaghetti code

Ví dụ, trong một dự án mình đang phụ trách, nó được phát triển đã lâu năm. Trong đó, phần frontend render UI (giao diện - HTML) thì nó sử dụng Smarty template file. Mỗi một màn hình của ứng dụng thường sẽ có tương ứng một file Smarty template cho UI và trong đó nó sẽ nhúng một file JavaScript để điều khiển các hoạt động trên màn hình.

Giải thích ví dụ trên:

  • Chúng ta có thể thấy template A đang sử dụng file Javascript 1 điều để điều khiển hành vi trên màn hình. Và vấn đề xảy ra tiếp theo là file template A đang include và sử dụng thêm file template X (file template X là template share được sử dụng chung, nó cũng có thể hiểu là component như hiện giờ).​
  • Template X đang sử dụng file JavaScript 2, nhưng file JavaScript 2 phụ thuộc vào template X bởi vì template X có một đoạn script khai báo một số biến, constant mà file JavaScript 2 đang sử dụng.​

Đọc đến đây thì chúng ta đã bắt đầu thấy khá rối rắm, code JavaScript không được phân tách rõ ràng, tính phụ thuộc vào trên file template nhiều, vì thế rất khó debug. Nhiều khi các developer đọc file JavaScript nhận thấy một biến vào đó, hoặc function nào đó mà không biết nó được định nghĩa nằm chỗ nào, hoặc giá trị của biến bị thay đổi bởi hàm nào. Có thể mất nhiều thời gian mới xác định ra được... "À thì ra nó là biến global được viết (inline JavaScript) trong file template".​

Refactor code

Hãy dừng việc viết Spaghetti code

Refactor code là cách chúng ta sẽ viết lại source code của một chức năng nào đó của chương trình nhưng không làm thay đổi về mặt hành vi sử dụng cũng như kết quả của chương trình. Mục đích của việc refactor code là làm cho source code trở nên dễ hiểu hơn, dễ tái sử dụng code và mở rộng hơn sau này.

Mình sẽ lấy một ví dụ thực tế trong dự án Garoon đã thực hiện để cải thiện phần code của ứng dụng Scheduler. Phạm vi của refactor là chức năng hiển thị dialog thông báo xác nhận facility (trang thiết bị - Ví dụ như phòng họp) đang bị chiếm dụng bởi các appointment khác. Bạn có thể xem đoạn clip dưới đây để thấy chức năng mình vừa nói trên sản phẩm.

Demo Scheduler - Confirm Facility Conflict

Dưới đây là code thực tế trước khi refactor

Legacy code trước khi được refactor

Có thể thấy đây là một phần code inline JavaScript trong một file template. Hàm showYN này thực hiện nhiệm vụ hiển thị confirm dialog có 2 button Yes - No. Ở đây code khá là rối.

  1. Tên hàm khó hiểu: rất khó mà hình dung ra được đây là một dialog để confirm
  2. Khó debug: JavaScript được viết trực tiếp trong template file
  3. Khó hiểu được code xử lý vấn đề gì: một hàm dài, cấu trúc điểu nhiều điều kiện, trộn chung với mã HTML, v.v...

Để xứ lý refactor cho chức năng này, chúng ta có thể áp dụng phương tách phần code của chức năng này thành component riêng. Cụ thể trong ví dụ này đã tách code thành 2 phần

  1. Dialog: nhiệm vụ chỉ xứ lý render UI của dialog

  2. Handler: nhiệm vụ điều khiển hoạt động hiển thị dialog và confirm (yes/no). VD như

    • Call API để kiểm tra xem facility có conflict hay không
    • Nếu có conflict thì hiển thị confirm dialog

Và đây là đoạn source code đã refactor

Code của Confirm Facility Conflict Dialog

Handler xử lý confirm

Unit Testing

Đi đôi với việc refactor thì mình cũng nên viết unit test cho những component mới được refactor.

Unit Testing hay còn gọi là kiểm thử đơn vị, là đơn vị nhỏ nhất trong tất cả các loại kiểm thử và sát với các developer nhất. Unit Test giúp chúng ta kiểm thử các đơn vị nhỏ nhất trong mã nguồn như function, component, v.v... ​Mục đích của unit test là nhằm kiểm tra mỗi đơn vị của mã nguồn của chương trình, các chức năng riêng rẽ hoạt động đúng như mong đợi.

Phân biệt các loại test

Các loại test trong phát triển phần mềm

Những lợi ích của unit test

  • Giúp phát hiện bug sớm hơn trong quá phát triển
  • Tiết kiệm thời gian ​đọc hiểu code
  • Nếu Unit Test được viết tốt thì nó có thể xem như là một document cho code của mình

Component unit test

Ví dụ minh họa unit test cho một component trên thực tế.

Tạm Kết

Đối với một product đòi hỏi sự phát triển và release liên tục để đáp ứng được nhu cầu của khách hàng thì việc tạm dừng phát triển tính năng để thay thế toàn bộ spaghetti code là một việc bất khả thi.​ Tùy vào từng dự án, tùy vào từng giai đoạn phát triển, chúng ta cần có một kế hoạch hợp lý để duy trì việc cải thiện chất lượng codebase. Chất lượng source code là nền tảng giúp sản phẩm có thể phát triển ổn định và mở rộng trong tương lai.

Mình xin tạm kết bài viết của mình tại đây, hẹn gặp lại các bạn ở các bài viết tiếp theo nhé!