Sự hỗn loạn front-end là có thật. Một mã nguồn không sụp đổ chỉ sau một đêm. Nó tích tụ dần. Một ngày thứ Ba nào đó, bạn thêm một thư viện định dạng ngày tháng. Sáu tháng sau, ai đó lại thêm một thư viện khác vì họ không tìm thấy cái đầu tiên. Các polyfill chất đống cho những trình duyệt mà bạn không còn hỗ trợ nữa. Các công cụ build chồng chất lên nhau. Cuối cùng, thư mục node_modules trở thành một "ngăn kéo chứa đồ phế thải kỹ thuật số", nơi không thứ gì có thể bị vứt bỏ mà không gây ra nỗi sợ hãi. Bạn ngừng cập nhật. Rồi bạn ngừng để ý đến nó. Đó là lúc mọi thay đổi nhỏ nhất cũng trở thành một canh bạc.

Tôi đã vấp phải bức tường này khi cố gắng nâng cấp Material UI trong một dự án cũ. Tôi mở package.json và gần như không nhận ra một nửa số mục trong đó. Hàng tá thư viện nằm chình ình ở đó, một số đã lỗi thời nhiều năm, số khác thì quá mơ hồ đến mức tôi phải dùng git blame để tìm xem ai đã thêm chúng và tại sao. Tôi chạy lệnh cài đặt phiên bản Material UI mới và terminal hiện lên hàng loạt cảnh báo về peer dependency. Gói (package) tôi muốn cập nhật thì ổn. Nhưng hệ sinh thái xung quanh nó thì không. Tôi nhận ra mình không phải đang thực hiện một bản nâng cấp. Tôi đang khai quật một đống đổ nát.

Tại sao sự lộn xộn này còn tốn kém hơn cả lòng tự trọng

Việc phớt lờ các dependency không chỉ là vấn đề về thẩm mỹ. Nó tạo ra những vấn đề thực sự và tốn kém.

Rủi ro bảo mật là mối đe dọa hiển nhiên. Các gói bị bỏ hoang mang theo những lỗ hổng đã được công bố mà các trình quét sẽ cảnh báo hàng tuần. Tệ hơn nữa, các thư viện bạn trực tiếp cài đặt có thể vẫn ổn, trong khi các dependency gián tiếp (transitive dependencies) mà chúng kéo theo thì không. Bạn đang thừa hưởng nợ kỹ thuật của người khác mà không hề hay biết.

Chi phí tăng dần theo thời gian. Bạn càng chờ đợi lâu, khoảng cách giữa các phiên bản càng lớn. Nhảy một phiên bản React lớn (major version) là một khối lượng công việc nhất định. Nhưng nhảy qua ba phiên bản là cả một dự án di chuyển (migration project) có thể ngốn mất hàng tuần lễ. Bạn sẽ không còn nhận được các bản sửa lỗi, cải tiến hiệu suất và khả năng tương thích với các công cụ hiện đại. Đội ngũ của bạn cuối cùng sẽ phải xây dựng dựa trên những hạn chế vốn dĩ không còn tồn tại nữa.

Các thư viện sẽ "chết". Một gói không còn người duy trì tích cực sẽ mặc nhiên trở thành bản fork riêng của bạn. Khi nó bị lỗi, bạn sẽ là người phải ngồi đọc mã nguồn đã được minify của nó vào lúc nửa đêm. Cộng đồng đã chuyển sang các giải pháp tốt hơn, còn đội ngũ của bạn thì bị kẹt lại để duy trì một "bóng ma".

Tốc độ phát triển sụt giảm nghiêm trọng. Các lập trình viên mới sẽ mất những ngày đầu tiên để học các API đặc thù của những công cụ đã bị thay thế bởi các tiêu chuẩn web hoặc các lựa chọn phổ biến khác. Thay vì bàn giao tính năng, các kỹ sư dày dạn kinh nghiệm của bạn lại trở thành những nhà sử học, giải thích tại sao dự án này vẫn còn sử dụng một task runner từ năm 2015.

Kiểm tra (Audit) trước khi chạm vào bất kỳ phiên bản nào

Sai lầm lớn nhất là chạy một lệnh cập nhật ồ ạt và hy vọng rằng các bài kiểm tra (tests) sẽ vượt qua. Hãy bắt đầu bằng việc kiểm tra (audit). Hãy cầm package.json lên và chất vấn từng mục một.

Hãy đặt ra bốn câu hỏi:

  • Vấn đề này giải quyết cái gì?
  • Chính xác thì chúng ta sử dụng nó ở đâu?
  • Nó có còn cần thiết không?
  • Hiện tại có giải pháp nào tốt hơn không?

Bạn sẽ tìm thấy sự dư thừa. Có thể cả momentdate-fns đều nằm trong danh sách vì hai lập trình viên khác nhau đã giải quyết cùng một vấn đề tại các thời điểm khác nhau. Có thể một polyfill cho Internet Explorer vẫn còn đó mặc dù dữ liệu phân tích cho thấy không có lưu lượng truy cập nào từ các trình duyệt cũ. Có lẽ một wrapper tùy chỉnh quanh fetch có thể được xóa bỏ vì các trình duyệt hiện đại đã xử lý các trường hợp biên (edge cases) một cách tự nhiên.

Đôi khi, thay thế còn tốt hơn là cập nhật. Việc vật lộn với một thư viện biểu đồ đã bị bỏ hoang qua ba năm với hàng loạt thay đổi gây lỗi (breaking changes) có thể mất nhiều thời gian hơn là việc chuyển sang một giải pháp thay thế ổn định và xây dựng lại một vài component. Hãy sẵn sàng để loại bỏ.

Lớp ẩn giấu: Transitive Dependencies và Semver

Các dependency trực tiếp chỉ là phần nổi của tảng băng chìm. Phần lớn thực sự nằm bên dưới trong các transitive dependencies — những gói mà các gói của bạn cần. Bạn không chọn chúng, nhưng chúng vẫn thực thi trong quá trình build của bạn. Chúng làm phình to bundle, mở rộng bề mặt tấn công và đôi khi xung đột với nhau theo những cách tạo ra các lỗi build cực kỳ khó hiểu.

Bạn cần hiểu semantic versioning (semver) đúng như ý nghĩa thực sự của nó, chứ không phải như những gì bạn hy vọng.

  • Major updates: Đây là những cuộc di chuyển. Hãy coi chúng là những thay đổi gây lỗi (breaking changes) cho đến khi có bằng chứng ngược lại. Hãy đọc changelog, dành thời gian và kiểm tra thật kỹ.
  • Minor updates: Những bản này thêm tính năng. Chúng cũng có thể thay đổi hành vi theo những cách tinh vi. Đừng mặc định rằng chúng hoàn toàn "miễn phí".
  • Patch updates: Những bản này sửa lỗi. Chúng thường an toàn, nhưng nếu mã của bạn đang dựa dẫm vào chính cái lỗi đó, hoặc nếu bản patch thay đổi một phần nội bộ mà bạn đang dùng để monkey-patching, bạn vẫn có thể làm hỏng hệ thống.

Hiểu rõ các quy tắc này sẽ giúp bạn phân loại rủi ro trước khi chạm vào bất cứ thứ gì.

Sử dụng công cụ như một thợ thủ công lành nghề

Nếu bạn sử dụng Yarn, một vài lệnh có sẵn sẽ biến việc đoán mò thành một quy trình bài bản.

Hãy chạy yarn outdated trước. Nó sẽ cho bạn một cái nhìn tổng thể về những gì đã bị lạc hậu...