2009-03-06 39 views
16

Giả sử chúng ta có phương thức chấp nhận giá trị của một liệt kê. Sau khi phương pháp này kiểm tra rằng giá trị là hợp lệ, nó switch es trên các giá trị có thể. Vì vậy, câu hỏi đặt ra là, phương pháp ưu tiên xử lý các giá trị không mong muốn là gì sau khi phạm vi giá trị đã được xác thực?Phương pháp ưu tiên để xử lý các giá trị enum bất ngờ là gì?

Ví dụ:

enum Mood { Happy, Sad } 

public void PrintMood(Mood mood) 
{ 
    if (!Enum.IsDefined(typeof(Mood), mood)) 
    { 
     throw new ArgumentOutOfRangeException("mood"); 
    } 

    switch (mood) 
    { 
     case Happy: Console.WriteLine("I am happy"); break; 
     case Sad: Console.WriteLine("I am sad"); break; 
     default: // what should we do here? 
    } 

phương pháp ưu tiên xử lý các trường hợp default là gì?

  • Leave a comment như // can never happen
  • Debug.Fail() (hoặc Debug.Assert(false))
  • throw new NotImplementedException() (hoặc bất kỳ ngoại lệ khác)
  • Một số cách khác mà tôi đã không nghĩ đến
+0

Tôi đăng một câu trả lời với ý kiến ​​của riêng tôi. Nếu bạn có một ý kiến ​​khác, hoặc một trường hợp khác có hỗ trợ ý kiến ​​này, hãy chia sẻ kiến ​​thức của bạn. Mục đích của câu hỏi là thu thập ý tưởng về chủ đề, để giúp người khác (và tôi) đối mặt với tình huống này. –

+0

tại sao được đánh dấu là java? – OscarRyz

+0

@ OSC: Nó phải là một câu hỏi độc lập về ngôn ngữ IMHO. Mọi người có thể tìm thấy câu trả lời cho cả hai ngôn ngữ ở đây. –

Trả lời

10

Tôi thích throw new NotImplementedException("Unhandled Mood: " + mood). Vấn đề là việc liệt kê có thể thay đổi trong tương lai và phương pháp này có thể không được cập nhật cho phù hợp. Ném một ngoại lệ có vẻ là phương pháp an toàn nhất.

Tôi không thích phương pháp Debug.Fail(), bởi vì phương pháp này có thể là một phần của thư viện và các giá trị mới có thể không được kiểm tra ở chế độ gỡ lỗi. Các ứng dụng khác sử dụng thư viện đó có thể đối mặt với hành vi thời gian chạy lạ trong trường hợp đó, trong khi trong trường hợp ném ngoại lệ, lỗi sẽ được biết ngay lập tức.

Lưu ý: NotImplementedException tồn tại trong commons.lang.

+0

Điều này cần phải được chỉnh sửa vào bài đăng gốc của bạn. –

+1

@SnOrfus: Tại sao? Đó là một câu trả lời hoàn toàn hợp lệ. –

+0

Như tôi đã nói trong câu trả lời của tôi, nó có thể hợp lệ, nhưng nó sai là OO khủng khiếp ... –

1

Bạn có thể có một dấu vết để mặc định gọi ra giá trị của enum được truyền. Ném ngoại lệ là OK nhưng trong một ứng dụng lớn sẽ có một số nơi mà mã của bạn không quan tâm đến các giá trị khác của enum.
Vì vậy, trừ khi bạn chắc chắn rằng mã có ý định xử lý tất cả các giá trị có thể có của enum, bạn sẽ phải quay lại sau và loại bỏ ngoại lệ.

7

Trong Java, cách tiêu chuẩn là để ném một AssertionError, vì hai lý do:

  1. Điều này đảm bảo rằng ngay cả khi asserts bị vô hiệu hóa, một lỗi được ném.
  2. Bạn đang khẳng định rằng không có giá trị enum nào khác, do đó, AssertionError sẽ ghi lại các giả định của bạn tốt hơn NotImplementedException (trong đó Java không có).
+0

Nhưng AssertionError mở rộng java.lang.Error, không phải java.lang.Exception. Điều này có nghĩa là các lớp khác trong mã sẽ không có cơ hội bắt ngoại lệ và phục hồi nó. (Về mặt kỹ thuật, họ có thể, nhưng thực tế hầu hết mọi người không (và không nên) bắt java.lang.Error.) –

+0

Nếu bạn đã kiểm tra xem nó có hợp lệ hay không, AssertionError sẽ không bao giờ xảy ra (nghĩa là, sau khi tất cả, những gì bạn đang khẳng định). Nếu nó xảy ra, mọi thứ quá sai lầm đến nỗi tôi nghĩ sẽ vô ích khi cố bắt nó. Nhưng tôi thấy rằng đôi khi nó có thể hữu ích để làm cho nó dễ bắt được. –

+0

Xem thêm phần thảo luận trong câu hỏi này: http://stackoverflow.com/questions/398953/java-what-is-the-preferred-throwable-to-use-in-a-private-utility-class-construct –

2

Đối với khá nhiều mỗi câu lệnh switch trong cơ sở mã của tôi, tôi có trường hợp mặc định sau

switch(value) { 
... 
default: 
    Contract.InvalidEnumValue(value); 
} 

Phương pháp này sẽ ném một ngoại lệ quy định chi tiết giá trị của enum tại điểm một lỗi được phát hiện.

public static void InvalidEnumValue<T>(T value) where T: struct 
{ 
    ThrowIfFalse(typeof(T).IsEnum, "Expected an enum type"); 
    Violation("Invalid Enum value of Type {0} : {1}", new object[] { typeof(T).Name, value }); 
} 
+0

Nhưng giá trị có thể hợp lệ, mặc dù phương pháp này chưa được cập nhật để phản ánh nó. Tôi nghĩ rằng thông báo "Giá trị không hợp lệ" hơi gây hiểu lầm ở đây. –

+0

@Hosam, nhưng mã đã được viết không mong đợi giá trị đó là lý do tại sao tôi đã chọn tên. "Giá trị bất ngờ" sẽ hoạt động tốt như tôi giả sử, nhưng nó có nghĩa là để nắm bắt rằng giá trị thực sự không phải là những gì các lập trình viên dự định – JaredPar

+0

@ JaredPar: Đủ công bằng. Tôi đã suy nghĩ nhiều hơn dọc theo dòng của "giá trị không đúng", mà làm cho nó khá rõ ràng rằng đó là một trường hợp unhandled (IMHO). –

0

Gọi nó là ý kiến ​​hoặc tùy chọn, nhưng ý tưởng đằng sau enum là nó đại diện cho danh sách đầy đủ các giá trị có thể. Nếu một "giá trị không mong muốn" đang được truyền xung quanh trong mã, thì enum (hoặc mục đích đằng sau enum) không được cập nhật. Sở thích cá nhân của tôi là mỗi enum mang một nhiệm vụ mặc định là Undefined.Cho rằng enum là một danh sách được xác định, nó sẽ không bao giờ bị lỗi thời với mã tiêu thụ của bạn.

Theo như những gì cần làm nếu hàm của bạn nhận được một giá trị không mong muốn hoặc Không xác định trong trường hợp của tôi, câu trả lời chung dường như không thể. Đối với tôi, nó phụ thuộc vào bối cảnh lý do để đánh giá giá trị enum: đó có phải là một tình huống mà việc thực thi mã phải tạm dừng hoặc có thể sử dụng giá trị mặc định không?

3

Ý kiến ​​của tôi là vì đó là lỗi lập trình, bạn nên khẳng định hoặc ném một RuntimException (Java, hoặc bất kỳ tương đương nào cho các ngôn ngữ khác). Tôi có UnhandledEnumException của riêng tôi mà kéo dài từ RuntimeException mà tôi sử dụng cho việc này.

0

Đó là trách nhiệm của hàm gọi để cung cấp đầu vào hợp lệ và bất kỳ thứ gì không có trong enum không hợp lệ (trình lập trình thực dụng dường như hàm ý điều này). Điều đó nói rằng, điều này ngụ ý rằng bất cứ khi nào bạn thay đổi enum của mình, bạn phải thay đổi TẤT CẢ mã chấp nhận nó làm đầu vào (và mã SOME tạo ra nó làm đầu ra). Nhưng đó có lẽ là sự thật. Nếu bạn có một enum thay đổi thường xuyên, bạn có lẽ nên sử dụng một cái gì đó khác hơn là một enum, xem xét rằng enums thường là các thực thể biên dịch.

+0

Tôi hoàn toàn đồng ý, nhưng trong thế giới thực, một vài phương pháp có thể bị bỏ qua khi enum được cập nhật, đó là lý do tại sao tôi hỏi câu hỏi. Bạn làm gì trong những trường hợp như vậy? –

3

Phản hồi chương trình chính xác sẽ là die theo cách cho phép nhà phát triển dễ dàng phát hiện sự cố. mmyersJaredPar cả hai đều có những cách tốt để thực hiện điều đó.

Tại sao lại chết? Điều đó có vẻ quá khắc nghiệt!

Lý do là nếu bạn không xử lý giá trị enum đúng cách và chỉ rơi qua, bạn sẽ đặt chương trình của mình thành trạng thái không mong muốn. Một khi bạn đang ở trong trạng thái bất ngờ, ai biết chuyện gì đang xảy ra. Điều này có thể dẫn đến dữ liệu xấu, lỗi khó theo dõi hoặc thậm chí là lỗ hổng bảo mật.

Ngoài ra, nếu chương trình chết, có nhiều khả năng bạn sẽ bắt nó trong QA và do đó nó thậm chí không đi ra khỏi cửa.

+0

Vâng, có vẻ như cực đoan! Sẽ không ném một ngoại lệ là đủ, theo nghĩa là một ứng dụng có thể phục hồi từ lỗi này? Nếu phương pháp này không quan trọng thì sao? Các trường hợp thất bại vẫn sẽ được ghi lại trong QA, vì chúng sẽ tạo ra các kết quả khác nhau hơn dự kiến. –

+0

Tuyệt vời, bạn phục hồi từ lỗi của bạn, nhưng bây giờ bạn không biết những gì nhà nước chương trình của bạn là in Như tôi đã nói, điều này sẽ dẫn đến các vấn đề subtler khác. QA sẽ thấy những lỗi tinh tế đó và báo cáo chúng, tất cả trong khi gốc của vấn đề không được giải quyết. –

+0

Không phải * tôi * người khôi phục từ lỗi, nhưng các lớp cao hơn trong hệ thống phân cấp cuộc gọi. Đây là một sự phân biệt quan trọng IMHO. Nếu lỗi đã được ghi lại (mà nên được), một thông báo lỗi tốt sẽ trực tiếp dẫn đến nguyên nhân gốc rễ. –

0

Tôi thường cố gắng để xác định giá trị không xác định (0):

enum Mood { Undefined = 0, Happy, Sad } 

Bằng cách đó tôi luôn luôn có thể nói:

switch (mood) 
{ 
    case Happy: Console.WriteLine("I am happy"); break; 
    case Sad: Console.WriteLine("I am sad"); break; 
    case Undefined: // handle undefined case 
    default: // at this point it is obvious that there is an unknown problem 
      // -> throw -> die :-) 
} 

Đây là ít nhất cách tôi thường làm điều này.

+0

Mặc dù tôi nhớ đã đọc ở đâu đó trong MSDN rằng người ta phải tạo một giá trị "Không xác định" cho một enum, tôi thấy đây là một vấn đề đối với một số enums không nên có giá trị như vậy (ví dụ: FileMode). Trừ khi nó cần thiết, nó buộc các nhà phát triển kiểm tra nó mỗi khi họ viết một phương thức. –

+1

Và trường hợp mặc định vẫn cần phải được xử lý, và đó là điểm của câu hỏi. –

+0

Vâng, tôi đã đề cập trong bình luận - ném và chết. Tôi sẽ ném ngoại lệ của riêng tôi có nguồn gốc từ lớp ngoại lệ. –

1

Đây là một trong những câu hỏi đó chứng minh lý do tại sao phát triển theo thử nghiệm là rất quan trọng.

Trong trường hợp này, tôi sẽ đi cho một NotSupportedException bởi vì nghĩa đen giá trị đã được unhandled và do đó không được hỗ trợ. Một NotImplementedException cung cấp thêm ý tưởng: "Điều này chưa hoàn thành";)

Mã gọi có thể xử lý tình huống như thế này và các bài kiểm tra đơn vị có thể được tạo để dễ dàng kiểm tra các loại tình huống này.

+0

Đây là một điểm tốt. Nhưng đôi khi bạn ném NotSupportedException cho các giá trị hợp lệ, như khi tạo một StreamReader với một FileMode.Write. Tôi đã nghĩ rằng trường hợp được thảo luận là khác nhau, đủ để nó không được xử lý như các trường hợp khác. –

2

Đối với C#, điều đáng biết là Enum.IsDefined() is dangerous. Bạn không thể dựa vào nó như bạn. Bắt một cái gì đó không phải của các giá trị dự kiến ​​là một trường hợp tốt cho ném một ngoại lệ và chết lớn tiếng. Trong Java, nó khác nhau bởi vì enums là các lớp không phải là số nguyên vì vậy bạn thực sự không thể nhận được các giá trị không mong muốn (trừ khi enum được cập nhật và câu lệnh switch của bạn không), đó là một lý do chính tại sao tôi thích Java enums .Bạn cũng phải phục vụ cho các giá trị null. Nhưng nhận được một trường hợp không null bạn không nhận ra là một trường hợp tốt cho ném một ngoại lệ quá.

+0

Tôi chắc chắn thích Java enums để C#. Bạn có thể giải thích về trường hợp ngoại lệ bạn muốn ném trong trường hợp các giá trị không được công nhận không? –

+0

Trong Java tôi muốn ném một IllegalStateException nếu tôi nhận được một giá trị enum tôi đã không nhận ra. IllegalArgumentException là OK nhưng với tôi nó có vẻ giống như nhà nước bất hợp pháp. – cletus

11

Tôi đoán hầu hết các câu trả lời ở trên đều hợp lệ, nhưng tôi không chắc chắn là đúng.

Câu trả lời đúng là bạn hiếm khi chuyển sang ngôn ngữ OO, nó cho biết bạn đang làm sai OO của mình. Trong trường hợp này, đó là một dấu hiệu hoàn hảo cho thấy lớp Enum của bạn có vấn đề.

Bạn chỉ nên gọi Console.WriteLine (mood.moodMessage()) và xác định moodMessage cho từng trạng thái.

Nếu một trạng thái mới được thêm vào - Tất cả mã của bạn nên thích ứng tự động, không có gì nên thất bại, ném ngoại lệ hoặc cần thay đổi.

Chỉnh sửa: trả lời nhận xét.

Trong ví dụ của bạn, thành "Good OO", chức năng của chế độ tệp sẽ được kiểm soát bởi đối tượng FileMode. Nó có thể chứa một đối tượng ủy nhiệm với các hoạt động "mở, đọc, viết ..." khác nhau cho mỗi FileMode, vì vậy File.open ("name", FileMode.Create) có thể được thực hiện như (xin lỗi về sự thiếu hiểu biết với API):

open(String name, FileMode mode) { 
    // May throw an exception if, for instance, mode is Open and file doesn't exist 
    // May also create the file depending on Mode 
    FileHandle fh = mode.getHandle(name); 
    ... code to actually open fh here... 
    // Let Truncate and append do their special handling 
    mode.setPosition(fh); 
} 

Đây là gọn gàng hơn nhiều so với cố gắng để làm điều đó với công tắc ... (bằng cách này, các phương pháp sẽ được cả hai gói tin và có thể giao cho "Chế độ" lớp)

Khi OO được thực hiện tốt, mọi phương thức đơn lẻ trông giống như một vài dòng mã đơn giản, dễ hiểu, rất đơn giản. Bạn luôn có được cảm giác rằng có một số lớn lộn xộn "Cheese Nucleus" tổ chức với nhau tất cả các đối tượng nacho nhỏ, nhưng bạn không bao giờ có thể tìm thấy nó - nó nachos tất cả các con đường xuống ...

+0

Giống như mọi thứ khác, enums là tốt khi chúng được sử dụng một cách chính xác. Ví dụ, nếu tôi đang thiết kế một API để xử lý tệp, tôi sẽ tạo ra 'File.Open (tên tệp chuỗi, chế độ FileMode)' trong đó FileMode là một giá trị đơn giản enum, không có hành vi nào gắn liền với nó. Tôi không thấy điều này "làm OO sai". –

+2

Woah, tôi không nói bất cứ điều gì về enums là xấu! Tôi thích liệt kê các loại an toàn - và việc bạn sử dụng đã chỉ định ở đó rất tuyệt vời. Đó là tuyên bố chuyển đổi có vấn đề. Điều đó chỉ ra rằng một hành vi nên được chuyển vào trong enum. –

+0

Xin lỗi nếu tôi hiểu lầm, nhưng câu hỏi vẫn còn: làm thế nào để bạn thực hiện 'File.Open' mà không có một tuyên bố chuyển đổi? Nói về bản thân mình, tôi sẽ không cung cấp cho các đối tượng FileMode một phương thức để mở một tệp, bởi vì nó đơn giản không thuộc về đó. Những gì bạn đề nghị như là một thay thế? –

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