Làm thế nào các lỗi nên được báo cáo trong các thư viện khoa học?


11

Có nhiều triết lý trong các ngành kỹ thuật phần mềm khác nhau về cách các thư viện nên đối phó với các lỗi hoặc các điều kiện đặc biệt khác. Một vài trong số những cái tôi đã thấy:

  1. Trả về mã lỗi với kết quả được trả về bởi một đối số con trỏ. Đây là những gì PETSc làm.
  2. Trả về lỗi bằng một giá trị sentinel. Ví dụ, malloc trả về NULL nếu không thể cấp phát bộ nhớ, sqrtsẽ trả về NaN nếu bạn chuyển số âm, v.v ... Cách tiếp cận này được sử dụng trong nhiều hàm libc.
  3. Ném ngoại lệ. Được sử dụng trong deal.II, Trilinos, v.v.
  4. Trả về một loại biến thể; ví dụ, hàm C ++ trả về một đối tượng kiểu Resultnếu nó chạy chính xác và sử dụng một kiểu Errorđể mô tả cách nó thất bại sẽ trả về std::variant<Error, Result>.
  5. Sử dụng khẳng định và sụp đổ. Được sử dụng trong p4est và một số phần của igraph.

Vấn đề với từng phương pháp:

  1. Kiểm tra cho mọi lỗi giới thiệu rất nhiều mã bổ sung. Các giá trị mà kết quả sẽ được lưu trữ luôn phải được khai báo trước, đưa ra nhiều biến tạm thời chỉ có thể được sử dụng một lần. Cách tiếp cận này giải thích những gì đã xảy ra lỗi nhưng có thể khó xác định lý do tại sao hoặc, đối với ngăn xếp cuộc gọi sâu, ở đâu.
  2. Các trường hợp lỗi là dễ dàng để bỏ qua. Trên hết, nhiều hàm thậm chí không thể có giá trị sentinel có ý nghĩa nếu toàn bộ phạm vi của các loại đầu ra là kết quả chính đáng. Nhiều vấn đề tương tự như # 1.
  3. Chỉ có thể có trong C ++, Python, v.v., không có trong C hoặc Fortran. Có thể được bắt chước trong C bằng cách sử dụng phép thuật setjmp / longjmp hoặc libunwind .
  4. Chỉ có thể có trong C ++, Rust, OCaml, v.v., không có trong C hoặc Fortran. Có thể được bắt chước trong C bằng cách sử dụng phép thuật vĩ mô.
  5. Có thể cho rằng nhiều thông tin nhất. Nhưng nếu bạn áp dụng cách tiếp cận này, giả sử, một thư viện C mà sau đó bạn viết một trình bao bọc Python cho, một lỗi ngớ ngẩn như chuyển một chỉ mục ngoài giới hạn cho một mảng sẽ làm hỏng trình thông dịch Python.

Phần lớn lời khuyên trên internet về xử lý lỗi được viết từ quan điểm của hệ điều hành, phát triển nhúng hoặc ứng dụng web. Sự cố là không thể chấp nhận và bạn phải lo lắng về bảo mật. Các ứng dụng khoa học không có những vấn đề này ở mức độ gần như nhau, nếu có.

Một xem xét khác là những loại lỗi có thể phục hồi hay không. Một thất bại malloc là không thể phục hồi và, trong mọi trường hợp, kẻ giết người hết bộ nhớ hệ điều hành sẽ nhận được nó trước khi bạn làm. Một chỉ mục nằm ngoài giới hạn cho kích thước mảng cũng không thể phục hồi được. Đối với tôi là người dùng, điều tốt nhất mà thư viện có thể làm là gặp sự cố với thông báo lỗi thông tin. Mặt khác, sự thất bại của bộ giải tuyến tính lặp để hội tụ có thể được phục hồi bằng cách sử dụng bộ giải nhân tố trực tiếp.

Làm thế nào các thư viện khoa học báo cáo lỗi và mong đợi chúng sẽ được xử lý? Tất nhiên tôi nhận ra rằng nó phụ thuộc vào ngôn ngữ mà thư viện được triển khai. Nhưng theo như tôi có thể nói, đối với bất kỳ thư viện đủ hữu ích nào, mọi người sẽ muốn gọi nó từ một ngôn ngữ khác ngoài ngôn ngữ mà nó được triển khai.

Bên cạnh đó, tôi nghĩ rằng cách tiếp cận số 5 có thể được cải thiện đáng kể cho thư viện C nếu nó xác định con trỏ hàm xử lý xác nhận toàn cầu là một phần của API công khai. Trình xử lý xác nhận sẽ mặc định báo cáo số tập tin / số dòng và sự cố. Các ràng buộc C ++ cho thư viện này sẽ xác định một trình xử lý xác nhận mới thay vào đó ném một ngoại lệ C ++. Tương tự, các ràng buộc Python sẽ xác định một trình xử lý xác nhận sử dụng API CPython để ném ngoại lệ Python. Nhưng tôi không biết bất kỳ ví dụ nào áp dụng phương pháp này.


Một xem xét khác là phân nhánh hiệu suất. Làm thế nào để các phương pháp khác nhau ảnh hưởng đến tốc độ của phần mềm? Chúng ta có nên sử dụng các xử lý lỗi khác nhau trong các phần "kiểm soát" của mã (ví dụ: xử lý các tệp đầu vào) so với các "công cụ" tính toán đắt tiền không?
LedHead

Lưu ý rằng câu trả lời tốt nhất sẽ khác nhau theo ngôn ngữ.
chrylis -on đình công-

Câu trả lời:


10

Tôi sẽ cung cấp cho bạn quan điểm của tôi, được mã hóa trong dự án deal.II mà bạn tham khảo.

Đầu tiên, có hai loại điều kiện lỗi: Lỗi có thể được phục hồi và lỗi không thể phục hồi.

  • Ví dụ, trước đây là nếu một tệp đầu vào không thể đọc được - ví dụ nếu bạn đang đọc thông tin từ một tệp như vậy $HOME/.dealiicó thể tồn tại hoặc không tồn tại. Chức năng đọc chỉ cần quay lại chức năng gọi cho cái sau để biết phải làm gì. Nó cũng có thể là một tài nguyên không có sẵn tại thời điểm này nhưng có thể sẽ trở lại sau một phút (một hệ thống tệp được gắn từ xa).

  • Ví dụ, thứ hai là, nếu bạn đang cố gắng thêm một vectơ kích thước 10 vào một vectơ kích thước 20: Hãy thử như bạn có thể, không có gì có thể làm được về điều này - có một lỗi trong mã dẫn đến điểm mà chúng tôi đã cố gắng để làm bổ sung.

Hai điều kiện này nên được xử lý khác nhau, bất kể ngôn ngữ lập trình bạn đang sử dụng là gì:

  • Trong trường hợp thứ hai, vì không có sự truy đòi, chấm dứt chương trình. Bạn có thể làm điều đó bằng cách ném một ngoại lệ hoặc trả lại mã lỗi cho người gọi biết rằng không có gì có thể được thực hiện, nhưng bạn cũng có thể hủy bỏ chương trình ngay lập tức vì điều đó giúp lập trình viên dễ dàng gỡ lỗi hơn.

  • Trong trường hợp trước đây, một tình huống đặc biệt đã phát sinh có thể được xử lý. Mặc dù C và Fortran không có cách nào để diễn đạt điều này, tất cả các ngôn ngữ hợp lý mà sau này đã kết hợp các cách vào tiêu chuẩn ngôn ngữ để đối phó với các lợi nhuận "đặc biệt" như vậy bằng cách cung cấp, "ngoại lệ". Sử dụng những thứ này - đó là những gì họ đang ở đó; chúng cũng được thiết kế theo cách mà bạn không thể quên bỏ qua chúng (nếu bạn làm thế, ngoại lệ chỉ lan truyền một cấp độ cao hơn).

Nói cách khác, những gì tôi ủng hộ ở đây (và những gì thỏa thuận.II làm) là một hỗn hợp các chiến lược 3 và 5 của bạn, tùy thuộc vào ngữ cảnh. Đúng là 3 không hoạt động với các ngôn ngữ như C hoặc Fortran - trong trường hợp đó, người ta có thể lập luận rằng đó là lý do chính đáng để không sử dụng các ngôn ngữ gây khó khăn để diễn đạt những gì bạn muốn làm.

x), nhưng vì người đánh giá cần được gọi nhiều lần nên nó không bị sập mà chỉ ném một ngoại lệ. Trong những trường hợp như vậy, mặc dù việc chuyển qua một giá trị âm không thể phục hồi được, người ta nên ném một ngoại lệ thay vì hủy bỏ chương trình. Tôi đã không đồng ý với lập trường này một vài năm trước đây, nhưng đã thay đổi suy nghĩ của mình sau khi các nguyên tắc phần mềm cộng đồng xSDK mã hóa yêu cầu rằng các chương trình không bao giờ bị sập (hoặc ít nhất nên có cách chuyển từ sự cố sang ngoại lệ - vì vậy hãy giải quyết. Bây giờ tôi có tùy chọn để Assertném ngoại lệ thay vì gọi abort().)


Tôi chỉ đề nghị ngược lại: ném một ngoại lệ khi tình huống không thể được xử lý và trả lại mã lỗi khi nó có thể được xử lý. Vấn đề là việc xử lý các ngoại lệ bị ném rất khó: lập trình viên ứng dụng phải biết loại ngoại lệ có thể có để bắt và xử lý chúng, nếu không chương trình sẽ bị sập. Sự cố là ổn và thậm chí được hoan nghênh cho các tình huống không thể xử lý được, bởi vì điểm va chạm được báo cáo ngoài luồng với python, ví dụ, nhưng đối với các tình huống thể xử lý, thì (hầu hết) không được hoan nghênh.
cdalitz

@cdalitz: Đó là một lỗ hổng thiết kế của C ++ mà bạn có thể ném các đối tượng thuộc bất kỳ loại nào. Nhưng bất kỳ phần mềm hợp lý nào (loại trừ Trilinos) chỉ đưa ra các ngoại lệ có nguồn gốc từ đó std::exceptionvà chúng có thể bị bắt bởi tham chiếu mà không cần biết loại dẫn xuất.
Wolfgang Bangerth

1
Nhưng tôi hoàn toàn không đồng ý với việc trả lại mã lỗi vì những lý do được nêu trong câu hỏi ban đầu: (i) Mã lỗi bị bỏ qua quá thường xuyên và do đó lỗi không được xử lý; (ii) trong nhiều trường hợp, đơn giản là không có giá trị đặc biệt nào có thể được trả về một cách hợp lý nếu loại trả về của hàm được cố định; (iii) các hàm có các kiểu trả về khác nhau và bạn phải xác định riêng trong từng trường hợp giá trị "ngoại lệ" sẽ đại diện cho một lỗi.
Wolfgang Bangerth

WB đã viết (xin lỗi, thủ thuật '@' không hoạt động vì một số lý do và tên người dùng bị xóa bởi StackExchage vì một số lý do): "Mã lỗi bị bỏ qua quá thường xuyên". Điều này thậm chí còn nhiều hơn cho việc bắt ngoại lệ: không có nhiều nhà phát triển phần mềm gặp khó khăn trong việc đặt dấu ngoặc cho mỗi lệnh gọi hàm trong khối thử / bắt. Nhưng nó chủ yếu là vấn đề của hương vị: miễn là tài liệu nêu rõ liệu và ngoại lệ nào một chức năng ném, tôi có thể xử lý nó. Nhưng một lần nữa có thể nói: nhiệm vụ viết tài liệu bị bỏ qua quá thường xuyên ;-)
cdalitz

Nhưng vấn đề là nếu bạn quên bắt một ngoại lệ, thì không có vấn đề gì về hạ lưu: Chương trình chỉ hủy bỏ. Sẽ dễ dàng tìm thấy nơi xảy ra sự cố. Nếu bạn quên kiểm tra mã lỗi, chương trình của bạn có thể bị sập tại một số điểm sau đó do trạng thái bên trong không xác định - nhưng vấn đề ban đầu vẫn hoàn toàn không rõ ràng. Rất khó để tìm ra những loại bọ này.
Wolfgang Bangerth
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.