2009-03-21 21 views
29

Một vấn đề phổ biến trong bất kỳ ngôn ngữ nào là để khẳng định rằng các tham số được gửi đến một phương thức đáp ứng yêu cầu của bạn và nếu không, để gửi các thông báo lỗi đẹp, có thông tin. Loại mã này được lặp đi lặp lại nhiều lần, và chúng tôi thường cố gắng tạo ra những người trợ giúp cho nó. Tuy nhiên, trong C#, có vẻ như những người giúp đỡ bị buộc phải đối phó với một số trùng lặp buộc chúng tôi bởi ngôn ngữ và trình biên dịch. Để thể hiện ý tôi, hãy để tôi trình bày một số mã thô không có người giúp đỡ, theo sau là một người trợ giúp có thể. Sau đó, tôi sẽ chỉ ra sự trùng lặp trong helper và cụm từ câu hỏi của tôi một cách chính xác.Cú pháp mượt mà, hấp dẫn nhất mà bạn đã tìm thấy để xác nhận tính chính xác của thông số trong C# là gì?

Thứ nhất, các mã không có người giúp đỡ:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    if(firstName == null) 
    { 
      throw new WhateverException("The value for firstName cannot be null."); 
    } 

    if(lastName == null) 
    { 
      throw new WhateverException("The value for lastName cannot be null."); 
    } 

    // Same kind of code for age, making sure it is a reasonable range (< 150, for example). 
    // You get the idea 
} 

}

Bây giờ, các mã với một nỗ lực hợp lý tại một helper:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
     Helper.Validate(x=> x !=null, "firstName", firstName); 
     Helper.Validate(x=> x!= null, "lastName", lastName); 
} 

Các chính câu hỏi là: Lưu ý cách mã có s giá trị của thông số tên của tham số ("firstName" firstName). Điều này là để thông báo lỗi có thể nói, "Blah blah blah giá trị cho tham số firstName." Bạn đã tìm thấy cách nào để giải quyết vấn đề này bằng cách sử dụng sự phản chiếu hay bất cứ điều gì khác? Hoặc một cách để làm cho nó ít đau đớn?

Và thông thường hơn, bạn có tìm thấy bất kỳ cách nào khác để hợp lý hóa nhiệm vụ xác thực thông số này trong khi giảm trùng lặp mã không?

EDIT: Tôi đã đọc mọi người nói về việc sử dụng thuộc tính Tham số, nhưng không bao giờ tìm thấy cách nào đó xung quanh trùng lặp. Bất cứ ai có may mắn với điều đó?

Cảm ơn!

+0

Sử dụng của 'ApplicationException' lớp không được khuyến khích. –

+0

Nitpicking: _Direct_ sử dụng 'ApplicationException' không được khuyến nghị –

+0

OK, tôi đã đổi thành Ngoại lệ, chỉ để tránh bất kỳ phiền nhiễu nào.Là một sang một bên, nếu đề xuất được dựa trên ý kiến ​​từ những người đã viết cuốn sách "Hướng dẫn khung", sau đó tôi không thực sự thuyết phục (mặc dù những kẻ đã viết một số công cụ tốt). Nhưng đó là một cuộc thảo luận cho một ngày khác. –

Trả lời

6

Điều này có thể phần nào hữu ích:

Design by contract/C# 4.0/avoiding ArgumentNullException

+0

Cảm ơn! Điều này dẫn đến 2 cách tiếp cận đã thổi bay tôi đi! Và tôi có thể thấy sử dụng cả hai. –

+0

Việc thực hiện xác thực thông thạo đó là khá slick huh. – core

+0

Có, nó rất trơn. Tôi đã không nhận ra rằng bạn có thể gọi một phương pháp mở rộng trên một biến đó là null. Tôi không biết liệu Anders có nghĩa là điều đó sẽ xảy ra hay không nếu đó là một tác dụng phụ. Dù bằng cách nào, nó rất hữu ích. –

0

Trong trường hợp này, thay vì sử dụng loại ngoại lệ của riêng mình, hoặc các loại thực sự chung chung như ApplicationException .. Tôi nghĩ rằng đó là tốt nhất để sử dụng được xây dựng trong các loại ngoại lệ được dành riêng cho sử dụng này:

Trong số những người .. System.ArgumentException, System.ArgumentNullException ...

+0

OK, nhưng xin vui lòng ... chúng ta không tập trung vào loại ngoại lệ. Đó là phụ thuộc vào vấn đề cốt lõi ở đây và do đó tôi chỉ cần gõ thứ đầu tiên xuất hiện trong đầu tôi. –

+0

Tốt .. nhưng tôi tin rằng bất cứ lúc nào bạn đang ném ngoại lệ, ném một loại ngoại lệ hữu ích là hoàn toàn cần thiết. – markt

15

Bạn nên kiểm tra Code Contracts; họ làm khá nhiều chính xác những gì bạn đang yêu cầu. Ví dụ:

[Pure] 
public static double GetDistance(Point p1, Point p2) 
{ 
    CodeContract.RequiresAlways(p1 != null); 
    CodeContract.RequiresAlways(p2 != null); 
    // ... 
} 
+0

Hey, thật tuyệt ... đặc biệt là nó sẽ được tích hợp vào ngôn ngữ trong C# 4.0. Nhưng vẫn ... chúng ta có thể làm gì trong thời gian chờ đợi? –

+0

Vâng, chúng sẽ được nướng trong .NET 4, nhưng bạn vẫn có thể sử dụng chúng ngay bây giờ. Họ không phụ thuộc vào bất cứ điều gì cụ thể để .NET 4, vì vậy bạn có thể đi tất cả các con đường trở lại 2.0 nếu bạn muốn. Dự án là ở đây: http://research.microsoft.com/en-us/projects/contracts/ –

+0

Bạn có thể làm việc trên các lớp của riêng bạn mà trông như thế, nhưng ném ngoại lệ cho các thư viện. Khá đơn giản để viết lên một. – user7116

0

Không biết nếu kỹ thuật này vận chuyển từ C/C++ với C#, nhưng tôi đã làm điều này với các macro:

 

    #define CHECK_NULL(x) { (x) != NULL || \ 
      fprintf(stderr, "The value of %s in %s, line %d is null.\n", \ 
      #x, __FILENAME__, __LINE__); } 
+0

Đó là chính xác loại điều tôi hy vọng, nhưng đừng nghĩ rằng nó có thể được thực hiện trực tiếp trong C#. –

+0

#x ở đây được gọi là toán tử xâu chuỗi và không, chúng tôi không có bất kỳ thứ gì giống như trong C# –

+0

C# không hỗ trợ loại thủ thuật tiền xử lý bạn tìm thấy trong trình biên dịch C++. –

1

sở thích của tôi là nên chỉ đánh giá điều kiện và chuyển kết quả thay vì truyền một biểu thức được đánh giá và tham số để đánh giá nó. Ngoài ra, tôi thích có khả năng tùy chỉnh toàn bộ tin nhắn. Lưu ý rằng đây là những ưu tiên đơn giản - tôi không nói rằng mẫu của bạn là sai - nhưng có một số trường hợp điều này rất hữu ích.

Helper.Validate(firstName != null || !string.IsNullOrEmpty(directoryID), 
       "The value for firstName cannot be null if a directory ID is not supplied."); 
5

Sử dụng thư viện của tôi The Helper Trinity:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    firstName.AssertNotNull("firstName"); 
    lastName.AssertNotNull("lastName"); 

    ... 
} 

Cũng hỗ trợ khẳng định rằng các thông số liệt kê là chính xác, các bộ sưu tập và các nội dung của họ là phi null, các thông số chuỗi đều là phòng không trống Etcetera. Xem tài liệu hướng dẫn sử dụng here để biết các ví dụ chi tiết.

+0

Tuyệt. Tôi sẽ kiểm tra. –

+0

+1. Tôi thích sử dụng các phương pháp mở rộng để giảm thiểu số lượng từ. Tôi muốn gọi phương thức MustNotBeNull hoặc một cái gì đó thay vào đó, nhưng đó chỉ là sở thích cá nhân –

+0

Điều gì xảy ra trong mã đó nếu firstName là null? Sẽ không phải là một NullRef xảy ra? Không bao giờ cố gắng để xem nếu phương pháp mở rộng vẫn có thể đính kèm vào một con trỏ null. –

7

Tôi giải quyết vấn đề chính xác này cách đây vài tuần, sau khi nghĩ rằng việc kiểm tra các thư viện có vẻ như cần một triệu phiên bản khác nhau của Assert để làm cho thông điệp của họ có tính mô tả.

Here's my solution.

Giới thiệu tóm tắt tổng kết - trao chút mã này:

int x = 3; 
string t = "hi"; 
Assert(() => 5*x + (2/t.Length) < 99); 

chức năng Khẳng định của tôi có thể in ra các bản tóm tắt sau đây về những gì đang được truyền cho nó:

(((5 * x) + (2/t.Length)) < 99) == True where 
{ 
    ((5 * x) + (2/t.Length)) == 16 where 
    { 
    (5 * x) == 15 where 
    { 
     x == 3 
    } 
    (2/t.Length) == 1 where 
    { 
     t.Length == 2 where 
     { 
     t == "hi" 
     } 
    } 
    } 
} 

Vì vậy, tất cả các tên định danh và các giá trị và cấu trúc của biểu thức, có thể được bao gồm trong thông báo ngoại lệ, mà không cần phải đặt lại chúng trong chuỗi được trích dẫn.

+0

Ồ, thật tuyệt vời. Tôi không biết tại sao tôi không nghĩ về điều đó (những ý tưởng tốt thường kích động cảm giác đó). Tôi đang viết một ứng dụng tính toán mà điều này có thể rất tốt đẹp về giải thích các câu trả lời đến. –

+0

Sự kết hợp của cây phản ánh và biểu thức cho phép nhiều thứ khiến bạn ngạc nhiên - đặc biệt nếu (như tôi) bạn đến từ nền C/C++, nơi thời gian chạy không tồn tại. Chúng ta phải tái phát minh ra một loạt những thứ mà Lispers đã làm trong nửa thế kỷ! –

+0

Ý tưởng hay và cũng có thể sử dụng cho ứng dụng calc! – flq

11

Wow, tôi thấy một cái gì đó thực sự thú vị ở đây. Chris ở trên đã cung cấp liên kết đến một câu hỏi Stack Overflow khác. Một trong những câu trả lời có chỉ vào một bài đăng blog trong đó mô tả làm thế nào để nhận được mã như thế này:

public static void Copy<T>(T[] dst, long dstOffset, T[] src, long srcOffset, long length) 
{ 
    Validate.Begin() 
      .IsNotNull(dst, “dst”) 
      .IsNotNull(src, “src”) 
      .Check() 
      .IsPositive(length) 
      .IsIndexInRange(dst, dstOffset, “dstOffset”) 
      .IsIndexInRange(dst, dstOffset + length, “dstOffset + length”) 
      .IsIndexInRange(src, srcOffset, “srcOffset”) 
      .IsIndexInRange(src, srcOffset + length, “srcOffset + length”) 
      .Check(); 

    for (int di = dstOffset; di < dstOffset + length; ++di) 
     dst[di] = src[di - dstOffset + srcOffset]; 
} 

tôi không tin nó là các trả lời tốt nhất, nhưng nó chắc chắn là thú vị. Here's the blog post, từ Rick Brewster.

6

Được rồi, tôi lại là một lần nữa, và tôi đã tìm thấy một thứ gì đó thật đáng kinh ngạc và thú vị. Đó là một bài đăng trên blog khác được đề cập đến từ câu hỏi SO khác mà Chris, ở trên, đã đề cập.

Cách tiếp cận của anh chàng này cho phép bạn viết này:

public class WebServer 
    { 
     public void BootstrapServer(int port, string rootDirectory, string serverName) 
     { 
      Guard.IsNotNull(() => rootDirectory); 
      Guard.IsNotNull(() => serverName); 

      // Bootstrap the server 
     } 
    } 

Lưu ý rằng không có chuỗi chứa "rootDirectory" và không có chuỗi chứa "serverName" !! Và thông báo lỗi của anh ta có thể nói điều gì đó như "Tham số rootDirectory không được rỗng."

Đây chính là điều tôi muốn và nhiều hơn tôi mong đợi. Here's the link to the guy's blog post.

Và việc thực hiện khá đơn giản, như sau:

public static class Guard 
    { 
     public static void IsNotNull<T>(Expression<Func<T>> expr) 
     { 
      // expression value != default of T 
      if (!expr.Compile()().Equals(default(T))) 
       return; 

      var param = (MemberExpression) expr.Body; 
      throw new ArgumentNullException(param.Member.Name); 
     } 
    } 

Lưu ý rằng điều này làm cho việc sử dụng "phản ánh tĩnh", do đó trong một vòng lặp chặt chẽ hoặc một cái gì đó, bạn có thể muốn sử dụng phương pháp Rick Brewster trên .

Ngay sau khi tôi đăng bài này, tôi sẽ bỏ phiếu cho Chris và phản hồi cho câu hỏi SO khác. Đây là một số công cụ tốt!!!

+1

Xin chào, hơi muộn khi nhận thấy điều này, nhưng tôi nghĩ rằng nó thiếu sức mạnh tổng quát của biểu thức một chút.Nhìn vào câu trả lời của tôi một lần nữa - bạn không cần phải viết một chức năng riêng biệt cho từng loại xác nhận bạn có thể muốn thực hiện. Chỉ cần viết một biểu thức trong cú pháp C# bình thường: 'X.Assert (() => serverName! = Null);' Không có lý do chính đáng để tích lũy một tập hợp các phương thức đặc biệt (IsNull, IsNotNull, AreEqual, vv) cho mỗi loại của khẳng định, bởi vì chúng ta có thể có được một mô tả đầy đủ của bất kỳ biểu thức nào và tất cả các giá trị trong nó, bất kể biểu thức nào xảy ra. –

+0

Earwicker, đó là một quan sát thú vị. Tôi thích nó, mặc dù nó không phải là một dunk slam và có thể có trường hợp mà tôi muốn đi với phương pháp tiếp cận nhiều phương pháp (IsNull, IsNotNull, vv) Hoặc ít nhất tôi muốn tranh luận với bản thân mình về nó. Các thông báo lỗi có thể được prettier nói "FirstName không thể null" hơn "The assertion (FirstName! = Null) thất bại". Thêm vào đó, nếu bạn có một trăm nơi mà bạn khẳng định (cái gì đó! = Null), điều đó giống như việc sao chép mã. Tôi không chắc liệu đó có phải là bản sao có hại hay không. Có thể phụ thuộc. Nhưng quan sát mát mẻ, cảm ơn. –

0

Nó không áp dụng ở khắp mọi nơi, nhưng nó có thể giúp đỡ trong nhiều trường hợp:

Tôi giả sử rằng "SomeMethod" đang tiến hành một số hoạt động về hành vi trên các dữ liệu "tên cuối cùng", "tên đầu tiên" và "tuổi" . Đánh giá thiết kế mã hiện tại của bạn. Nếu ba phần dữ liệu đang khóc cho một lớp, hãy đưa chúng vào một lớp học. Trong lớp học đó, bạn cũng có thể đặt séc của mình. Điều này sẽ miễn phí "SomeMethod" từ kiểm tra đầu vào.

Kết quả cuối cùng sẽ là một cái gì đó như thế này:

public void SomeMethod(Person person) 
{ 
    person.CheckInvariants(); 
    // code here ... 
} 

Cuộc gọi sẽ là một cái gì đó như thế này (nếu bạn sử dụng .NET 3.5):

SomeMethod(new Person { FirstName = "Joe", LastName = "White", Age = 12 }); 

theo giả định rằng lớp sẽ trông giống như sau:

public class Person 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 

    public void CheckInvariants() 
    { 
     assertNotNull(FirstName, "first name"); 
     assertNotNull(LastName, "last name"); 
    } 

    // here are your checks ... 
    private void assertNotNull(string input, string hint) 
    { 
     if (input == null) 
     { 
      string message = string.Format("The given {0} is null.", hint); 
      throw new ApplicationException(message); 
     } 
    } 

Thay vì đường cú pháp .NET 3.5 bạn cũng có thể sử dụng hàm tạo đối số để tạo đối tượng Person.

0

Tương tự như vậy, this đăng bởi Miško Hevery trên Google Testing Blog cho rằng loại kiểm tra thông số này có thể không phải lúc nào cũng là một điều tốt. Các cuộc tranh luận kết quả trong các ý kiến ​​cũng làm tăng một số điểm thú vị.

+0

Có sự thật cho quan điểm này. Tôi không nhất thiết phải nói về các hợp đồng thực thi tôn giáo (tôi nghĩ điều đó tốt trong một số trường hợp nhưng không phải tất cả). Nhưng vẫn còn, một số phương pháp cần phải thực hiện kiểm tra nhất định, và thường bạn kết thúc mong muốn người giúp đỡ cho những trường hợp đó. –

+0

Chắc chắn các loại séc này rất hữu ích trong nhiều tình huống, tôi chỉ muốn ý kiến ​​tương phản xung quanh cho các thế hệ tương lai :) –

2

Lokad Shared Libraries cũng có phân tích cú pháp IL thực hiện dựa trên điều này, tránh phải trùng lặp tên thông số trong chuỗi.

Ví dụ:

Enforce.Arguments(() => controller,() => viewManager,() => workspace); 

sẽ ném một ngoại lệ với tên tham số thích hợp nếu bất kỳ đối số được liệt kê là null. Nó cũng có một quy tắc thực thi chính sách dựa trên thực sự gọn gàng.

ví dụ:

Enforce.Argument(() => username, StringIs.Limited(3, 64), StringIs.ValidEmail); 
+0

Rất tốt. Cảm ơn đã chỉ cho tôi ở đó. –

3

Đây là câu trả lời của tôi cho vấn đề. Tôi gọi nó là "Guard Claws". Nó sử dụng bộ phân tích IL từ Shared Libs Lokad nhưng có một cách tiếp cận đơn giản hơn để nêu các điều khoản bảo vệ thực tế:

string test = null; 
Claws.NotNull(() => test); 

Bạn có thể xem chi tiết ví dụ về việc sử dụng nó trong specs.

Vì nó sử dụng lambdas thực làm đầu vào và chỉ sử dụng trình phân tích cú pháp IL để tạo ra ngoại lệ trong trường hợp vi phạm nên thực hiện tốt hơn trên "đường dẫn hạnh phúc" hơn thiết kế dựa trên biểu thức ở nơi khác trong các câu trả lời này.

Các liên kết không làm việc, đây là URL: http://github.com/littlebits/guard_claws/

+0

Tuyệt. Bất kỳ kế hoạch sử dụng params args để cho phép một số biến được xác minh? –

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