Đừng đổ lỗi cho tối ưu hiệu năng nữa: Ứng dụng của bạn giật lag, có thể là do thiết kế sản phẩm quá tệ

Đừng đổ lỗi cho tối ưu hiệu năng nữa: Ứng dụng của bạn giật lag, có thể là do thiết kế sản phẩm quá tệ

Chắc hẳn mỗi khi chúng ta code xong một trang web và deploy lên production thì có một điều chúng ta khá quan tâm là Tối ưu hiệu năng (Performance Optimization).

Trên mạng có nhiều bài viết chứa những thuật ngữ nghe rất đao to búa lớn:

  • Sử dụng Virtual List (danh sách ảo) để tối ưu việc render danh sách dài...
  • Sử dụng Web Worker để đẩy các tính toán phức tạp ra khỏi luồng chính (main thread)...
  • Sử dụng WASM để viết lại thuật toán lõi...

Sau khi viết vài bài về tối ưu hiệu năng, cũng như tìm hiểu thêm từ các đồng nghiệp, tôi đã tự hỏi bản thân một câu: "Tại sao bạn lại cần render một danh sách có tới 10.000 dòng dữ liệu? Người dùng có thực sự xem hết được không?"

Đây chính là chủ đề tôi muốn nói đến hôm nay: Ở thời điểm hiện tại, 90% những cái gọi là "nút thắt hiệu năng" (performance bottleneck) trong mảng frontend vốn dĩ không phải là vấn đề kỹ thuật, mà là vấn đề của sản phẩm.

Đừng cắm đầu vào việc tối ưu code chỉ để phục vụ cho một thiết kế sản phẩm vô lý (kiểu như render một vạn dòng dữ liệu) nữa. Việc cắt bỏ các requirement "ảo" và điều chỉnh lại logic tương tác (interaction logic) mới là giải pháp đúng đắn.

Đám kỹ sư chúng ta, cầm trong tay những công nghệ frontend tiên tiến nhất (Vite, Rust, WASM), nhưng ngày qua ngày lại đang tạo ra những thiết kế sản phẩm tồi tệ.

Chúng ta đang giải quyết sai vấn đề

Hãy cùng dựng lại một hiện trường tối ưu hiệu năng kinh điển nhé.

Ngữ cảnh: Một super table trong hệ thống admin/back-office.

PM báo requirement: Bảng này phải hiển thị tất cả đơn hàng, cỡ 50 cột, mỗi trang hiển thị 500 dòng, lại còn phải hỗ trợ tìm kiếm realtime, hỗ trợ kéo thả cột, trong mỗi ô có thể còn có dropdown menu...

Phản ứng đầu tiên của developer (dưới góc độ kỹ thuật):

  • 50 cột x 500 dòng = 25.000 DOM node, trình duyệt chắc chắn sẽ treo (crash/lag).
  • Nhanh! Đưa Virtual Scroll vào!
  • Nhanh! Thêm Debounce vào!
  • Nhanh! Dùng Memoization (cache) đi!

Vì cái requirement này, chúng ta import vào các third-party library phức tạp, viết ra những đoạn code tối ưu tối nghĩa và khó hiểu. Thậm chí, để giải quyết các lỗi UI do Virtual Scroll gây ra (ví dụ: height collapse - sập chiều cao, positioning bugs - lỗi định vị trí), chúng ta lại đắp thêm cả đống patch (code vá lỗi).

Cuối cùng, trang web cũng hết lag. Chúng ta cảm thấy bản thân rất "bá đạo", kỹ thuật rất khủng.

Nhưng chúng ta chưa bao giờ đặt ra câu hỏi cốt lõi nhất:

Võng mạc và não bộ của con người, liệu có thực sự xử lý được cùng lúc 50 cột x 500 dòng dữ liệu không?

Câu trả lời là: Không thể.

Khi trên màn hình chi chít toàn là dữ liệu, tải trọng nhận thức (cognitive load) của người dùng đã overload. Họ căn bản không thể tìm thấy thứ mình muốn xem. Thứ họ cần không phải là render hiệu năng cao, thứ họ cần là filtersearch.

Chúng ta dùng công nghệ đỉnh cao, để implement một thiết kế phản nhân loại. Đây không gọi là tối ưu, đây gọi là "tạo nghiệp".

Tối ưu thực sự, bắt đầu từ việc cắt requirement

Tôi từng tiếp nhận một dự án đang phát triển của công ty, trang web giật lag đến mức FPS chỉ còn 10. Developer tiền nhiệm để lại vài nghìn dòng code phức tạp dùng để tối ưu render, bảo trì đúng kiểu "sống không bằng chết".

Sau khi tiếp nhận, Leader của tôi không bảo mọi người bắt tay ngay vào sửa một dòng code render nào.

Chúng tôi đã họp lại và phân tích những lỗi đang mắc phải:

  1. Ở cột log JSON gốc của đơn hàng này, độ dài trung bình 3000 ký tự, nếu bê tất cả hiển thị lên bảng, thì có ai thèm đọc không? Cắt! Đổi thành một nút "Xem chi tiết", click vào mới load. Số lượng DOM node giảm 20%.
  2. 50 cột dữ liệu này, người dùng thực sự quan tâm với tần suất cao nhiều đến vậy không? Mặc định chỉ hiển thị 8 cột cốt lõi. Phần còn lại đưa vào cột tùy chỉnh, người dùng muốn xem thì tự tick chọn. Số lượng DOM node giảm 80%.
  3. Tại sao lại phải load một lần 500 dòng? Khi người dùng cuộn đến dòng thứ 400, họ còn nhớ dòng thứ 1 là cái gì không? Cắt! Đổi thành phân trang (pagination) tiêu chuẩn, mỗi trang 20 dòng. Số lượng DOM node giảm 96%.

Làm xong 3 việc này, tôi thậm chí đã xóa sạch toàn bộ code Virtual Scroll trước đó, rollback về thẻ <table> thuần nhất.

Kết quả thì sao?

  • Trang web chạy nhanh như bay (vì DOM chỉ còn 1% so với ban đầu).
  • Code cực kỳ đơn giản (bảo trì lại càng đơn giản hơn 🤔).
  • Người dùng ngược lại còn vui hơn (vì UI gọn gàng, phân cấp thông tin rõ ràng).

Đây mới là tối ưu hiệu năng đỉnh cao nhất: Không chỉ tối ưu hiệu năng của máy móc, mà còn tối ưu trải nghiệm của con người.

Cạm bẫy của "cái tôi kỹ thuật"

Tại sao chúng ta luôn sa lầy vào vũng bùn tối ưu kỹ thuật mà không thoát ra được? 😒 Bởi vì chúng ta có cái tôi kỹ thuật.

Là kỹ sư, trong tiềm thức chúng ta luôn cảm thấy: Thừa nhận requirement này không làm được (hoặc làm không tốt), là do trình độ kỹ thuật của mình kém.

  • PM muốn "Logo phải to lên, nhưng tổng thể phải bé lại", tôi cũng phải làm ra cho bằng được!
  • PM muốn chạy một quả cầu 3D trên trang này, tôi liền phải đi cày Three.js!

Chúng ta đang cố gắng dùng kỹ thuật để bù đắp cho sự lười biếng về mặt logic của sản phẩm!

Bởi vì PM lười suy nghĩ về phân cấp thông tin (information hierarchy), nên họ ném thẳng cả một đống thông tin cho frontend, rồi bắt bạn đi làm lazy load.

Kỹ thuật không phải là vạn năng.

Năng lực render của trình duyệt là có giới hạn, luồng chính của JS là đơn luồng, dung lượng pin của thiết bị di động là có hạn. Quan trọng hơn cả, sự chú ý của người dùng là cực kỳ hữu hạn.

Khi bạn phát hiện ra mình phải dùng những công nghệ mới cực kỳ phức tạp thì mới có thể miễn cưỡng làm cho một trang web chạy được...

Lúc này, nguồn cơn của vấn đề thường không nằm ở code, mà có thể nằm ở PRD (Tài liệu yêu cầu sản phẩm).

Tổng kết

Lần tới, khi bạn lại đối mặt với một requirement gây giật lag, đừng vội vàng mở Profiler lên để phân tích hiệu năng.

Hãy thử làm theo các bước sau:

  • Chúng ta có thực sự cần xử lý 100.000 dòng dữ liệu ở frontend không? Có thể aggregate sẵn ở backend, và chỉ trả về kết quả cho tôi được không?
  • Cái chart này có thực sự cần realtime không? Người dùng có thực sự nhìn rõ được sự thay đổi tính bằng 1 milisecond không? Đổi thành 5 giây refresh một lần có được không?
  • Nhét cả một cái bản đồ hoàn chỉnh vào cái modal/popup này thì quá lag. Có thể đổi thành: click vào thumbnail, rồi redirect sang một trang bản đồ chuyên biệt được không?

Bạn phải nói cho PM biết: Bản thân hiệu năng, cũng là một tính năng của sản phẩm.

Nếu chỉ vì muốn nhồi nhét thêm nhiều tính năng mà hy sinh đi độ mượt mà - thứ tính năng cốt lõi nhất, thì đúng là "tham bát bỏ mâm".

Code tốt nhất, là Không có dòng code nào.

Cùng chung một đạo lý, tối ưu hiệu năng tốt nhất, là Không có requirement đó.

Với tư cách là một Developer, giá trị của bạn không chỉ thể hiện ở việc bạn biết viết Virtual List, mà còn thể hiện ở việc bạn có dám đập bàn trong buổi review requirement và nói:

"Cái thiết kế này sao mà phản nhân loại thế!! Chúng ta đổi sang một cách làm tốt hơn được không?"

Đừng tiếp tục tạo ra những thiết kế sản phẩm tồi tệ nữa. San phẳng cái đống đó đi, mới là tối ưu thực sự.

Ngoài ra nếu muốn đào sâu hơn nữa, học các kiến thức chuyên sâu thực tiễn hơn từ giảng viên là chuyên gia có hơn 10 năm kinh nghiệm frontend và từng là kỹ sư tại Shopee Singapo thì bạn có thể tham khảo khoá học React Nâng Cao bên dưới của Sydexa nha!

React Nâng Cao - Chuyên Sâu với Tối Ưu Hiệu Năng
Khóa học React từ cơ bản đến nâng cao với chuyên gia. Trở thành Senior React Developer với Sydexa.

Read more