Khi nào thì thích hợp để ném một ngoại lệ từ bên trong một thuộc tính getter hoặc setter? Khi nào thì không thích hợp? Tại sao? Các liên kết đến các tài liệu bên ngoài về chủ đề này sẽ hữu ích ... Google xuất hiện rất ít một cách đáng ngạc nhiên.
Khi nào thì thích hợp để ném một ngoại lệ từ bên trong một thuộc tính getter hoặc setter? Khi nào thì không thích hợp? Tại sao? Các liên kết đến các tài liệu bên ngoài về chủ đề này sẽ hữu ích ... Google xuất hiện rất ít một cách đáng ngạc nhiên.
Câu trả lời:
Microsoft có các đề xuất về cách thiết kế thuộc tính tại http://msdn.microsoft.com/en-us/library/ms229006.aspx
Về cơ bản, họ khuyến nghị rằng những người nhận tài sản là những người truy cập nhẹ và luôn an toàn khi gọi. Họ khuyên bạn nên thiết kế lại getters thành các phương pháp nếu ngoại lệ là thứ bạn cần phải ném. Đối với những người thiết lập, họ chỉ ra rằng ngoại lệ là một chiến lược xử lý lỗi phù hợp và có thể chấp nhận được.
Đối với trình lập chỉ mục, Microsoft chỉ ra rằng có thể chấp nhận được cả getters và setters để ném ngoại lệ. Và trên thực tế, nhiều trình chỉ mục trong thư viện .NET thực hiện điều này. Ngoại lệ phổ biến nhất là ArgumentOutOfRangeException
.
Có một số lý do khá tốt tại sao bạn không muốn đưa ra các ngoại lệ trong bộ nhận tài sản:
obj.PropA.AnotherProp.YetAnother
- với kiểu cú pháp này, việc quyết định vị trí đưa các câu lệnh bắt ngoại lệ trở nên khó khăn.Một lưu ý nhỏ, cần lưu ý rằng chỉ vì một thuộc tính không được thiết kế để tạo ra một ngoại lệ, điều đó không có nghĩa là nó sẽ không; nó có thể dễ dàng là mã gọi điện. Ngay cả hành động đơn giản là cấp phát một đối tượng mới (như một chuỗi) có thể dẫn đến ngoại lệ. Bạn nên luôn viết mã của mình một cách phòng thủ và mong đợi các ngoại lệ từ bất kỳ thứ gì bạn gọi ra.
Không có gì sai khi ném các ngoại lệ từ những người thiết lập. Rốt cuộc, cách nào tốt hơn để chỉ ra rằng giá trị không hợp lệ cho một thuộc tính nhất định?
Đối với getters, nó thường bị coi là khó hiểu, và điều đó có thể được giải thích khá dễ dàng: nói chung, một property getter báo cáo trạng thái hiện tại của một đối tượng; do đó, trường hợp duy nhất hợp lý để một getter ném là khi trạng thái không hợp lệ. Nhưng nó cũng thường được coi là một ý tưởng hay để thiết kế các lớp của bạn sao cho ban đầu không thể lấy một đối tượng không hợp lệ hoặc đưa nó vào trạng thái không hợp lệ thông qua các phương tiện thông thường (tức là luôn đảm bảo khởi tạo đầy đủ trong các hàm tạo, và thử làm cho các phương thức ngoại lệ an toàn đối với tính hợp lệ của trạng thái và các bất biến của lớp). Miễn là bạn tuân theo quy tắc đó, người nhận tài sản của bạn sẽ không bao giờ rơi vào tình huống họ phải báo cáo trạng thái không hợp lệ, và do đó không bao giờ ném.
Có một ngoại lệ mà tôi biết, và nó thực sự là một ngoại lệ khá quan trọng: bất kỳ đối tượng nào đang triển khai IDisposable
. Dispose
được dự định cụ thể như một cách để đưa đối tượng vào trạng thái không hợp lệ và thậm chí còn có một lớp ngoại lệ đặc biệt ObjectDisposedException
, được sử dụng trong trường hợp đó. Hoàn toàn bình thường khi ném ObjectDisposedException
từ bất kỳ thành viên nào trong lớp, bao gồm cả những người nhận thuộc tính (và loại trừ Dispose
chính nó), sau khi đối tượng đã được xử lý.
IDisposable
phải trở nên vô dụng sau khi a Dispose
. Nếu việc gọi một thành viên sẽ yêu cầu sử dụng một tài nguyên Dispose
không có sẵn (ví dụ: thành viên sẽ đọc dữ liệu từ một luồng đã bị đóng), thành viên nên ném ObjectDisposedException
thay vì rò rỉ ArgumentException
, ví dụ , nhưng nếu một thành viên có một biểu mẫu với các thuộc tính đại diện cho giá trị trong các lĩnh vực nhất định, nó sẽ có vẻ xa hữu ích hơn để cho phép tài sản đó phải được đọc sau khi xử lý (năng suất các giá trị đánh máy cuối cùng) so với yêu cầu ...
Dispose
được hoãn lại cho đến khi tất cả các thuộc tính như vậy đã được đọc. Trong một số trường hợp, một luồng có thể sử dụng chặn các lần đọc trên một đối tượng trong khi một luồng khác đóng nó và nơi dữ liệu có thể đến bất kỳ lúc nào trước đó Dispose
, có thể hữu ích nếu bạn Dispose
cắt dữ liệu đến nhưng cho phép đọc dữ liệu đã nhận trước đó. Người ta không nên tạo ra sự phân biệt giả tạo giữa Close
và Dispose
trong những tình huống mà không cần phải tồn tại.
Get...
thay vào đó, nó là một phương pháp. Một ngoại lệ ở đây là khi bạn phải triển khai một giao diện hiện có yêu cầu bạn cung cấp một thuộc tính.
Nó hầu như không bao giờ thích hợp trên một getter, và đôi khi thích hợp trên một setter.
Tài nguyên tốt nhất cho những loại câu hỏi này là "Hướng dẫn thiết kế khung" của Cwalina và Abrams; nó có sẵn dưới dạng sách đóng gáy và các phần lớn của nó cũng có sẵn trực tuyến.
Từ phần 5.2: Thiết kế tài sản
TRÁNH ném các ngoại lệ từ người nhận tài sản. Người nhận tài sản phải là những hoạt động đơn giản và không nên có điều kiện tiên quyết. Nếu một getter có thể ném một ngoại lệ, nó có lẽ nên được thiết kế lại để trở thành một phương thức. Lưu ý rằng quy tắc này không áp dụng cho các trình chỉ mục, nơi chúng tôi mong đợi các trường hợp ngoại lệ là kết quả của việc xác thực các đối số.
Lưu ý rằng hướng dẫn này chỉ áp dụng cho người nhận tài sản. Có thể đưa ra một ngoại lệ trong một bộ thiết lập thuộc tính.
ObjectDisposedException
khi đồ vật đã được Dispose()
gọi và một thứ gì đó sau đó yêu cầu giá trị thuộc tính? Có vẻ như hướng dẫn nên là "tránh ném các ngoại lệ từ bộ lấy thuộc tính, trừ khi đối tượng đã được xử lý trong trường hợp đó bạn nên xem xét ném một ObjectDisposedExcpetion".
Một cách tiếp cận hay đối với Ngoại lệ là sử dụng chúng để ghi lại mã cho chính bạn và các nhà phát triển khác như sau:
Các trường hợp ngoại lệ nên dành cho các trạng thái chương trình đặc biệt. Điều này có nghĩa là bạn có thể viết chúng ở bất cứ đâu bạn muốn!
Một lý do mà bạn có thể muốn đưa chúng vào getters là ghi lại API của một lớp - nếu phần mềm ném ra một ngoại lệ ngay khi lập trình viên cố gắng sử dụng sai thì họ sẽ không sử dụng sai! Ví dụ: nếu bạn có xác thực trong quá trình đọc dữ liệu, bạn có thể không tiếp tục và truy cập kết quả của quá trình nếu có lỗi nghiêm trọng trong dữ liệu. Trong trường hợp này, bạn có thể muốn thực hiện ném đầu ra nếu có lỗi để đảm bảo rằng một lập trình viên khác kiểm tra điều kiện này.
Chúng là một cách ghi lại các giả định và ranh giới của một hệ thống con / phương pháp / bất cứ thứ gì. Trong trường hợp chung, họ không nên bị bắt! Điều này cũng là do chúng không bao giờ được ném nếu hệ thống đang hoạt động cùng nhau theo cách mong đợi: Nếu một ngoại lệ xảy ra, nó cho thấy rằng các giả định của một đoạn mã không được đáp ứng - ví dụ: nó không tương tác với thế giới xung quanh theo cách nó được dự định ban đầu. Nếu bạn bắt gặp một ngoại lệ được viết cho mục đích này, điều đó có thể có nghĩa là hệ thống đã đi vào trạng thái không thể đoán trước / không nhất quán - điều này cuối cùng có thể dẫn đến sự cố hoặc hỏng dữ liệu hoặc tương tự có khả năng khó phát hiện / gỡ lỗi hơn nhiều.
Thông báo ngoại lệ là một cách báo cáo lỗi rất thô sơ - chúng không thể được thu thập một cách tổng thể và chỉ thực sự chứa một chuỗi. Điều này khiến chúng không phù hợp để báo cáo các vấn đề trong dữ liệu đầu vào. Trong quá trình chạy bình thường, bản thân hệ thống sẽ không rơi vào trạng thái lỗi. Do đó, các thông điệp trong chúng nên được thiết kế cho các lập trình viên chứ không phải cho người dùng - những thứ sai trong dữ liệu đầu vào có thể được phát hiện và chuyển tiếp đến người dùng ở các định dạng phù hợp hơn (tùy chỉnh).
Ngoại lệ (haha!) Đối với quy tắc này là những thứ như IO, nơi các ngoại lệ không nằm trong tầm kiểm soát của bạn và không thể kiểm tra trước.
Tất cả điều này được ghi lại trong MSDN (như được liên kết trong các câu trả lời khác) nhưng đây là quy tắc chung ...
Trong setter, nếu thuộc tính của bạn nên được xác thực ở trên và ngoài loại. Ví dụ: một thuộc tính có tên PhoneNumber có lẽ phải có xác thực regex và sẽ gây ra lỗi nếu định dạng không hợp lệ.
Đối với getters, có thể khi giá trị là null, nhưng rất có thể đó là thứ bạn sẽ muốn xử lý trên mã gọi (theo hướng dẫn thiết kế).
MSDN: Các loại ngoại lệ chuẩn bị bắt và ném
Đây là một câu hỏi rất phức tạp và câu trả lời phụ thuộc vào cách đối tượng của bạn được sử dụng. Theo quy tắc thông thường, các bộ nhận và thiết lập thuộc tính là "liên kết muộn" không nên đưa ra các ngoại lệ, trong khi các thuộc tính chỉ có "liên kết sớm" sẽ ném các ngoại lệ khi có nhu cầu. BTW, công cụ phân tích mã của Microsoft đang định nghĩa việc sử dụng các thuộc tính quá hẹp theo quan điểm của tôi.
"late binding" có nghĩa là các thuộc tính được tìm thấy thông qua phản xạ. Ví dụ: thuộc tính Serializable "được sử dụng để tuần tự hóa / deserialize một đối tượng thông qua các thuộc tính của nó. Việc ném một ngoại lệ trong loại tình huống này sẽ phá vỡ mọi thứ theo một cách thảm khốc và không phải là một cách tốt để sử dụng ngoại lệ để tạo mã mạnh hơn.
"early binding" có nghĩa là trình biên dịch sử dụng thuộc tính được ràng buộc trong mã. Ví dụ: khi một số mã mà bạn viết tham chiếu đến một bộ lấy thuộc tính. Trong trường hợp này, bạn có thể đưa ra các ngoại lệ khi chúng có ý nghĩa.
Một đối tượng có các thuộc tính bên trong có trạng thái được xác định bởi các giá trị của các thuộc tính đó. Các thuộc tính thể hiện thuộc tính nhận biết và nhạy cảm với trạng thái bên trong của đối tượng không nên được sử dụng để ràng buộc muộn. Ví dụ, giả sử bạn có một đối tượng phải được mở, truy cập, sau đó đóng. Trong trường hợp này, việc truy cập các thuộc tính mà không gọi open trước sẽ dẫn đến một ngoại lệ. Giả sử, trong trường hợp này, chúng tôi không ném một ngoại lệ và chúng tôi cho phép mã truy cập vào một giá trị mà không ném một ngoại lệ? Mã sẽ có vẻ hạnh phúc mặc dù nó có một giá trị từ một getter không có ý nghĩa. Bây giờ chúng ta đã đặt đoạn mã được gọi là getter vào một tình huống xấu vì nó phải biết cách kiểm tra giá trị để xem nó có phải là không. Điều này có nghĩa là mã phải đưa ra các giả định về giá trị mà nó nhận được từ thuộc tính getter để xác thực nó. Đây là cách mã xấu được viết.
Tôi đã có mã này mà tôi không chắc chắn về ngoại lệ nào để ném.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
Tôi đã ngăn mô hình có thuộc tính là null ngay từ đầu bằng cách buộc nó như một đối số trong hàm tạo.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}