Cải tiến mã nguồn

Khái niệm

Mỗi người có một khái niệm cải tiến mã nguồn (code refactoring) khác nhau, và đặc biệt khi chuyển ngữ sang tiếng(việt) để tìm một thuật ngữ chính xác càng khó hơn. Ở đây tôi xin chuyển nghĩa từ refactoring thành cải tiến và chọn định nghĩa của Martin Fowler:

Refactoring là thay đổi ở cấu trúc bên trong mà không làm thay đổi hành vi với bên ngoài của hệ thống.

Cải tiến là một quá trình cơ học, hình thức và trong nhiều trường hợp rất đơn giản để làm việc với mã của hệ thống đã tồn tại để chúng trở nên “tốt hơn”. Khái niệm “tốt hơn” là một khái niệm mang tính chủ quan, và không có nghĩa là luôn làm ứng dụng chạy nhanh hơn mà thường được hiểu là theo các kỹ thuật hướng đối tượng, tăng an toàn kiểu dữ liệu, cải thiện hiệu xuất , đễ đọc, dễ bảo trì và mở rộng.

Hiệu quả của cải tiến

Sản xuất phần mềm sẽ là không hiệu quả nếu như bạn không thể theo kịp thay đổi của thế giới. Nếu như chúng ta chỉ sản xuất ra các phần mềm trong một vài ngày thì đơn giản hơn rất nhiều. Nhưng trong thế giới này chúng ta có rất nhiều đối thủ cạnh tranh. Nên nếu bạn không cải tiến phần mềm của mình, khi đối thủ có một số tính năng hưu ích mới mà bạn không cập nhật thì sản phẩm của bạn nhanh chóng bị lạc hậu. Bởi thế là một lập trình viên bạn phải đón nhận và hành động một cách thích hợp với những thay đổi. Và khi thực hiện cải tiến mã là bạn đang làm điều đó.

Cải thiện thiết kế

Nếu không áp dụng cải tiến khi phát triển ứng dụng, thì thiết kế sẽ ngày càng tồi đi. Vì khi phát triển ứng dụng thì ta sẽ ưu tiên cho các mục tiêu ngắn hạn (đặc biệt khi áp dụng các quy trình phát triển linh hoạt), nên mã ngày càng mất đi cấu trúc. Một trong những tên của vấn đề này gọi là technical debt (nợ kỹ thuật). Khi xảy ra vấn đề thì rất khó để quản lý và dễ bị tổn thương  .Thế nên việc áp dụng cải tiến sẽ giúp cho mã giữ được thiết kế tốt hơn là ưu điểm quan trọng.

Mã dễ đọc hơn

Khi lập trình là chúng ta đang giao tiếp với máy tính để yêu cầu chúng làm điều mình muốn. Nhưng còn có người khác tham gia vào quá trình này là các lập trình viên khác hay chính chúng ta trong tương lai. Chúng ta biết khi lập trình thường sẽ có người phải đọc để kiểm tra xem có vấn đề với mã đó không hoặc để mở rộng hệ thống.

Nhưng có một vấn đề là khi làm việc, lập trình viên thường không nghĩ tới những người đó trong tương lai. Vậy thì trong trường hợp này cải tiến đóng vai quan trọng là giúp cải thiện thiết kế của hệ thống, từ đó cũng giúp đọc mã dễ hơn.

Lợi ích hệ quả

Từ những lợi ích cơ bản ở trên ta có thêm các lợi ích khác: do hệ thống hiện thời có một thiết kế tốt hơn và mã dễ hiểu hơn, từ đó thì việc mở rộng hệ thống dễ dàng hơn, khó bị tổn thương hơn, nên tốc độ phát triển hệ thống luôn được duy trì; mã và thiết kế dễ đọc hơn, từ đó giúp tìm ra lỗi dễ dàng hơn; vì những mục tiêu ngắn hạn lập trình viên có thể chấp nhận một lỗ hổng nào đó về công nghệ hay thiết kế mà hiện thời không gây ảnh hưởng gì tới hệ thống, nhưng khi hệ thống lớn dần thì những lỗ hổng này được tích tụ và làm cho hệ thống dễ bị tổn thương, thể nên việc cải tiến giúp nhanh chóng sửa những lỗ hổng này.

 

Thời điểm thực hiện

 

Khi thêm một chức năng mới

Khi thêm một chức năng mới, ta phải đọc lại mã để hiểu. Như vậy nếu lúc này ta thực hiện việc cải tiến, mã sẽ dễ hiểu hơn, cộng với đó là này ta cũng dễ dàng hiểu mã hơn vì mình là người đã đọc và thực hiện việc cải tiến. Một lý do khác là khi thực hiện việc cải tiến vào thời điểm này thì thiết kế của hệ thống sẽ tốt hơn, từ đó việc mở rộng cũng dễ dàng hơn.

 

Khi sửa lỗi

Khi sửa lỗi ta cũng phải đọc mã, và như vậy việc cải tiến làm mã dễ đọc hơn, có cấu trúc rõ ràng hơn từ đó dễ dàng phát hiện lỗi là điều cần thiết. Và bởi thế nếu bạn được gán là người sửa một lỗi nào đó thì bạn cũng thường được gán là người phải cải tiến mã.

 

Khi rà soát mã

Nhiều tổ chức thực hiện việc rà soát mã (code review).  Rà soát mã giúp cho các lập trình viên giỏi truyền lại cho các lập trình viên ít kinh nghiệm hơn, giúp cho mọi người viết mã rõ ràng hơn. Mã có thể là rất rõ ràng với tác giả, nhưng với người khác thì có thể không, bởi thế rà soát mã sẽ làm cho nhiều người đọc mã hơn. Có nhiều người đọc mã thì mã phải dễ đọc hơn và có nhiều ý tường hơn được trao đổi giữa các thành viên trong nhóm hơn. Bởi thế khi bạn thực hiện rà soát bạn phải đọc mã. Lần đầu bạn đọc, bạn bắt đầu hiểu mã. Lần tiếp theo bạn sẽ có nhiều ý tưởng hơn để cải tiến mã, từ đó bạn có thể thực hiện việc cải tiến.

 

Các “mã bẩn” thường gặp

Chúng ta đã biết cần thực hiện cải tiến khi nào, nhưng có một câu hỏi khác là mã như thế nào thì cần cải tiến? Khái niệm mã bẩn (code smell) là mã có thể sinh vấn đề một cách lâu dài, sẽ giúp ta phát hiện mã cần phải cải tiến. Sau đâu chúng ta sẽ liệt kê một số loại mã bẩn thường gặp:

  • Mã lặp

Là những đoạn mã xuất hiện nhiều hơn một lần trong một hoặc nhiều ứng dụng của một chủ thể. Đó là hệ quả của các hành động: sao chép mã; các chức năng tương tự được viết bởi các lập trình viên khác nhau. Hệ quả là mã trở nên dài hơn, khó hiểu hơn và khó bảo trì hơn.

Ví dụ đoạn mã tính giá trị trung bình của một mảng số nguyên trên C:

extern int array1[];

extern int array2[];

 

int sum1 = 0;

int sum2 = 0;

int average1 = 0;

int average2 = 0;

 

for (int i = 0; i < 4; i++){

   sum1 += array1[i];

}

average1 = sum1/4;

for (int i = 0; i < 4; i++){

   sum2 += array2[i];

}

average2 = sum2/4;

Ta thấy hai vòng lặp for là giống nhau!

  • Hàm dài

Hàm dài là quá phức tạp, có lượng mã lớn. Nên khó để hiểu, triển khai, bảo trì và tái sử dụng. Nên việc tách thành các hàm nhỏ hơn là điều cần thiết.

  • Lớp lớn

Lớp lớn là lớp chứa quá nhiều thuộc tính và chức năng, thường là của nhiều lớp khác. Bởi thế cũng khó để đọc, bảo trì và tái sử dụng. Nên cần thực hiện các kỹ thuật cần thiết để phân bổ thành nhiều lớp khác nhau.

  • Hàm có nhiều tham số đầu vào

Khi một hàm có nhiều tham số đầu vào sẽ gây khó khăn để đọc, dùng và thay đổi. Với các ngôn ngữ lập trình hướng đối tượng ta có thể nhóm các tham số có liên quan vào một đối tượng để giảm số lượng tham số đầu vào.

  • Tính năng không phải của lớp

Là hiện tượng một phương thức không nên thuộc một lớp, nhưng do phương thức muốn sử dụng các dữ liệu của lớp đó nên lập trình viên đã gán phương thức  cho lớp. Đây là một vi phạm trong lập trình hướng đối tượng. Ta cần trả phương thức đó về đúng đối tượng.

  • Lớp có quan hệ quá gần gũi

Đó là hiện tượng hai lớp có thể truy xuất vào các thuột tính riêng tư của nhau một cách không cần thiết. Điều đó đẫn đến là chính các lớp đó hay lớp con của chúng có thể thay đổi các thuộc tính của lớp con lại một cách “vô thức”.

  • Lớp quá nhỏ

Việc tạo, bảo trì và hiểu một lớp tốn tài nguyên. Vậy nếu lớp đó quá nhỏ thì ta nên xóa bỏ lớp đó đi.

  • Lệnh switch

Một trong những dấu hiệu tốt của lập trình hướng đối tượng là việc không dùng lệnh switch. Vấn đề của lệnh switch chủ yếu là vấn đề lặp mã. Bạn thường gặp những lệnh switch để phân bổ các chức năng ở nhiều nơi khác nhau trong cùng một ứng dụng. Và mỗi khi bạn thêm một tính năng mới, bạn phải tìm ở tất cả các lệnh này để thay đổi.

  • Từ chối kế thừa

Điều này xảy ra khi một lớp được thừa kế dữ liệu cũng như các tính năng của lớp cha, nhưng lại không cần phải dùng tới chúng. Chúng ta không thể cấm đoán điều, nhưng nếu điều này xảy ra thường dẫn tới vấn đề hiểu lầm và vấn đề.

  • Định danh quá dài hoặc quá ngắn

Các định danh cần mô tả đủ ý nghĩa để mã dễ đọc hơn và tránh gây hiểu nhầm. Bởi thế các định danh quá ngẵn thường không mô tả hết ý nghĩa và gây ra nhầm lẫn.  Nhưng các định danh quá dài cũng có vấn đề tương tự. Bởi thế trong trường hợp này chúng ta có thể viết tắt hay tìm định danh thay thế.

  • Dùng quá nhiều giá trị

Nếu trong mã có nhiều giá trị  thì khi cần phải thay đổi các giá trị đó vì một lý do nào đó (ví dụ thay đổi độ chính xác) thì bạn cần phải thay đổi ở nhiều nơi. Không chỉ thế mà không phải ai và lúc nào cũng nhớ những giá trị đó có ý nghĩa gì, nên làm cho mã trở nên khó đọc hơn. Trong trường hợp này bạn có thể thay bằng cách đặt tên cho các hằng.

 

Các kỹ thuật cải tiến cơ bản

Các kỹ thuật cải tiến được chia thành các nhóm tùy theo tiêu chí. Ở đây chúng ta sẽ chia theo mục đích.

  1. Trừu tượng hóa mã nguồn
  2. Chia nhỏ mã nguồn
  3. Chuẩn hóa mã nguồn

 

Kết luận

Cải tiến là công việc cần thiết và đem lại hiệu quả cao. Nhưng lượng các kỹ thuật mà ta áp dụng được cho dự án của mình thì rất nhiều và tốn kém thời gian. Trong bài viết này tôi không hy vọng sẽ trình bày được tất cả hay nhiều kỹ thuật mà chỉ là một số kỹ thuật căn bản nhất.


Tài liệu tham khảo

Fowler, M., K. Beck, et al. (1999). Refactoring: Improving the Design of Existing Code.
Ritchie, P. "Refactoring with Microsoft Visual Studio 2010."
(2011). http://sourcemaking.com/refactoring.