Refactoring - Mở đầu

Refactoring

Refactoring chắc hẳn ai đang làm phần mềm thì đều biết đến kỹ thuật này, trước đây thì tôi nghĩ refactoring chỉ là một bước phụ, không quan trọng, lúc nào mình thích thì mình làm thôi. Nhưng sau khi tham gia khóa học Agile Development tôi thấy việc refactoring là rất cần thiết trong một dự án. Trong series về refactoring này tôi sẽ trình bày refactoring là gì, tầm quan trọng của nó và các kỹ thuật để refactoring.

Refactoring là gì?

Refactoring là một quá trình cải tiến code có thể kiểm soát được mà không tạo ra chức năng mới. Nó biến những thứ hỗn độn thành những thiết kế đơn giản hơn và clean code.

Clean code

Về cơ bản, clean code có một số tính năng như:

  1. Clean code rõ ràng cho các lập trình viên khác. Ở đây ta không kể đến những thuật toán cực kỳ phức tạp. Đặt tên biến, tên hàm thì khó hiểu, không liên quan đến chức năng của nó; các class và method viết thì dài, khó để nhớ hết chức năng của nó. Tất cả những điều đó làm cho code bị bẩn (code smell hay code sloppy) và khó để hiểu, nắm bắt.
  2. Clean code không trùng lặp. Khi code bị lặp và ta phải thay đổi một số thứ ở phần code bị lặp đó, thì ta phải thay đổi tất cả những phần còn lại. Điều này làm chậm lại quá trình code.
  3. Clean code chứa ít code nhất có thể. Code ít hơn thì ta chỉ cần nhớ ít hơn, dễ bảo trì hơn, ít bugs hơn. Vì vậy hãy giữ cho code ngắn và đơn giản.
  4. Clean code vượt qua tất cả các test. Nếu code của bạn chỉ vượt qua 95% test case thì code đó chưa được clean.
  5. Clean code thì bảo trì dễ dàng hơn và chi phí ít tốn kém hơn.

Technical debt

Tất cả mọi người trong chúng ta đều cố gắng hết sức để viết code chuẩn ngay từ đầu. Có lẽ không có người nào cố ý viết code không clean để làm ảnh hưởng đến dự án. Vậy tại thời điểm nào mà clean code trở nên không clean?

Phép ẩn dụ technical debt (tạm dịch là nợ kỹ thuật) liên quan đến code không clean được đề xuất bở Ward Cunningham.

Nếu bạn nhận được khoản vay từ ngân hàng, điều này giúp cho việc đầu tư nhanh hơn. Đương nhiên bạn phải trả thêm tiền việc này - tức là bạn không những phải trả nợ gốc, mà còn phải trả thêm lãi. Không cần phải nói, thậm chí bạn phải trả số tiền lãi nhiều hơn số tiền bạn thu được từ việc đầu tư, điều này làm cho việc trả lại số tiền cho ngân hàng là không thể.

Điều tương tự cũng xẩy ra khi ta code. Bạn có thể tạm thời tăng tốc độ dự án bằng việc không viết test có các tính năng (tức là bạn đang nợ), nhưng điều này sẽ dần dần làm chậm tiến độ của bạn mỗi ngày do bug cho đến khi bạn phải trả hết nợ bằng cách viết test.

Các nguyên nhân của nợ kỹ thuật

Áp lực kinh doanh

Đôi khi các tình huống về mặt kinh doanh, khi mà khách hàng họ muốn sản phẩm của họ được đẩy lên sớm hơn có thể buộc bạn phải triển khai các tính năng trước khi hoàn tất.

Trong trường hợp này, các bản bổ sung, fix bug sẽ được đẩy lên để hoàn thành những phần đó của dự án.

Thiếu hiểu biết về hậu quả của nợ kỹ thuật

Đôi khi sếp của bạn không hiểu về nợ kỹ thuật (nợ này ở mức chấp nhận được) cũng có thể làm chận tốc độ phát triển dự án.

Điều này có thể làm cho nó quá khó để các thành viên trong team dành thời gian để refactor bởi vì sếp của bạn không nhìn thấy giá trị mà nó mang lại.

Không đáp ứng được mối quan hệ chặt chẽ của các component

Đây là khi dự án của bạn là một khối chứ không phải là sản phẩm của từng module riêng lẻ.

Trong trường hợp này, bất kì thay đổi nào đối với một phần trong dự án sẽ ảnh hưởng đến các phần còn lại. Sự phát triển của cả team trở nên khó khắn hơn vì khó để có thể tách riêng công việc của các thành viên.

Thiếu việc test

Việc thiếu phản hồi ngay lập tức khuyến khích việc sửa đổi nhanh, nhưng có nguy cơ gây ra lỗi, và đôi khi ảnh hưởng trực tiếp đến môi trường production.

Ảnh hưởng của việc này có thể trở thành thảm họa. Ví dụ, một hotfix vô tội có thể gửi một email đến toàn bộ khách hàng hay xóa dữ liệu khách hàng hiện tại trong cơ sở dữ liệu.

Thiếu tài liệu

Điều này làm chậm việc giới thiệu cho người mới về dự án và có thể làm chậm lại quá trình phát triển nếu những người chủ chốt rời khỏi dự án.

Thiếu sự tương tác giữa các thành viên trong nhóm

Nếu các kiến thức về dự án không được trao đổi, hiểu biết về dự án như các quy trình, thông tin dự án của mọi người sẽ lạc hậu.

Tình huống này có thể trở nên trầm trọng hơn khi các bạn junior developer được đào tạo không chính xác bởi các mentor.

Phát triển đồng thời dài hạn tại một số nhánh

Điều này có thể dẫn đến sự tích tụ về nợ kỹ thuật, sau đó sẽ tăng lên khi các thay đổi, bổ sung được merge vào project.

Càng nhiều sự thay đổi từ nhiều người làm cho nợ kỹ thuật ngày càng lớn.

Trì hoãn việc refactoring

Yêu cầu dự án liên tục thay đổi và tại một số thời điểm các phần code đó sẽ trở nên lỗi thời, rườm rà và phải được refactor để đáp ứng các yêu cầu mới.

Mặt khác, các lập trình viên đang viết code mới mỗi ngày mà làm việc với các phần code quá cũ. Vì vậy, việc refactoring sẽ bị trì hoãn, những phần code bị phụ thuộc nhiều sẽ phải được làm lại trong tương lai.

Thiếu tuân thủ việc giám sát

Điều này xảy ra khi mọi người làm việc trong dự án viết code khi họ đã cảm thấy phù hợp.

Vậy khi nào thì cần refactor?

3 luật cơ bản

  1. Khi bạn làm một cái gì đó lần đầu tiên, ta chỉ cần hoàn thành phần đó thôi.
  2. Khi bạn làm một cái gì đó tương tự lần thứ hai, mặc dù cảm thấy hơi lo lắng nhưng vẫn hãy làm điều tương tự như bước 1.
  3. Khi bạn làm gì đó lần thứ ba, hãy bắt đầu refactoring.

Khi thêm một tính năng

  1. Refactoring giúp bạn hiểu được code của người khác viết Nếu bạn phải làm việc với code của người khác viết, hãy thử refactor nó trước tiên. Làm code trở nên clean thì mình sẽ dễ dàng nắm bắt hơn. Bạn sẽ cải thiện nó không chỉ cho mình mà còn cho những người sử dụng nó sau bạn.
  2. Refactoring giúp ta dễ dàng thêm các tính năng mới

Khi fix một bug

Các bug hoạt động giống như ngoài đời thực (sâu, bọ): chúng sống ở những nơi tối nhất, bẩn nhất trong code. Làm code của bạn được clean thì có thể khám phá ra những bug của chính mình.

Các sếp đánh giá cao việc chủ động refactoring vì nó loải bỏ sự refactoring cần thiết cho các task phức tạp sau này. Happy bosses make happy programmers!

Trong khi review code

Review code có thể là cơ hội cuối cùng để làm code clean trước nó sẵn sàng để public.

Tốt nhất ta nên thực hiện việc review theo cặp. Bằng cách này ta có thể khắc phục các vấn đề đơn giản một cách nhanh chóng và đánh giá được thời gian fix các bug khó hơn.

Cách để refactor

Refactoring nên được thực hiện từ các thay đổi nhỏ, trong mỗi chúng làm cho code hiện tại tốt hơn một chút trong khi chương trình vẫn hoạt động.

Checklist of refactoring done right way

  1. Code trở nên clean hơn Nếu code vẫn chưa clean sau khi refactoring thì coi như ta đang lãng phí 1 giờ trong cuộc đời dành cho việc refactoring. Hãy cố gắng tìm ra lý do tại sao điều này lại xảy ra.

    • Điều này thường xuyên xảy ra khi ta trộn lẫn việc refactoring thay đổi nhỏ với nhau thành một thay đổi lớn. Vì vậy ta rất dễ mất tâm trí của mình, đặc biệt nếu ta có một khoảng thời gian giới hạn.
    • Nhưng nó có thể xảy ra khi ta làm việc với code rất bẩn. Dù bạn cải thiện, toàn bộ code còn lại vẫn là một thảm họa. Trong trường hợp này ta nên nghĩ đến việc đập đi toàn bộ code và code lại. Nhưng trước đó ta nên dành ra một khoảng thời gian để viết test. Nếu không bạn sẽ gây ra hậu quả không đáng có.
  2. Không nên tạo chức năng mới trong quá trình refactor Không nên kết hợp việc refactoring và phát triển các chức năng mới với nhau. Cố gắng tách những quy trình này độc lập đối với từng commit.

  3. Tất cả các test case phải được pass sau khi refactoring Có hai trường hợp khi các test case không còn dùng được sau khi refactoring:

    • Gây ra lỗi khi refactoring. Cái này thì dễ dàng, chỉ cần sửa lỗi đó là xong.
    • Các test case ở mức thấp Trong trường hợp này, các bài test như là để đổ lỗi, và cách duy nhất để sửa lỗi này là refactor các bài test và viết các test ở mức cao hơn. Một cách tuyệt vời để tránh tình trạng này là sử dụng Behavior Driven Development (BDD).

Tài liệu tham khảo

Đây chỉ mới là phần giới thiệu mở đầu về refactoring, ở phần sau tôi sẽ giới thiệu chi tiết các phương pháp để refactoring, các trường hợp nên sử dụng các phương pháp đó, lý do sử dụng và cách xử lý.