2008-10-21 35 views
9

Bạn có nên luôn kiểm tra tham số và ném ngoại lệ trong .NET khi tham số không phải là những gì bạn mong đợi? Ví dụ. null đối tượng hoặc chuỗi rỗng?Luôn kiểm tra thông số và ném ngoại lệ

Tôi bắt đầu thực hiện việc này, nhưng sau đó nghĩ rằng điều này sẽ làm nổi bật mã của tôi rất nhiều nếu nó được thực hiện trên mọi phương thức. Tôi có nên kiểm tra các tham số cho cả phương thức riêng và công cộng không?

Tôi sẽ ném rất nhiều ngoại lệ ArgumentNullException ("name") mặc dù mã xử lý ngoại lệ không thực sự có thể làm bất kỳ điều gì khác theo chương trình vì không đảm bảo rằng "tên" sẽ không thay đổi trong tương lai.

Tôi cho rằng thông tin này chỉ hữu ích khi xem nhật ký đầy thông tin ngoại lệ?

Thực tiễn tốt nhất là luôn "đồng nhất cho điều tồi tệ nhất".

Trả lời

3

Tùy thuộc vào người tiêu dùng của lớp/phương pháp của bạn. Nếu đó là tất cả nội bộ, tôi sẽ nói nó không quan trọng. Nếu bạn có người tiêu dùng không xác định/bên thứ ba, thì có bạn sẽ muốn kiểm tra rộng rãi.

1

Triết lý của tôi trong trường hợp này là thông báo và tiếp tục (nếu có). Các pseudo-code sẽ là:

if value == not_valid then 
#if DEBUG 
    log failure 
    value = a_safe_default_value 
#elsif RELASE 
    throw 
#endif 
end 

Bằng cách này bạn có thể dễ dàng lặp quá trình phát triển, và có người sử dụng kiểm tra ứng dụng của bạn mà không có nó là một hành động trong sự thất vọng.

5

Không có gì tệ hơn là theo đuổi một tham chiếu 'đối tượng không được đặt thành thông báo của một đối tượng'. Nếu mã của bạn là đủ phức tạp, rất khó để biết những gì không - đặc biệt là trong các hệ thống sản xuất và đặc biệt là nếu nó trong một điều kiện biên giới hiếm. Một ngoại lệ rõ ràng đi một chặng đường dài trong xử lý sự cố những vấn đề đó. Đó là một nỗi đau, nhưng đó là một trong những điều bạn sẽ không hối tiếc nếu một cái gì đó xấu hiện xảy ra.

12

Hai xu của tôi: Tất cả các phương pháp công cộng của bạn phải luôn kiểm tra tính hợp lệ của các thông số được truyền. Điều này thường được gọi là "lập trình theo hợp đồng" và là cách tuyệt vời để nắm bắt lỗi nhanh chóng và tránh truyền chúng thông qua các chức năng riêng của bạn mà nhiều người sẽ tranh luận (bao gồm cả bản thân tôi) không nên là đơn vị được kiểm tra trực tiếp. Theo như ném ngoại lệ, nếu hàm hoặc chương trình của bạn không thể sửa lỗi chính nó, nó sẽ ném một ngoại lệ vì nó đã bị ném vào trạng thái không hợp lệ.

6

Đối với phương thức công khai, thì có: chắc chắn là cần thiết để kiểm tra đối số của bạn; cho các cuộc gọi nội bộ/riêng tư, Eric Lippert có thể phân loại chúng là "có tiêu đề" (here); lời khuyên của anh ta không phải là bắt họ ... sửa mã!

Để tránh mã bloat, bạn có thể muốn xem xét AOP, ví dụ: postsharp. Để minh họa, Jon Skeet có thuộc tính postsharp thực hiện kiểm tra đối số null, here. Sau đó (để trích dẫn ví dụ của mình), bạn chỉ có thể chỉ định phương thức:

[NullArgumentAspect("text")] 
public static IEnumerable<char> GetAspectEnhancedEnumerable(string text) 
{ /* text is automatically checked for null, and an ArgumentNullException thrown */ } 

Một mẹo tiện dụng khác ở đây có thể là các phương pháp mở rộng; các phương thức mở rộng có tính năng tò mò mà bạn có thể gọi chúng trên các cá thể rỗng ...vì vậy bạn có thể làm như sau (trong đó việc sử dụng Generics thay vì "đối tượng" là vì vậy bạn không vô tình gọi nó bằng cách đấm bốc một giá trị-type):

static void ThrowIfNull<T>(this T value, string name) where T : class 
{ 
    if (value == null) throw new ArgumentNullException(name); 
} 
// ... 
stream.ThrowIfNull("stream"); 

Và bạn có thể làm điều tương tự với dùng ngoài trời of-range vv

1

cách tiếp cận tôi đi là để kiểm tra đối số và ném ngoại lệ các thành viên hiển thị công khai - bất kỳ bằng cách hiển thị công khai tôi là ranh giới lắp ráp bên ngoài (vì vậy bất kỳ public, protected hoặc protected internal phương pháp trên một lớp public này. là bởi vì bạn (thường) thiết kế một hội đồng để hoạt động như một đơn vị tự trị nên bất cứ điều gì trong giới hạn của hội đồng nên tuân thủ các quy tắc về cách ca sẽ bất cứ điều gì khác trong đó.

Đối với bất kỳ thành viên không hiển thị công khai nào (ví dụ: internal hoặc private thành viên hoặc lớp học), tôi sử dụng Debug.Assert để thực hiện việc kiểm tra thay thế. Bằng cách này, nếu bất kỳ người gọi nào trong hội đồng vi phạm hợp đồng bạn tìm ra ngay lập tức trong thời gian phát triển/thử nghiệm, nhưng bạn không có chi phí hiệu năng trong giải pháp được triển khai cuối cùng vì các câu lệnh này được xóa trong RELEASE builds.

0

Vâng, điều đó tùy thuộc. Nếu mã của bạn là sẽ lấy null anyway, và ném một ngoại lệ sau đó, nó có thể làm cho tinh thần hơn để đảm bảo rằng bạn có mã sạch sẽ hợp lý. Nếu nó có thể không được phát hiện bằng cách khác, hoặc việc dọn dẹp có thể chạy dài hoặc có thể có một cuộc gọi ngoài quy trình (ví dụ: cơ sở dữ liệu), bạn có thể không cố thay đổi thế giới không chính xác, sau đó thay đổi lại.

1

Phần lớn kinh nghiệm của tôi là với các hệ thống tương đối bị hạn chế, trong đó mã bloat thuộc loại này là không mong muốn. Vì vậy bản năng của riêng tôi là sử dụng xác nhận chỉ gỡ lỗi hoặc để hoàn toàn loại bỏ nó. Bạn hy vọng rằng bất kỳ đầu vào không hợp lệ nào có thể xảy ra trong khi kiểm tra người gọi cung cấp cho bạn các giá trị không tốt, vì vậy miễn là bạn thử nghiệm ở chế độ gỡ lỗi cũng như chế độ phát hành, bạn sẽ thấy chẩn đoán. Nếu không, bạn sẽ gỡ lỗi sự cố khi nó xảy ra.

Nếu kích thước và hiệu suất mã không quan trọng (và trong hầu hết tất cả mã, kiểm tra khoảng trống hoặc dải ô đơn giản sẽ không ảnh hưởng đến hiệu suất), thì càng khẳng định bạn để lại mã trong chế độ phát hành thì càng có nhiều cơ hội có chẩn đoán lỗi mà không cần phải tạo lại lỗi trong chế độ thử nghiệm. Đây có thể là một tiết kiệm thời gian lớn. Đặc biệt, nếu sản phẩm của bạn là thư viện thì một tỷ lệ đáng kể các báo cáo "lỗi" là do lỗi của khách hàng, do đó không có số tiền kiểm tra trước khi phát hành có thể ngăn chúng xảy ra trong môi trường hoang dã. Bạn càng sớm có thể chứng minh với khách hàng rằng mã của họ là sai, họ càng sớm có thể sửa chữa nó và bạn có thể quay lại tìm lỗi của riêng bạn.

Tuy nhiên, trong C/C++ tôi thấy rằng trường hợp cụ thể của việc kiểm tra con trỏ null chỉ là một trợ giúp nhỏ. Nếu ai đó chuyển cho bạn một con trỏ, thì điều kiện hiệu lực đầy đủ không phải là "không được rỗng". Nó cần phải trỏ đến bộ nhớ có thể đọc được (có lẽ cũng có thể ghi) bởi quá trình hiện tại với kích thước nhất định và chứa đúng loại đối tượng, có thể trong một tập con nhất định của tất cả các trạng thái có thể. Nó không cần phải được giải phóng, không bị đụng chạm bởi một bộ đệm tràn ngập ở nơi khác, có thể không được đồng thời sửa đổi bởi một chủ đề khác, vv Bạn sẽ không kiểm tra tất cả những gì ở mục nhập, vì vậy bạn vẫn có thể bỏ lỡ thông số. Bất cứ điều gì dẫn bạn hoặc các lập trình viên khác nghĩ "con trỏ này không phải là rỗng vì vậy nó phải hợp lệ", bởi vì bạn đã thử nghiệm chỉ một phần nhỏ của điều kiện hợp lệ, là gây hiểu lầm.

Nếu bạn đang đi qua con trỏ, bạn đã ở trong lãnh thổ nơi bạn cần tin tưởng người gọi không cho bạn rác. Việc từ chối một trường hợp rác cụ thể vẫn khiến bạn tin tưởng người gọi không cho bạn bất kỳ loại rác nào khác mà họ có thể gợi ý khó phát hiện hơn.Nếu bạn thấy rằng con trỏ null là một loại rác phổ biến từ người gọi cụ thể của bạn, thì bằng mọi cách kiểm tra cho chúng, vì nó giúp tiết kiệm thời gian chẩn đoán lỗi ở những nơi khác trong hệ thống. Nó phụ thuộc vào việc đánh giá xem có phát hiện lỗi trong mã người gọi của bạn với dấu hiệu "truyền con trỏ null cho tôi" hay không, có giá trị mã của chính bạn (có lẽ ở kích thước nhị phân, và chắc chắn trong mã nguồn): có thể lãng phí thời gian và kiểm tra bất động sản trên màn hình cho họ.

Tất nhiên trong một số ngôn ngữ bạn không vượt qua bằng con trỏ và người gọi chỉ có cơ hội hạn chế để làm hỏng bộ nhớ, vì vậy có ít phạm vi hơn cho rác. Nhưng trong Java, ví dụ, vượt qua đối tượng sai vẫn là một lỗi lập trình phổ biến hơn là truyền một null sai. Nulls là trong mọi trường hợp thường khá dễ dàng để chẩn đoán nếu bạn để chúng vào thời gian chạy để phát hiện, và nhìn vào stacktrace. Vì vậy, giá trị của kiểm tra null là khá hạn chế ngay cả ở đó. Trong C++ và C# bạn có thể sử dụng pass-by-reference nơi null sẽ bị cấm.

Điều tương tự cũng xảy ra đối với bất kỳ đầu vào không hợp lệ cụ thể nào khác mà bạn có thể kiểm tra và bất kỳ ngôn ngữ nào. Kiểm tra trước và sau điều kiện đầy đủ (nếu có thể), tất nhiên là một vấn đề khác, vì nếu bạn có thể kiểm tra toàn bộ hợp đồng cuộc gọi thì bạn đang ở trên nền tảng vững chắc hơn nhiều. Và nếu bạn có thể sử dụng dệt hoặc bất cứ điều gì để khẳng định các hợp đồng mà không cần thêm vào mã nguồn của chính hàm đó, điều đó thậm chí còn tốt hơn.

Các vấn đề liên quan