Làm thế nào tôi nên xử lý đầu vào người dùng không hợp lệ?


12

Tôi đã suy nghĩ về vấn đề này trong một thời gian và tôi sẽ tò mò muốn có ý kiến ​​từ các nhà phát triển khác.

Tôi có xu hướng có một phong cách lập trình rất phòng thủ. Khối hoặc phương thức điển hình của tôi trông như thế này:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

Ngoài ra, trong quá trình tính toán, tôi kiểm tra tất cả các con trỏ trước khi hủy bỏ chúng. Ý tưởng của tôi là, nếu có một số lỗi và một số con trỏ NULL sẽ xuất hiện ở đâu đó, chương trình của tôi sẽ xử lý việc này một cách độc đáo và chỉ cần từ chối tiếp tục tính toán. Tất nhiên nó có thể thông báo về vấn đề với thông báo lỗi trong nhật ký hoặc một số cơ chế khác.

Nói một cách trừu tượng hơn, cách tiếp cận của tôi là

if all input is OK --> compute result
else               --> do not compute result, notify problem

Các nhà phát triển khác, trong số họ, một số đồng nghiệp của tôi, sử dụng một chiến lược khác. Ví dụ, họ không kiểm tra con trỏ. Họ cho rằng một đoạn mã phải được cung cấp đầu vào chính xác và nó không chịu trách nhiệm cho những gì xảy ra nếu đầu vào sai. Ngoài ra, nếu một ngoại lệ con trỏ NULL làm hỏng chương trình, một lỗi sẽ được tìm thấy dễ dàng hơn trong quá trình thử nghiệm và có nhiều cơ hội được sửa chữa hơn.

Câu trả lời của tôi cho điều này là bình thường: nhưng nếu lỗi không được tìm thấy trong quá trình thử nghiệm và xuất hiện khi sản phẩm đã được sử dụng bởi khách hàng thì sao? Một cách ưa thích cho các lỗi để hiển thị chính nó là gì? Nó có nên là một chương trình không thực hiện một hành động nhất định, nhưng vẫn có thể tiếp tục hoạt động, hoặc một chương trình gặp sự cố và cần phải được khởi động lại?

Tóm tắt

Bạn có lời khuyên nào trong hai cách tiếp cận để xử lý dữ liệu nhập sai?

Inconsistent input --> no action + notification

hoặc là

Inconsistent input --> undefined behaviour or crash

Biên tập

Cảm ơn câu trả lời và đề xuất. Tôi là một fan hâm mộ của thiết kế theo hợp đồng quá. Nhưng ngay cả khi tôi tin tưởng người đã viết mã gọi phương thức của tôi (có thể là chính tôi), vẫn có thể có lỗi, dẫn đến nhập sai. Vì vậy, cách tiếp cận của tôi là không bao giờ giả sử một phương thức được thông qua đầu vào chính xác.

Ngoài ra, tôi sẽ sử dụng một cơ chế để nắm bắt vấn đề và thông báo về nó. Trên một hệ thống phát triển, nó sẽ mở một hộp thoại để thông báo cho người dùng. Trong một hệ thống sản xuất, nó sẽ chỉ ghi một số thông tin vào nhật ký. Tôi không nghĩ rằng kiểm tra thêm có thể dẫn đến các vấn đề về hiệu suất. Tôi không chắc chắn nếu các xác nhận là đủ, nếu chúng bị tắt trong một hệ thống sản xuất: có thể một số tình huống sẽ xảy ra trong sản xuất không xảy ra trong quá trình thử nghiệm.

Dù sao, tôi thực sự ngạc nhiên khi nhiều người theo cách tiếp cận ngược lại: họ để ứng dụng gặp sự cố "cố ý" vì họ cho rằng điều này sẽ giúp tìm lỗi dễ dàng hơn trong quá trình thử nghiệm.


Luôn luôn phòng thủ mã. Cuối cùng, vì lý do hiệu suất, bạn có thể đặt một công tắc để vô hiệu hóa một số thử nghiệm trong chế độ phát hành.
deadalnix

Hôm nay tôi đã sửa một lỗi liên quan đến kiểm tra con trỏ NULL bị thiếu. Một số đối tượng đã được tạo trong quá trình đăng xuất ứng dụng và hàm tạo đã sử dụng một getter để truy cập một số đối tượng khác không còn ở đó nữa. Đối tượng không có nghĩa là được tạo ra tại thời điểm đó. Nó được tạo do một lỗi khác: một số bộ đếm thời gian không bị dừng trong khi đăng xuất -> một tín hiệu đã được gửi -> người nhận đã cố gắng tạo một đối tượng -> constructor đã truy vấn và sử dụng một đối tượng khác -> con trỏ NULL -> crash ). Tôi thực sự không muốn một tình huống khó chịu như vậy làm hỏng ứng dụng của tôi.
Giorgio

1
Quy tắc sửa chữa: Khi bạn phải thất bại, thất bại ồn ào và càng sớm càng tốt.
deadalnix

"Quy tắc sửa chữa: Khi bạn phải thất bại, thất bại một cách ồn ào và càng sớm càng tốt.": Tôi đoán tất cả các BSOD của Windows là một ứng dụng của quy tắc này. :-)
Giorgio

Câu trả lời:


8

Bạn đã hiểu đúng. Hãy hoang tưởng. Đừng tin tưởng mã khác, ngay cả khi đó là mã của riêng bạn. Bạn quên mọi thứ, bạn thay đổi, mã phát triển. Đừng tin tưởng mã bên ngoài.

Một điểm tốt đã được thực hiện ở trên: nếu đầu vào không hợp lệ nhưng chương trình không bị sập thì sao? Sau đó, bạn nhận được rác trong cơ sở dữ liệu và lỗi xuống dòng.

Khi được hỏi về một số (ví dụ: giá bằng đô la hoặc số đơn vị) tôi muốn nhập "1e9" và xem mã này làm gì. Nó có thể xảy ra.

Bốn thập kỷ trước, nhận bằng Cử nhân Khoa học Máy tính của tôi từ UCBer siêu, chúng tôi đã được thông báo rằng một chương trình tốt là xử lý lỗi 50%. Hãy hoang tưởng.


Vâng, IMHO đây là một trong số ít các tình huống trong đó bị hoang tưởng là một tính năng và không phải là vấn đề.
Giorgio

"Điều gì sẽ xảy ra nếu đầu vào không hợp lệ nhưng chương trình không gặp sự cố? Sau đó, bạn nhận được rác trong cơ sở dữ liệu và lỗi xuống dòng.": Thay vì sự cố, chương trình có thể từ chối thực hiện thao tác và trả về kết quả không xác định. Không xác định sẽ lan truyền thông qua tính toán và không có rác sẽ được tạo ra. Nhưng chương trình không cần phải sụp đổ để đạt được điều này.
Giorgio

Có, nhưng - quan điểm của tôi là chương trình phải XÁC ĐỊNH đầu vào không hợp lệ và đối phó với nó. Nếu đầu vào không được kiểm tra, nó sẽ hoạt động vào hệ thống và những thứ khó chịu sẽ đến sau. Thậm chí sụp đổ còn tốt hơn thế!
Andy Canfield

Tôi hoàn toàn đồng ý với bạn: phương pháp hoặc chức năng điển hình của tôi bắt đầu bằng một chuỗi kiểm tra để đảm bảo rằng dữ liệu đầu vào là chính xác.
Giorgio

Hôm nay tôi lại có một xác nhận rằng chiến lược "kiểm tra mọi thứ, không tin tưởng gì" thường là một ý tưởng hay. Một đồng nghiệp của tôi có ngoại lệ con trỏ NULL vì thiếu kiểm tra. Hóa ra trong bối cảnh đó, việc có một con trỏ NULL là chính xác vì một số dữ liệu chưa được tải và việc kiểm tra con trỏ là chính xác và đơn giản là không làm gì khi đó là NULL. :-)
Giorgio

7

Bạn đã có ý tưởng đúng

Bạn có lời khuyên nào trong hai cách tiếp cận để xử lý dữ liệu nhập sai?

Đầu vào không nhất quán -> không có hành động + thông báo

hoặc tốt hơn

Đầu vào không nhất quán -> hành động được xử lý phù hợp

Bạn thực sự không thể thực hiện một phương pháp cắt cookie để lập trình (bạn có thể) nhưng bạn sẽ kết thúc với một thiết kế công thức làm những việc theo thói quen thay vì từ sự lựa chọn có ý thức.

Chủ nghĩa giáo điều ôn hòa với chủ nghĩa thực dụng.

Steve McConnell nói điều đó tốt nhất

Steve McConnell đã viết khá nhiều cuốn sách ( Code Complete ) về lập trình phòng thủ và đây là một trong những phương pháp mà ông khuyên rằng bạn nên luôn xác nhận đầu vào của mình.

Tôi không nhớ nếu Steve đề cập đến điều này, tuy nhiên bạn nên xem xét việc này cho các phương pháp và chức năng không riêng tư và chỉ những người khác khi thấy cần thiết.


2
Thay vì công khai, tôi sẽ đề nghị, tất cả các phương pháp không riêng tư để bảo vệ các ngôn ngữ đã được bảo vệ, chia sẻ hoặc không có khái niệm hạn chế truy cập (mọi thứ đều công khai, ngầm).
JustinC

3

Không có câu trả lời "đúng" ở đây, đặc biệt là không chỉ định ngôn ngữ, loại mã và loại sản phẩm mà mã có thể đi vào. Xem xét:

  • Vấn đề ngôn ngữ. Trong Mục tiêu-C, việc gửi tin nhắn đến số không thường xuyên; Không có gì xảy ra, nhưng chương trình cũng không sụp đổ. Java không có con trỏ rõ ràng, vì vậy con trỏ không phải là mối quan tâm lớn ở đó. Trong C, bạn cần cẩn thận hơn một chút.

  • Bị hoang tưởng là vô lý, nghi ngờ không chính đáng hoặc không tin tưởng. Điều đó có lẽ không tốt hơn cho phần mềm so với mọi người.

  • Mức độ quan tâm của bạn phải tương xứng với mức độ rủi ro trong mã và khó khăn có thể xảy ra trong việc xác định bất kỳ vấn đề nào xuất hiện. Điều gì xảy ra trong trường hợp xấu nhất? Người dùng khởi động lại chương trình và tiếp tục nơi họ rời đi? Công ty mất hàng triệu đô la?

  • Bạn không thể luôn xác định đầu vào xấu. Bạn có thể so sánh một cách tôn trọng con trỏ của bạn với con số không, nhưng điều đó chỉ bắt được một trong 2 ^ 32 giá trị có thể, gần như tất cả đều là xấu.

  • Có rất nhiều cơ chế khác nhau để xử lý lỗi. Một lần nữa, nó phụ thuộc vào một mức độ nào đó về ngôn ngữ. Bạn có thể sử dụng macro xác nhận, tuyên bố điều kiện, kiểm tra đơn vị, xử lý ngoại lệ, thiết kế cẩn thận và các kỹ thuật khác. Không ai trong số họ là hoàn hảo, và không ai phù hợp cho mọi tình huống.

Vì vậy, nó chủ yếu đi đến nơi bạn muốn đặt trách nhiệm. Nếu bạn đang viết thư viện để người khác sử dụng, có lẽ bạn muốn cẩn thận hết mức có thể về các đầu vào bạn nhận được và cố gắng hết sức để phát ra các lỗi hữu ích khi có thể. Trong các chức năng và phương thức riêng tư của riêng bạn, bạn có thể sử dụng các xác nhận để bắt những lỗi ngớ ngẩn nhưng nếu không thì sẽ đặt trách nhiệm cho người gọi (đó là bạn) để không chuyển rác vào.


+1 - Câu trả lời hay. Mối quan tâm chính của tôi là đầu vào sai có thể gây ra sự cố xuất hiện trên hệ thống sản xuất (khi quá muộn để làm điều gì đó về nó). Tất nhiên, tôi nghĩ bạn hoàn toàn đúng khi nói rằng nó phụ thuộc vào thiệt hại mà một vấn đề như vậy có thể gây ra cho người dùng.
Giorgio

Ngôn ngữ đóng một vai trò lớn. Trong PHP, một nửa mã phương thức của bạn kết thúc việc kiểm tra loại biến là gì và thực hiện hành động thích hợp. Trong Java nếu phương thức chấp nhận một int bạn không thể vượt qua nó bất cứ điều gì khác để phương thức của bạn kết thúc rõ ràng hơn.
chap

1

Chắc chắn phải có một thông báo, chẳng hạn như một ngoại lệ ném. Nó phục vụ như một người đứng đầu cho các lập trình viên khác, những người có thể đang sử dụng sai mã mà bạn đã viết (cố gắng sử dụng nó cho mục đích mà nó không có ý định làm) rằng đầu vào của họ không hợp lệ hoặc dẫn đến lỗi. Điều này rất hữu ích trong việc theo dõi các lỗi, trong khi nếu bạn chỉ trả về null, mã của họ sẽ tiếp tục cho đến khi họ cố gắng sử dụng kết quả và nhận được một ngoại lệ từ các mã khác nhau.

Nếu mã của bạn gặp lỗi trong khi gọi đến một mã khác (có thể là cập nhật cơ sở dữ liệu không thành công) nằm ngoài phạm vi của đoạn mã cụ thể đó, bạn thực sự không kiểm soát được nó và cách duy nhất của bạn là ném ngoại lệ giải thích điều gì bạn biết (chỉ những gì bạn được nói bởi mã bạn đã gọi). Nếu bạn biết rằng một số đầu vào chắc chắn sẽ dẫn đến kết quả như vậy, bạn không thể không thực thi mã của mình và đưa ra một ngoại lệ cho biết đầu vào nào không hợp lệ và tại sao.

Trên một ghi chú liên quan đến người dùng cuối, tốt nhất là trả lại một cái gì đó mô tả nhưng đơn giản để bất cứ ai cũng có thể hiểu nó. Nếu khách hàng của bạn gọi và nói rằng "chương trình bị hỏng, hãy sửa nó", bạn có rất nhiều công việc trong tay để theo dõi những gì đã sai và tại sao, và hy vọng bạn có thể tái tạo vấn đề. Sử dụng xử lý thích hợp các trường hợp ngoại lệ không chỉ có thể ngăn ngừa sự cố mà còn cung cấp thông tin có giá trị. Một cuộc gọi từ một khách hàng nói rằng "Chương trình đang báo lỗi cho tôi. Nó nói 'XYZ không phải là đầu vào hợp lệ cho phương thức M, vì Z quá lớn", hoặc một số điều như vậy, ngay cả khi họ không biết ý nghĩa của nó là gì, bạn biết chính xác nơi để tìm. Ngoài ra, tùy thuộc vào thực tiễn kinh doanh của công ty bạn, thậm chí bạn có thể không khắc phục những vấn đề này, vì vậy tốt nhất là để lại cho họ một bản đồ tốt.

Vì vậy, phiên bản ngắn của câu trả lời của tôi là tùy chọn đầu tiên của bạn là tốt nhất.

Inconsistent input -> no action + notify caller

1

Tôi đã vật lộn với vấn đề tương tự trong khi trải qua một lớp đại học về lập trình. Tôi nghiêng về phía hoang tưởng và có xu hướng kiểm tra mọi thứ nhưng được cho biết đây là hành vi sai lầm.

Chúng tôi đã được dạy "Thiết kế theo hợp đồng". Nhấn mạnh là các điều kiện tiên quyết, bất biến và hậu điều kiện được chỉ định trong các ý kiến ​​và tài liệu thiết kế. Là người triển khai phần mã của tôi, tôi nên tin tưởng vào kiến ​​trúc sư phần mềm và trao quyền cho họ bằng cách tuân theo các thông số kỹ thuật bao gồm các điều kiện tiên quyết (những phương thức đầu vào của tôi phải có khả năng xử lý và những đầu vào nào tôi sẽ không được gửi) . Kiểm tra quá mức trong mọi phương thức gọi kết quả là phình to.

Các xác nhận nên được sử dụng trong các lần lặp xây dựng để xác minh tính chính xác của chương trình (xác nhận các điều kiện tiên quyết, bất biến, điều kiện đăng). Các xác nhận sau đó sẽ được bật trong biên dịch sản xuất.


0

Sử dụng "xác nhận" là cách để thông báo cho các nhà phát triển đồng nghiệp rằng họ đang làm sai, tất nhiên chỉ là phương pháp "riêng tư" . Kích hoạt / vô hiệu hóa chúng chỉ là một cờ để thêm / xóa tại thời điểm biên dịch và do đó rất dễ dàng để loại bỏ các xác nhận khỏi mã sản xuất. Ngoài ra còn có một công cụ tuyệt vời để biết nếu bạn bằng cách nào đó làm sai trong phương pháp của riêng bạn.

Đối với việc xác minh các tham số đầu vào trong các phương thức công khai / được bảo vệ, tôi thích làm việc phòng thủ hơn và kiểm tra các tham số và ném UnlimitedArgumentException hoặc tương tự. Đó là lý do tại sao có ở đây cho. Nó cũng phụ thuộc vào việc bạn có viết API hay không. Nếu đó là API và thậm chí nhiều hơn nếu đó là nguồn đóng, hãy xác thực mọi thứ tốt hơn để các nhà phát triển biết chính xác những gì đã sai. Mặt khác, nếu nguồn có sẵn cho các nhà phát triển khác, nó không phải là màu đen / trắng. Chỉ cần phù hợp với sự lựa chọn của bạn.

Chỉnh sửa: chỉ để thêm rằng nếu bạn tìm ví dụ tại Oracle JDK, bạn sẽ thấy rằng họ không bao giờ kiểm tra "null" và để mã bị sập. Vì dù sao nó cũng sẽ ném NullPulumException, tại sao phải kiểm tra null và ném ngoại lệ rõ ràng. Tôi đoán nó có ý nghĩa gì đó.


Trong Java, bạn nhận được một ngoại lệ con trỏ null. Trong C ++, một con trỏ null làm hỏng ứng dụng. Có thể có những ví dụ khác: chia cho số 0, chỉ số ngoài phạm vi, v.v.
Giorgio
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.