14. Thà có lỗi biên dịch (compiler) và lỗi liên kết (linker) còn hơn lỗi khi chạy chương trình (run-time)

Estimated reading: 8 minutes 292 views

Tóm tắt

Đừng để các lỗi xảy ra khi chạy (run-time) nếu bạn có thể phát hiện chúng khi biên dịch: Hãy viết mã sao cho trình biên dịch (compiler) có thể kiểm tra tính bất biến ngay trong quá trình biên dịch thay vì để chúng đến khi chạy. Kiểm tra khi chạy phụ thuộc vào dữ liệu và luồng điều khiển, do đó bạn hiếm khi có thể đám bảo kiểm tra một cách toàn diện. Ngược lại, kiểm tra khi biên dịch không phụ thuộc vào dữ liệu hoặc luồng điều khiển và thường mang lại độ tin cậy cao hơn.

Thảo luận

Ngôn ngữ C++ cung cấp nhiều cơ hội để “tăng tốc” việc phát hiện lỗi bằng cách đẩy chúng vào giai đoạn biên dịch. Tận dụng khả năng kiểm tra tĩnh này mang lại nhiều lợi ích, bao gồm:

  • Kiểm tra tĩnh không phụ thuộc vào dữ liệu hoặc luồng điều khiển chương trình: Kiểm tra tĩnh đảm bảo chương trình đúng mà không cần biết trước các dữ liệu đầu vào hoặc các tình huống chạy. Trong khi đó, kiểm tra khi chạy yêu cầu bạn phải kiểm thử với tất cả các dữ liệu đầu vào khả dĩ, điều này là một thách thức lớn.
  • Mô hình chương trình được biểu diễn tĩnh mạnh mẽ hơn: Một chương trình dựa nhiều vào kiểm tra khi biên dịch thay vì kiểm tra khi chạy thường phản ánh một thiết kế tốt hơn, vì mô hình mà chương trình tạo ra được biểu diễn một cách chính xác thông qua hệ thống kiểu (dữ liệu) của C++. Bằng cách này, bạn và trình biên dịch cùng có một cách nhìn nhất quán về các bất biến của chương trình. Kiểm tra khi chạy thường chỉ là phương án dự phòng để kiểm tra những điều có thể thực hiện khi biên dịch nhưng không thể diễn đạt chính xác trong ngôn ngữ lập trình (Mục 68). → Giải thích chi tiết kèm ví dụ
  • Kiểm tra tĩnh giảm chi phí so với chạy thật: Kiểm tra tĩnh thay thế kiểm tra động giúp chương trình chạy nhanh hơn mà không hi sinh tính đúng đắn.

Một trong những công cụ kiểm tra tĩnh mạnh mẽ nhất của C++ là hệ thống kiểm tra kiểu tĩnh. Cuộc tranh luận về việc nên kiểm tra kiểu tĩnh (C++, Java, ML, Haskell) hay kiểm tra kiểu động (Smalltalk, Ruby, Python, Lisp) vẫn luôn sôi động. Không có câu trả lời duy nhất cho mọi trường hợp, vì mỗi phương pháp có những ưu điểm riêng và được áp dụng hiệu quả trong các ngôn ngữ và phong cách lập trình khác nhau.

Những người ủng hộ kiểm tra tĩnh cho rằng nó giúp loại bỏ một lượng lớn lỗi có thể xảy ra khi chạy, làm cho chương trình mạnh mẽ hơn. Trong khi đó, những người ủng hộ kiểm tra động lập luận rằng trình biên dịch chỉ có thể phát hiện một phần nhỏ các lỗi tiềm ẩn, vì vậy việc kiểm tra khi chạy thông qua unit test vẫn là điều cần thiết. Do đó, họ cho rằng không cần quá gò bó vào kiểm tra tĩnh mà thay vào đó có thể tận dụng một môi trường lập trình linh hoạt hơn.

Một điều rõ ràng là: Trong bối cảnh C++, một ngôn ngữ có hệ thống kiểm tra tĩnh mạnh mẽ nhưng ít hỗ trợ kiểm tra tự động khi chạy, lập trình viên nên tận dụng hệ thống kiểu tĩnh bất cứ khi nào có thể. Tuy nhiên, đối với những kiểm tra phụ thuộc vào dữ liệu và luồng điều khiển (ví dụ: kiểm tra giới hạn mảng hoặc xác thực dữ liệu đầu vào), kiểm tra khi chạy vẫn là hợp lý.

Ví dụ

Dưới đây là một số trường hợp bạn có thể thay thế kiểm tra khi chạy bằng kiểm tra khi biên dịch:

  1. Điều kiện Boolean khi biên dịch: Nếu bạn cần kiểm tra các điều kiện Boolean khi biên dịch, chẳng hạn như sizeof(int) >= 8, hãy sử dụng static_assert thay vì kiểm tra khi chạy (Mục 91).
  2. Đa hình khi biên dịch: Hãy cân nhắc thay thế đa hình khi chạy (sử dụng hàm ảo virtual functions) bằng đa hình khi biên dịch (sử dụng template) khi định nghĩa các hàm hoặc kiểu tổng quát. Cách này giúp trình biên dịch kiểm tra mã tốt hơn (Mục 64).
  3. Enum: Hãy sử dụng enum (hoặc tốt hơn, các kiểu dữ liệu đầy đủ) khi bạn cần biểu diễn các hằng số có ý nghĩa hoặc các giá trị số nguyên bị giới hạn.
  4. Downcasting: Nếu bạn thường xuyên sử dụng dynamic_cast (hoặc tệ hơn, static_cast mà không được kiểm tra) để thực hiện ép kiểu xuống lớp con, điều đó có thể cho thấy rằng lớp cơ sở (base class) của bạn cung cấp quá ít chức năng. Hãy cân nhắc thiết kế lại giao diện lớp cơ sở để chương trình có thể diễn đạt tính toán trực tiếp thông qua lớp cơ sở.

Ngoại lệ

Một số điều kiện không thể được kiểm tra tại thời điểm biên dịch mà yêu cầu kiểm tra trong thời gian chạy. Đối với những điều kiện này, hãy ưu tiên sử dụng assert để phát hiện lỗi lập trình nội bộ (Mục 68). Với các lỗi khác như lỗi phụ thuộc vào dữ liệu, hãy tuân theo hướng dẫn xử lý lỗi trong các mục tiếp theo (69 đến 75).

Tham khảo

[AlexandrescuOl] §3 • [Boost] • [Meyers97] §46 • [StroustrnpOO] §2.4.2 • [SutterO2] §4 • [SutterO4] §2, §19

Link

Leave a Comment

Chia sẻ:

14. Thà có lỗi biên dịch (compiler) và lỗi liên kết (linker) còn hơn lỗi khi chạy chương trình (run-time)

Or copy link

CONTENTS