Lập trình viên tồi quan tâm đến code. Lập trình viên giỏi quan tâm đến cấu trúc dữ liệu và mối quan hệ giữa chúng.
Lập trình viên giỏi quan tâm đến cấu trúc dữ liệu và mối quan hệ giữa chúng
Góc nhìn sâu sắc từ Linus Torvalds, cha đẻ của Git và Linux
Gần đây, tôi tình cờ đọc được một câu nói cực kỳ thấm thía của Linus Torvalds (cha đẻ của Linux và Git) trong bài viết hay ho trên Stack Overflow này. (Bài viết này sẽ đào sâu thêm về nhiều trích dẫn khác từ bài đăng đó.)
Nó đã tóm tắt một cách hoàn hảo vấn đề tôi đang trăn trở gần đây:
"Lập trình viên tồi quan tâm đến code. Lập trình viên giỏi quan tâm đến cấu trúc dữ liệu và mối quan hệ giữa chúng."
Ngay trước câu nói đó, Linus còn chia sẻ thêm:
“Git thực chất có một thiết kế đơn giản, với các cấu trúc dữ liệu ổn định và được tài liệu hóa khá đầy đủ. Thật vậy, tôi là một người ủng hộ mạnh mẽ việc thiết kế code xoay quanh dữ liệu, thay vì ngược lại. Tôi nghĩ đó là một trong những lý do giúp Git thành công [...] Tôi thậm chí sẽ khẳng định rằng, sự khác biệt giữa một lập trình viên tồi và một lập trình viên giỏi là việc anh ta coi trọng code hay cấu trúc dữ liệu của mình hơn.”
Cấu trúc dữ liệu tốt giúp việc thiết kế và bảo trì code trở nên dễ dàng hơn rất nhiều. Nó làm cho phần mềm đáng tin cậy hơn, hệ thống dễ hiểu hơn và code dễ đọc hơn. Khi thiết kế bất kỳ phần mềm nào, logic ứng dụng thường nên đi theo mô hình dữ liệu (data model). Coi mô hình dữ liệu là thứ "nghĩ sau" sẽ khiến bạn phải tốn công sức hơn rất nhiều về sau. Ngược lại, một mô hình dữ liệu được suy nghĩ kỹ lưỡng sẽ giúp việc di chuyển dữ liệu (migrations) và phát triển trên các hệ thống phức tạp trở nên dễ dàng hơn rất nhiều.
Khi đọc câu nói này, tôi chợt nhận ra vô số ví dụ trong quá khứ đã minh chứng cho điều đó. Tôi từng làm việc trong một dự án mà chúng tôi đã dành khá nhiều thời gian để tối ưu hóa các thuật toán phức tạp, chỉ để rồi nhận ra rằng bằng cách tái cấu trúc dữ liệu, chúng tôi có thể loại bỏ hoàn toàn những nhóm vấn đề đó. Chúng tôi đã thay thế một function dài 500 dòng bằng một function chỉ 50 dòng kết hợp với một cấu trúc dữ liệu được thiết kế tốt. Code mới không chỉ nhanh hơn mà còn dễ hiểu và dễ bảo trì hơn rất nhiều. (Tất nhiên, vấn đề sau đó cũng "dịch chuyển xuống cấp thấp hơn" – nơi mà phần lớn công sức lại đổ vào việc tái cấu trúc dữ liệu hiện có.)
Một trích dẫn đáng giá khác cũng liên quan đến chủ đề này được tìm thấy trong cuốn sách The Art of Unix Programming:
Quy tắc Biểu diễn: Tích hợp kiến thức vào dữ liệu để logic chương trình trở nên đơn giản và mạnh mẽ.
Ngay cả logic thủ tục (procedural logic) đơn giản nhất cũng khó để con người kiểm tra, nhưng các cấu trúc dữ liệu phức tạp lại khá dễ để mô hình hóa và suy luận. Để dễ hình dung, hãy thử so sánh khả năng biểu đạt và giải thích của một sơ đồ cây con trỏ (pointer tree) với năm mươi nút, với một biểu đồ lưu đồ (flowchart) của một chương trình năm mươi dòng. Hoặc, so sánh một trình khởi tạo mảng (array initializer) thể hiện bảng chuyển đổi với một câu lệnh switch tương đương. Sự khác biệt về tính minh bạch và rõ ràng là rất lớn. (Xem Quy tắc 5 của Rob Pike).
Dữ liệu dễ quản lý hơn nhiều so với logic chương trình. Do đó, khi bạn đứng trước lựa chọn giữa sự phức tạp trong cấu trúc dữ liệu và sự phức tạp trong code, hãy chọn cái trước. Hơn nữa: trong quá trình phát triển thiết kế, bạn nên tích cực tìm cách chuyển đổi sự phức tạp từ code sang dữ liệu.
Cộng đồng Unix không phải là nơi khởi nguồn của nhận định này, nhưng rất nhiều code Unix đã thể hiện rõ ảnh hưởng của nó. Khả năng thao tác con trỏ (pointer) vượt trội của ngôn ngữ C đặc biệt đã khuyến khích việc sử dụng các cấu trúc tham chiếu (reference structures) được sửa đổi linh hoạt (dynamically-modified) ở mọi cấp độ code, từ kernel trở lên. Việc truy vết con trỏ đơn giản trong các cấu trúc này thường đảm nhận những nhiệm vụ mà các ngôn ngữ khác sẽ phải thể hiện bằng các thủ tục phức tạp hơn.
Lời khuyên then chốt ở đây là: hãy bắt đầu từ dữ liệu. Cố gắng giảm độ phức tạp của code bằng cách áp dụng kiểu dữ liệu (stricter types) chặt chẽ hơn cho các interface hoặc cơ sở dữ liệu của bạn. Dành thêm thời gian để suy nghĩ kỹ lưỡng về các cấu trúc dữ liệu ngay từ ban đầu.
Điều này không có nghĩa là code không quan trọng. Rõ ràng, mọi thứ đều quan trọng – nhưng việc có một cái nhìn tổng quan (high-level approach) vững chắc về cách dữ liệu sẽ luân chuyển và cách các thành phần khác nhau tương tác sẽ cực kỳ hữu ích, trước khi đi sâu vào các chi tiết code.
All rights reserved