Коли доцільно кинути виняток із домоволодільця або сеттера? Коли це не підходить? Чому? Посилання на зовнішні документи з цього питання допоможуть ... Google виявився напрочуд мало.
Коли доцільно кинути виняток із домоволодільця або сеттера? Коли це не підходить? Чому? Посилання на зовнішні документи з цього питання допоможуть ... Google виявився напрочуд мало.
Відповіді:
Microsoft має свої рекомендації щодо створення властивостей на веб- сторінці http://msdn.microsoft.com/en-us/library/ms229006.aspx
По суті, вони рекомендують, щоб власники нерухомості були легкими аксесуарами, які завжди безпечно телефонувати. Вони рекомендують переробляти геттери як методи, якщо винятки - це щось, що потрібно кинути. Для розробників вони вказують, що винятки є відповідною і прийнятною стратегією обробки помилок.
Що стосується індексаторів, Microsoft вказує на те, що допустимо викиди винятків як для геттерів, так і для сетерів. І насправді багато індексаторів у бібліотеці .NET роблять це. Найбільш поширеним винятком є ArgumentOutOfRangeException
.
Є кілька досить вагомих причин, чому ви не хочете кидати винятки у власників нерухомості:
obj.PropA.AnotherProp.YetAnother
- з таким видом синтаксису стає проблематичним вирішити, куди слід вводити заяви вилучення винятків.Як бічна примітка, слід пам’ятати, що те, що властивість не призначена для викидання виключень, це не означає, що це не буде; це може легко викликати код, який це робить. Навіть простий акт виділення нового об’єкта (як рядок) може призвести до винятків. Ви завжди повинні писати свій код оборонно і очікувати винятків із усього, до чого ви посилаєтесь.
Немає нічого поганого в тому, щоб кидати винятки із сетерів. Зрештою, який кращий спосіб вказати, що значення не є дійсним для даної властивості?
Для відвідувачів це, як правило, нахмуриться, і це можна пояснити досить легко: власник, що отримує властивість, загалом повідомляє про поточний стан об'єкта; таким чином, єдиний випадок, коли геттер кидає розум, коли держава недійсна. Але загалом вважається гарною ідеєю розробити свої класи таким чином, що спочатку просто неможливо отримати недійсний об'єкт або перевести його в недійсний стан звичайними засобами (тобто завжди забезпечувати повну ініціалізацію в конструкторах, і спробуйте зробити методи безпечними для винятку стосовно дійсності штату та інваріантів класів). Поки ви дотримуєтесь цього правила, ваші власники майна ніколи не повинні потрапляти в ситуацію, коли вони повинні повідомляти про недійсний стан, і, таким чином, ніколи не кидати.
Я знаю один виняток, і він є насправді досить важливим: будь-який об'єкт, що реалізує IDisposable
. Dispose
спеціально призначений як спосіб привести об'єкт у недійсний стан, і навіть існує спеціальний клас виключень ObjectDisposedException
, який буде використаний у такому випадку. Цілком нормально викидати ObjectDisposedException
з будь-якого члена класу, включаючи власників нерухомості (і виключаючи Dispose
себе), після того, як об’єкт був утилізований.
IDisposable
повинні бути марними після а Dispose
. Якщо виклик члена вимагатиме використання ресурсу, який Dispose
зробив недоступним (наприклад, учасник читав би дані з закритого потоку), член повинен викидати, ObjectDisposedException
а не витікати, наприклад ArgumentException
, але якщо у нього є форма з властивостями, які представляють Значення в певних полях, здається, набагато корисніше дозволити зчитувати такі властивості після видалення (даючи останні введені значення), ніж вимагати ...
Dispose
будуть відкладені, поки не будуть прочитані всі подібні властивості. У деяких випадках, коли один потік може використовувати блокування читання на об’єкті, а інший закриває його, і коли дані можуть надходити в будь-який час до Dispose
цього, може бути корисно Dispose
відрізати вхідні дані, але дозволяти читати раніше отримані дані. Не слід форсувати штучне відмінність між Close
і Dispose
в ситуаціях , коли ніхто інакше не повинні були б існувати.
Get...
метод. Виняток тут - коли вам доведеться реалізувати існуючий інтерфейс, який вимагає надання властивості.
Це майже ніколи не підходить для геттера, а іноді доречно на сеттері.
Найкращий ресурс для подібних питань - "Настанови рамкового дизайну" Cwalina and Abrams; вона доступна як переплетена книга, а великі її частини також доступні в Інтернеті.
З розділу 5.2: Дизайн власності
УНИКНУЙТЕ викидання винятків у власників майна Отримати майно мають бути простими операціями та не повинні мати передумов. Якщо геттер може кинути виняток, він, ймовірно, повинен бути перероблений як метод. Зауважте, що це правило не поширюється на індексатори, де ми очікуємо винятки в результаті перевірки аргументів.
Зауважте, що ця інструкція стосується лише власників нерухомості. Добре кинути виняток у програмі налаштування властивості.
ObjectDisposedException
як тільки об’єкт Dispose()
викликав, і щось згодом запитує про значення властивості? Здається, що вказівок повинен бути "уникати викидів винятків з одержувачів властивостей, якщо тільки об'єкт не розміщений. У цьому випадку слід розглянути можливість кидання ObjectDisposedExcpetion".
Один із приємних підходів до Винятку - використовувати їх для документування коду для себе та інших розробників:
Винятки мають бути для виняткових держав програми. Це означає, що добре писати їх куди завгодно!
Однією з причин, можливо, ви захочете поставити їх в getters - це документувати API класу - якщо програмне забезпечення видає виняток, як тільки програміст намагається використовувати його неправильно, тоді вони не будуть використовувати його неправильно! Наприклад, якщо у вас є перевірка під час процесу зчитування даних, можливо, не має сенсу мати можливість продовжувати та отримувати доступ до результатів процесу, якщо в даних були фатальні помилки. У цьому випадку ви, можливо, захочете отримати викид виводу, якщо були помилки, щоб переконатися, що інший програміст перевіряє цю умову.
Вони є способом документування припущень та меж підсистеми / методу / будь-якого іншого. У загальному випадку їх не слід ловити! Це також тому, що вони ніколи не кидаються, якщо система працює разом таким чином, як очікувалося: якщо трапиться виняток, це показує, що припущення про фрагмент коду не виконуються - наприклад, він не взаємодіє зі світом навколо нього способом спочатку це було призначено. Якщо ви потрапили на виняток, який був написаний для цієї мети, це, ймовірно, означає, що система перейшла в непередбачуваний / непослідовний стан - це в кінцевому підсумку може призвести до збоїв або пошкодження даних або подібного, що, ймовірно, буде набагато складніше виявити / налагодити.
Повідомлення про винятки є дуже грубим способом повідомляти про помилки - їх не можна збирати масово і лише дійсно містять рядок. Це робить їх непридатними для повідомлення про проблеми у вхідних даних. У звичайному режимі система сама не повинна вводити стан помилок. В результаті цього повідомлення в них повинні бути розроблені для програмістів, а не для користувачів - речі, неправильні у вхідних даних, можуть бути виявлені та передані користувачам у більш підходящих (користувацьких) форматах.
Виняток (ха-ха!) З цього правила - це такі речі, як IO, де винятки не знаходяться під вашим контролем і їх не можна перевірити заздалегідь.
Це все зафіксовано в MSDN (як це пов'язано з іншими відповідями), але ось загальне правило:
У програмі налаштування, якщо ваше майно має бути підтверджено вище та за типом. Наприклад, властивість під назвою PhoneNumber, ймовірно, повинна мати перевірку регулярних виразів і повинна видавати помилку, якщо формат недійсний.
Для Getters, можливо, коли значення є нульовим, але, швидше за все, це те, що ви хочете обробити в коді виклику (відповідно до інструкцій щодо проектування).
MSDN: Ловлення та метання стандартних типів виключень
Це дуже складне питання, і відповідь залежить від того, як використовується ваш об’єкт. Як правило, власники та налаштування властивостей, які є "пізньою прив'язкою", не повинні викидати винятки, тоді як властивості з виключно "ранньою прив'язкою" повинні кидати винятки, коли виникає потреба. До речі, інструмент аналізу коду Microsoft визначає використання властивостей занадто вузько.
"пізнє зв'язування" означає, що властивості знаходять через відображення. Наприклад, атрибут Serializeable "використовується для серіалізації / деріаріалізації об'єкта через його властивості. Закидання винятку під час подібної ситуації розбиває речі катастрофічно і не є хорошим способом використання винятків для створення більш надійного коду.
"раннє прив'язування" означає, що використання властивості компілятором пов'язане використання властивості. Наприклад, коли якийсь код, на який ви пишете, посилається на отримання ресурсу. У цьому випадку нормально викидати винятки, коли вони мають сенс.
Об'єкт із внутрішніми атрибутами має стан, визначений значеннями цих атрибутів. Властивості, що виражають атрибути, які відомі та чутливі до внутрішнього стану об'єкта, не повинні використовуватися для пізнього прив'язки. Наприклад, скажімо, у вас є об'єкт, який потрібно відкрити, отримати доступ до нього, а потім закрити. У цьому випадку доступ до властивостей без виклику відкривати спочатку повинен спричинити виняток. Припустимо, у цьому випадку ми не викидаємо виняток і дозволяємо коду отримати доступ до значення, не кидаючи виняток? Код здасться щасливим, навіть якщо він отримав значення від геттера, який не має сенсу. Тепер ми поставили код, який викликав геттера в погану ситуацію, оскільки він повинен знати, як перевірити значення, щоб побачити, чи не має сенсу. Це означає, що код повинен робити припущення про значення, яке воно отримало від одержувача властивостей, щоб перевірити його. Ось як пишеться поганий код.
У мене був цей код, де я не знав, який виняток кинути.
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);
}
Я не допустив, щоб модель у першу чергу була недійсною, примушуючи її як аргумент у конструкторі.
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);
}