Trở Lại Với Thiết Kế Mô-Đun Trong Ngôn Ngữ C - nói dối e blog

Trở Lại Với Thiết Kế Mô-Đun Trong Ngôn Ngữ C

Năm ngoái tôi từng chia sẻ về những hạn chế của ngôn ngữ C trong việc hỗ trợ mô-đun hóa. Lúc đó tôi đã đề xuất một phương pháp gọi là USING để biểu diễn mối quan hệ phụ thuộc giữa các mô-đun trong C, đồng thời xử lý thứ tự khởi tạo mô-đun một cách hợp lý.

Các ngôn ngữ lập trình hiện đại đều nhận thức rõ tầm quan trọng của việc xây dựng hệ thống như lắp ráp lego, vì vậy họ tích hợp hỗ trợ mô-đun hóa ngay ở cấp độ ngôn ngữ. Điều này đã được cộng đồng kỹ sư phần mềm thừa nhận qua thời gian. So với các “đàn em” như Go - ngôn ngữ có sẵn từ khóa import và package, C quả thực đã quá già cỗi. Giải pháp tôi đề xuất trước đây chỉ là mô phỏng thô sơ, bởi không có sự hỗ trợ từ chính ngôn ngữ thì khó có thể làm được điều gì hoàn hảo.

Trong thực tiễn dự án, tôi đã sử dụng phương pháp USING suốt nhiều năm trời và tạm hài lòng. Trước đó từng thử nghiệm các phương pháp phức tạp và “tinh tế” hơn nhưng đều bị loại bỏ. Tại sao ư? Vì mỗi khi đưa vào khái niệm mới, chi phí học tập cho thành viên mới lại tăng lên đáng kể. Dù ai cũng có kinh nghiệm với C, nhưng bối cảnh dự án mỗi người từng trải qua lại khác biệt. Việc tiếp nhận cái mới luôn tốn kém, và bất kỳ điều gì không bắt buộc ở cấp độ ngôn ngữ đều dễ bị chất vấn. Tại sao không làm theo cách khác? Đây là câu hỏi thường trực trong đầu mỗi lập trình viên.

Phương pháp USING không hoàn hảo, nhưng đủ đơn giản để được chấp nhận. Tuy nhiên, xét về mặt biểu đạt logic thì nó vẫn thừa thãi. Khi một mô-đun sử dụng mô-đun khác, điều này đã tự thân thể hiện qua mã nguồn. Theo quy ước C, việc #include file .h liên quan đã ngụ ý sự phụ thuộc vào mô-đun tương ứng. Chỉ dựa vào kỹ thuật macro và một lần #include khó có thể giải quyết trật tự khởi tạo chính xác, bởi C thiếu khái niệm mô-đun rõ ràng. Giải pháp biên dịch từng mô-đun con thành thư viện động từng được tôi thử nghiệm, nhưng lại phát sinh vấn đề mới - đặc biệt là với các thư viện có độ phân mảnh cao.

Gần đây, dựa trên trải nghiệm học tập ngôn ngữ Go suốt nửa năm qua, tôi đã suy nghĩ lại vấn đề này và tìm ra hướng tiếp cận mới. Nếu thiết lập quy chuẩn đặt tên API thống nhất cho các mô-đun con, có thể tận dụng trình biên dịch và công cụ hỗ trợ để thực hiện meta programming.

Cụ thể, chúng ta có thể dùng objdump phân tích file .o sau khi biên dịch. Ví dụ với mô-đun foo được triển khai trong foo.c, lệnh objdump -t sẽ liệt kê các ký hiệu được export và reference trong file object. Khi yêu cầu tất cả API của mô-đun con tuân thủ quy tắc đặt tên nhất quán (ví dụ: fooApi với quy ước camelCase), công cụ có thể dễ dàng nhận diện các phụ thuộc thông qua các ký hiệu reference. Từ đó tự động sinh ra hàm khởi tạo tương tự phương pháp USING trước đây. Các hàm khởi tạo mô-đun tùy chỉnh có thể thống nhất đặt tên theo mẫu fooInit - nếu tồn tại, chúng sẽ được gọi tự động bởi đoạn mã được sinh ra.

Quy trình này tuy phức tạp nhưng hoàn toàn có thể tự động hóa bằng các công cụ xây dựng như make. Tôi chưa vội trình bày chi tiết triển khai cụ thể, nhưng có thể sẽ khởi động một dự án mã nguồn mở trong tương lai gần để hiện thực hóa ý tưởng này.

0%