Кращі практики: викидання виключень із властивостей


111

Коли доцільно кинути виняток із домоволодільця або сеттера? Коли це не підходить? Чому? Посилання на зовнішні документи з цього питання допоможуть ... Google виявився напрочуд мало.


Дивіться також: stackoverflow.com/questions/633944/…
Jon B


1
Я прочитав обидва ці питання, але не відповів на це запитання повністю IMO.
Джон Сейгель

Коли це необхідно. Як попередні запитання, так і відповіді показують, що дозволяти та цінувати винятки з геттера чи сетера, тому ви можете просто "бути розумним".
Lex Li

Відповіді:


135

Microsoft має свої рекомендації щодо створення властивостей на веб- сторінці http://msdn.microsoft.com/en-us/library/ms229006.aspx

По суті, вони рекомендують, щоб власники нерухомості були легкими аксесуарами, які завжди безпечно телефонувати. Вони рекомендують переробляти геттери як методи, якщо винятки - це щось, що потрібно кинути. Для розробників вони вказують, що винятки є відповідною і прийнятною стратегією обробки помилок.

Що стосується індексаторів, Microsoft вказує на те, що допустимо викиди винятків як для геттерів, так і для сетерів. І насправді багато індексаторів у бібліотеці .NET роблять це. Найбільш поширеним винятком є ArgumentOutOfRangeException.

Є кілька досить вагомих причин, чому ви не хочете кидати винятки у власників нерухомості:

  • Оскільки властивості "видаються" полями, не завжди очевидно, що вони можуть кинути виняток (за задумом); тоді як за допомогою методів програмісти навчаються очікувати та досліджувати, чи є винятки очікуваним наслідком виклику методу.
  • Геттерам використовується багато інфраструктури .NET, як серіалізатори та прив'язка даних (наприклад, у WinForms та WPF) - робота з винятками в таких контекстах може швидко стати проблематичною.
  • Отримані властивості автоматично оцінюються налагоджувачами під час перегляду або огляду об’єкта. Виняток тут може заплутати і уповільнити ваші зусилля налагодження. З тих же причин також небажано виконувати інші дорогі операції у властивостях (наприклад, доступ до бази даних).
  • Властивості часто використовуються в ланцюговій умові: obj.PropA.AnotherProp.YetAnother- з таким видом синтаксису стає проблематичним вирішити, куди слід вводити заяви вилучення винятків.

Як бічна примітка, слід пам’ятати, що те, що властивість не призначена для викидання виключень, це не означає, що це не буде; це може легко викликати код, який це робить. Навіть простий акт виділення нового об’єкта (як рядок) може призвести до винятків. Ви завжди повинні писати свій код оборонно і очікувати винятків із усього, до чого ви посилаєтесь.


41
Якщо ви стикаєтесь з фатальним винятком, як-от "поза пам'яттю", навряд чи має значення, чи ви отримаєте виняток у властивості чи десь в іншому місці. Якщо ви не отримали його у власності, ви отримаєте його пару наносекунд пізніше від наступного, що виділяє пам'ять. Питання не в тому, "чи може власність кинути виняток?" Майже весь код може кинути виняток через смертельний стан. Питання полягає в тому, чи має майно за задумом виключати виключення як частину свого конкретного договору.
Ерік Ліпперт

1
Я не впевнений, що розумію аргумент у цій відповіді. Для xample, що стосується прив'язки даних - і WinForms, і WPF спеціально написані, щоб правильно обробляти винятки, викинуті за властивостями, і трактувати їх як збої перевірки - що є прекрасним (деякі навіть вважають це найкращим) способом надання перевірки доменної моделі .
Павло Мінаєв

6
@Pavel - хоча і WinForms, і WPF можуть вишукано відновлюватися після винятку в доступі до ресурсів, не завжди легко виявити та відновити такі помилки. У деяких випадках (наприклад, коли у WPF програма встановлення шаблону керування викидає виняток) виняток проковтується беззвучно. Це може призвести до болісних сесій налагодження, якщо ви ніколи раніше не стикалися з подібними випадками.
Л.Бушкін

1
@Steven: Тоді скільки ви користувались цим класом у винятковому випадку? Якщо вам тоді довелося написати захисний код, щоб обробити всі ці винятки через один збій, і, мабуть, поставити відповідні типові параметри, чому б не вказати ці типові параметри у своєму улові? Крім того, якщо винятки з властивостей передаються користувачеві, чому б просто не викинути оригінал "InvalidArgumentException" або подібне, щоб вони могли поставити файл відсутніх налаштувань?
Джаф - Бен Дюгід

6
Є причина, чому це правила, а не правила; жодна інструкція не охоплює всі випадки божевільного краю. Я, мабуть, зробив би ці методи, а не властивості, але це заклик судження.
Ерік Ліпперт

34

Немає нічого поганого в тому, щоб кидати винятки із сетерів. Зрештою, який кращий спосіб вказати, що значення не є дійсним для даної властивості?

Для відвідувачів це, як правило, нахмуриться, і це можна пояснити досить легко: власник, що отримує властивість, загалом повідомляє про поточний стан об'єкта; таким чином, єдиний випадок, коли геттер кидає розум, коли держава недійсна. Але загалом вважається гарною ідеєю розробити свої класи таким чином, що спочатку просто неможливо отримати недійсний об'єкт або перевести його в недійсний стан звичайними засобами (тобто завжди забезпечувати повну ініціалізацію в конструкторах, і спробуйте зробити методи безпечними для винятку стосовно дійсності штату та інваріантів класів). Поки ви дотримуєтесь цього правила, ваші власники майна ніколи не повинні потрапляти в ситуацію, коли вони повинні повідомляти про недійсний стан, і, таким чином, ніколи не кидати.

Я знаю один виняток, і він є насправді досить важливим: будь-який об'єкт, що реалізує IDisposable. Disposeспеціально призначений як спосіб привести об'єкт у недійсний стан, і навіть існує спеціальний клас виключень ObjectDisposedException, який буде використаний у такому випадку. Цілком нормально викидати ObjectDisposedExceptionз будь-якого члена класу, включаючи власників нерухомості (і виключаючи Disposeсебе), після того, як об’єкт був утилізований.


4
Спасибі Павло. Ця відповідь стосується "чому", а не просто заявляти ще раз, що викидати виняток із властивостей не годиться.
РішенняYogi

1
Мені не подобається думка про те, що абсолютно всі члени команди IDisposableповинні бути марними після а Dispose. Якщо виклик члена вимагатиме використання ресурсу, який Disposeзробив недоступним (наприклад, учасник читав би дані з закритого потоку), член повинен викидати, ObjectDisposedExceptionа не витікати, наприклад ArgumentException, але якщо у нього є форма з властивостями, які представляють Значення в певних полях, здається, набагато корисніше дозволити зчитувати такі властивості після видалення (даючи останні введені значення), ніж вимагати ...
supercat

1
... які Disposeбудуть відкладені, поки не будуть прочитані всі подібні властивості. У деяких випадках, коли один потік може використовувати блокування читання на об’єкті, а інший закриває його, і коли дані можуть надходити в будь-який час до Disposeцього, може бути корисно Disposeвідрізати вхідні дані, але дозволяти читати раніше отримані дані. Не слід форсувати штучне відмінність між Closeі Disposeв ситуаціях , коли ніхто інакше не повинні були б існувати.
supercat

Розуміння причини правила дозволяє знати, коли потрібно порушити правило (Реймонд Чен). У цьому випадку ми можемо побачити, що якщо є непоправна помилка будь-якого типу, ви не повинні ховати її в геттері, оскільки в таких випадках заявці потрібно зайти якнайшвидше.
Бен

Я намагався зробити те, що користувачі, які отримують власність, як правило, не повинні містити логіки, яка б допускала непоправні помилки. Якщо це так, можливо, це краще, ніж Get...метод. Виняток тут - коли вам доведеться реалізувати існуючий інтерфейс, який вимагає надання властивості.
Павло Мінаєв

24

Це майже ніколи не підходить для геттера, а іноді доречно на сеттері.

Найкращий ресурс для подібних питань - "Настанови рамкового дизайну" Cwalina and Abrams; вона доступна як переплетена книга, а великі її частини також доступні в Інтернеті.

З розділу 5.2: Дизайн власності

УНИКНУЙТЕ викидання винятків у власників майна Отримати майно мають бути простими операціями та не повинні мати передумов. Якщо геттер може кинути виняток, він, ймовірно, повинен бути перероблений як метод. Зауважте, що це правило не поширюється на індексатори, де ми очікуємо винятки в результаті перевірки аргументів.

Зауважте, що ця інструкція стосується лише власників нерухомості. Добре кинути виняток у програмі налаштування властивості.


2
Хоча (загалом) я згоден з такими вказівками, він вважає корисним дати деяке додаткове розуміння того, чому їх слід дотримуватись - і які наслідки можуть виникнути при їх ігноруванні.
Л.Бушкін

3
Як це стосується одноразових об’єктів та вказівок, які слід розглянути киданням, ObjectDisposedExceptionяк тільки об’єкт Dispose()викликав, і щось згодом запитує про значення властивості? Здається, що вказівок повинен бути "уникати викидів винятків з одержувачів властивостей, якщо тільки об'єкт не розміщений. У цьому випадку слід розглянути можливість кидання ObjectDisposedExcpetion".
Скотт Дорман

4
Дизайн - це мистецтво та наука пошуку розумних компромісів в умовах конфліктних вимог. Будь-який спосіб здається розумним компромісом; Я не був би здивований, коли утилізований предмет кинеться на майно; і я б не здивувався, якби цього не сталося. Оскільки використання розпорядженого об’єкта є жахливою практикою програмування, було б нерозумно очікувати.
Ерік Ліпперт

1
Інший сценарій, коли цілком допустимо викидати винятки зсередини getters - це коли об'єкт використовує інваріанти класів для перевірки його внутрішнього стану, який потрібно перевіряти, коли публічний доступ зроблено, незалежно від методу чи властивості
Trap

2

Один із приємних підходів до Винятку - використовувати їх для документування коду для себе та інших розробників:

Винятки мають бути для виняткових держав програми. Це означає, що добре писати їх куди завгодно!

Однією з причин, можливо, ви захочете поставити їх в getters - це документувати API класу - якщо програмне забезпечення видає виняток, як тільки програміст намагається використовувати його неправильно, тоді вони не будуть використовувати його неправильно! Наприклад, якщо у вас є перевірка під час процесу зчитування даних, можливо, не має сенсу мати можливість продовжувати та отримувати доступ до результатів процесу, якщо в даних були фатальні помилки. У цьому випадку ви, можливо, захочете отримати викид виводу, якщо були помилки, щоб переконатися, що інший програміст перевіряє цю умову.

Вони є способом документування припущень та меж підсистеми / методу / будь-якого іншого. У загальному випадку їх не слід ловити! Це також тому, що вони ніколи не кидаються, якщо система працює разом таким чином, як очікувалося: якщо трапиться виняток, це показує, що припущення про фрагмент коду не виконуються - наприклад, він не взаємодіє зі світом навколо нього способом спочатку це було призначено. Якщо ви потрапили на виняток, який був написаний для цієї мети, це, ймовірно, означає, що система перейшла в непередбачуваний / непослідовний стан - це в кінцевому підсумку може призвести до збоїв або пошкодження даних або подібного, що, ймовірно, буде набагато складніше виявити / налагодити.

Повідомлення про винятки є дуже грубим способом повідомляти про помилки - їх не можна збирати масово і лише дійсно містять рядок. Це робить їх непридатними для повідомлення про проблеми у вхідних даних. У звичайному режимі система сама не повинна вводити стан помилок. В результаті цього повідомлення в них повинні бути розроблені для програмістів, а не для користувачів - речі, неправильні у вхідних даних, можуть бути виявлені та передані користувачам у більш підходящих (користувацьких) форматах.

Виняток (ха-ха!) З цього правила - це такі речі, як IO, де винятки не знаходяться під вашим контролем і їх не можна перевірити заздалегідь.


2
Як ця правдива та відповідна відповідь була спростована? У StackOverflow не повинно бути ніякої політики, і якщо ця відповідь, здавалося, пропустила бичаче око, додайте коментар до цього ефекту. Оскарження відповідей - це відповіді, нерелевантні чи неправильні.
дебат

1

Це все зафіксовано в MSDN (як це пов'язано з іншими відповідями), але ось загальне правило:

У програмі налаштування, якщо ваше майно має бути підтверджено вище та за типом. Наприклад, властивість під назвою PhoneNumber, ймовірно, повинна мати перевірку регулярних виразів і повинна видавати помилку, якщо формат недійсний.

Для Getters, можливо, коли значення є нульовим, але, швидше за все, це те, що ви хочете обробити в коді виклику (відповідно до інструкцій щодо проектування).



0

Це дуже складне питання, і відповідь залежить від того, як використовується ваш об’єкт. Як правило, власники та налаштування властивостей, які є "пізньою прив'язкою", не повинні викидати винятки, тоді як властивості з виключно "ранньою прив'язкою" повинні кидати винятки, коли виникає потреба. До речі, інструмент аналізу коду Microsoft визначає використання властивостей занадто вузько.

"пізнє зв'язування" означає, що властивості знаходять через відображення. Наприклад, атрибут Serializeable "використовується для серіалізації / деріаріалізації об'єкта через його властивості. Закидання винятку під час подібної ситуації розбиває речі катастрофічно і не є хорошим способом використання винятків для створення більш надійного коду.

"раннє прив'язування" означає, що використання властивості компілятором пов'язане використання властивості. Наприклад, коли якийсь код, на який ви пишете, посилається на отримання ресурсу. У цьому випадку нормально викидати винятки, коли вони мають сенс.

Об'єкт із внутрішніми атрибутами має стан, визначений значеннями цих атрибутів. Властивості, що виражають атрибути, які відомі та чутливі до внутрішнього стану об'єкта, не повинні використовуватися для пізнього прив'язки. Наприклад, скажімо, у вас є об'єкт, який потрібно відкрити, отримати доступ до нього, а потім закрити. У цьому випадку доступ до властивостей без виклику відкривати спочатку повинен спричинити виняток. Припустимо, у цьому випадку ми не викидаємо виняток і дозволяємо коду отримати доступ до значення, не кидаючи виняток? Код здасться щасливим, навіть якщо він отримав значення від геттера, який не має сенсу. Тепер ми поставили код, який викликав геттера в погану ситуацію, оскільки він повинен знати, як перевірити значення, щоб побачити, чи не має сенсу. Це означає, що код повинен робити припущення про значення, яке воно отримало від одержувача властивостей, щоб перевірити його. Ось як пишеться поганий код.


0

У мене був цей код, де я не знав, який виняток кинути.

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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.