IDE Không Phải Là Lựa Chọn Duy Nhất (Kết Thúc)
Tôi đã bắt đầu cảm thấy mất hứng thú với loạt bài này. Kéo dài thời gian quá lâu cũng không tốt. Từ đầu tôi vốn không định viết một bài hướng dẫn nhập môn về công cụ cụ thể nào (GNU Make). Mục đích ban đầu là muốn giới thiệu cho những người nghiện nặng IDE của Microsoft một góc nhìn khác, đồng thời xóa tan cảm giác huyền bí xung quanh các công cụ giao diện dòng lệnh (CUI) như Make. Công cụ là phương tiện phục vụ con người, không nên trở thành gánh nặng. Dù là IDE, công cụ RAD hay các công cụ CUI khác đều phải tuân theo nguyên tắc này. Khi bạn hiểu được triết lý ẩn sau công cụ, nó sẽ mang lại sự tiện lợi thực sự. Mỗi công việc đều có công cụ phù hợp, đừng biến mọi thứ thành đinh chỉ vì bạn đang cầm búa.
Khi mỗi loại công cụ đều có lượng người dùng đông đảo, và những người dùng này không phải ai cũng thiếu suy nghĩ độc lập, việc tìm hiểu các lĩnh vực khác nhau luôn là điều đáng làm. Điều này đúng với việc lựa chọn môi trường phát triển, đúng với việc chọn ngôn ngữ lập trình, và cũng đúng với các phương pháp phát triển phần mềm khác nhau.
Hôm nay tôi không định viết dài dòng, chỉ muốn kết thúc loạt bài này một cách gọn nhẹ bằng cách giải quyết nốt những vấn đề còn tồn đọng.
Với các dự án lớn, khi sử dụng Visual Studio (VS), tôi thường quản lý chúng thông qua thư mục ảo và các dự án con. Không rõ các bạn khác có thói quen tương tự không. Từ thời Visual C++ 6.0, tôi gần như không còn dùng VS nữa nên cũng không nắm rõ xu hướng phát triển hiện tại của IDE này. Có lẽ hiện tại đã có những cách tổ chức tốt hơn.
Tuy nhiên khi rời khỏi VS, nếu chúng ta chuyển sang GNU Make hoặc các công cụ tương tự (như Boost Jam mà tôi từng dùng, hay CMake được một số bạn đề xuất), thông thường chúng ta sẽ quản lý dự án lớn theo cấu trúc thư mục hệ điều hành. Cụ thể, mỗi dự án con sẽ được đặt trong một thư mục con riêng. Ngay cả khi một mô-đun lớn là phần không thể tách rời của dự án con, nó cũng thường được tách thành thư viện tĩnh. Dù thư viện tĩnh đó chỉ được sử dụng duy nhất một lần.
Việc chia nhỏ mã nguồn thành các phần có quy mô hợp lý và phân loại chúng vào các thư mục khác nhau là một thói quen tốt.
Khi viết Makefile, bạn có thể tạo một Makefile riêng cho mỗi thư mục chứa mã nguồn. Làm thế nào để các Makefile ở các thư mục con (kể cả các thư mục lồng sâu nhiều cấp) có thể làm việc cùng nhau? Cách thông thường là sử dụng Shell để gọi đệ quy chính Make.
Ví dụ, thư mục src có hai dự án con là foo1 và foo2. Makefile ở thư mục gốc src thường được viết như sau:
|
|
Biến $(MAKE) là một biến được định nghĩa sẵn, chứa lệnh Shell để gọi chính công cụ Make. Cách viết này có vẻ hơi lặp lại, vì vậy tôi thường trích xuất các thư mục con foo1 và foo2 thành biến:
|
|
Phiên bản này vẫn còn nhiều điểm có thể tối ưu hóa, nhưng nếu tiếp tục mở rộng sẽ dẫn đến các kỹ thuật “cao cấp” hơn. Những kiến thức được dùng ở đây đã được giới thiệu ở các phần trước, trừ $* - ký hiệu biểu diễn chuỗi ký tự trong mục tiêu sau khi loại bỏ phần hậu tố .ext.
Theo quy trình đã trình bày ở các phần trước, bạn sẽ nhận thấy việc build dự án thường để lại nhiều file trung gian trong thư mục mã nguồn. Trong các ví dụ trước, chúng ta đều thêm mục tiêu clean để dọn dẹp các file này. Tuy nhiên, theo hướng dẫn trong tài liệu chính thức của GNU Make, việc làm này không được khuyến khích vì sẽ làm “bừa” thư mục mã nguồn. Thông thường, chúng ta nên định nghĩa một thư mục đầu ra riêng cho các file trung gian. Việc này đòi hỏi một chút kỹ thuật nhưng không khó thực hiện - do giới hạn bài viết nên tôi sẽ không đưa ví dụ cụ thể.
Vì từng dùng VS từ thời kỳ đầu, tôi có thói quen duy trì ít nhất hai phiên bản build song song: một bản Debug và một bản Release, đặt riêng biệt trong các thư mục đầu ra khác nhau. Khi rebuild bản Debug sẽ không ảnh hưởng đến quá trình build lại bản Release. Boost Jam xử lý yêu cầu này rất tốt, thậm chí còn cho phép bạn tùy biến phức tạp hơn như “bản Release tắt RTTI” hay “bản Debug bật ngoại lệ C++” một cách dễ dàng. Tất nhiên, Boost Jam cũng phải trả giá cho sự linh hoạt này bằng một chút phức tạp trong cấu hình.
Quay lại với GNU Make, cách đơn giản nhất là sử dụng các giá trị biến khác nhau khi biên dịch các phiên bản khác nhau. Ví dụ: các cờ tối ưu hóa khác nhau, thư mục đầu ra khác nhau. Mặc dù GNU Make hỗ trợ các câu lệnh điều kiện tương tự #if trong C, nhưng không thể giải quyết trọn vẹn vấn đề này. Chúng ta cần sử dụng một tính năng quan trọng khác: GNU Make cho phép định nghĩa các biến liên quan đến mục tiêu cụ thể:
|
|
Cách viết này khiến giá trị của biến chỉ có hiệu lực khi build mục tiêu tương ứng. Làm thế nào để biến ý tưởng này thành hiện thực phụ thuộc vào sự sáng tạo của bạn.
Hãy nhớ rằng bất kỳ công cụ nào cũng cần thời gian học hỏi. Chỉ khi sử dụng thường xuyên mới nâng cao được kỹ năng thành thạo. Khi viết Makefile, đừng coi đó là việc sửa đổi file cấu hình thông thường, mà hãy xem nó như một ngôn ngữ lập trình - một ngôn ngữ giúp bạn tăng hiệu suất làm việc. Khi bạn dạy máy tính thực hiện những thao tác vốn phải làm thủ công, đó chính là bản chất của nghề lập trình viên.