Налагодження проти викидів


93

Я прочитав безліч статей (і ще пару подібних запитань, які були опубліковані на StackOverflow) про те, як і коли використовувати твердження, і я їх добре розумів. Але все-таки я не розумію, яка мотивація повинна спонукати мене використовувати Debug.Assertзамість простого виключення. Я маю на увазі те, що у .NET за замовчуванням відповідь на невдале твердження - "зупинити світ" та показати вікно повідомлення користувачеві. Хоча такий спосіб поведінки можна модифікувати, я вважаю це дуже прикрим і зайвим робити це, хоча я міг би натомість просто підкинути відповідний виняток. Таким чином, я міг легко записати помилку в журнал програми безпосередньо перед тим, як видалити виняток, і, крім того, моя програма не обов'язково зависає.

То чому я, якщо взагалі, маю використовувати Debug.Assertзамість простого винятку? Розміщення твердження там, де воно не повинно бути, може просто спричинити всі види "небажаної поведінки", тому, на мій погляд, я справді нічого не отримую, використовуючи твердження, замість того, щоб кидати виняток. Ви згодні зі мною, чи мені тут чогось не вистачає?

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


Редагувати: Я бачу це, якщо затвердження не вдалося, це означає, що програма перейшла в якийсь пошкоджений, несподіваний стан. То чому я хотів би продовжувати виконання? Не має значення, працює програма на налагоджувальній чи випускній версії. Те саме стосується обох


1
Що стосується речей, які ви кажете: "якщо помилка виявлена ​​у виробничому випуску, я все одно хотів би, щоб" твердження "провалилося", винятки - це те, що ви повинні використовувати
Том Нейленд,

1
Ефективність - це єдина причина. Нульова перевірка всього, що весь час може зменшити швидкість, хоча це може бути зовсім непомітно. Це в основному для випадків, які ніколи не повинні статися, наприклад, ви знаєте, що ви вже перевірили нуль у попередній функції, немає сенсу витрачати цикли, перевіряючи це знову. Debug.assert ефективно діє як останній шансовий модульний тест, щоб повідомити вас.
котиться

Відповіді:


176

Хоча я згоден з тим, що ваші міркування є правдоподібними - тобто, якщо твердження порушено несподівано, має сенс зупинити страту, кинувши - я особисто не використовував би винятків замість тверджень. Ось чому:

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

  • Завжди повинно бути можливим скласти тестовий кейс, який виконує заданий викид. Якщо неможливо створити такий тестовий приклад, тоді у вашій програмі є шлях до коду, який ніколи не виконується, і його слід видалити як мертвий код.

  • НІКОЛИ не повинно бути можливо створити тест-кейс, який спричиняє спрацьовування твердження. Якщо твердження спрацьовує, або код помилковий, або твердження помилкове; в будь-якому випадку, щось потрібно змінити в коді.

Ось чому я б не замінив твердження винятком. Якщо твердження насправді не може запуститись, то заміна його на виняток означає, що у вашій програмі є неперевірний шлях коду . Мені не подобаються неперевірені шляхи коду.


16
Проблема із твердженнями полягає в тому, що їх немає у виробничій збірці. Недотримання передбачуваної умови означає, що ваша програма увійшла до невизначеної ділянки поведінки, і в цьому випадку відповідальна програма повинна якнайшвидше зупинити виконання (розкручування стека також є дещо небезпечним, залежно від того, наскільки жорстким ви хочете отримати). Так, твердження, як правило, неможливо звільнити, але ви не знаєте, що можливо, коли щось піде в дику природу. Те, що ви вважали неможливим, могло статися на виробництві, і відповідальна програма повинна виявляти порушені припущення та діяти негайно.
kizzx2

2
@ kizzx2: Добре, так скільки неможливих винятків на рядок виробничого коду ви пишете?
Ерік Ліпперт,

4
@ kixxx2: Це C #, тому ви можете зберігати твердження у виробничому коді за допомогою Trace.Assert. Ви навіть можете використовувати файл app.config для перенаправлення виробничих тверджень на текстовий файл, а не грубий для кінцевого користувача.
HTTP 410,

3
@AnorZaken: Ваша думка ілюструє недолік дизайну у винятках. Як я вже зазначав в інших місцях, винятками є (1) смертельні катастрофи, (2) помилки з головою, які ніколи не повинні траплятися, (3) невдачі у проектуванні, коли виняток використовується для сигналізації про невиключний стан, або (4) несподівані екзогенні умови . Чому ці чотири абсолютно різні речі представлені винятками? Якби я був моїм druthers, boneheaded «нульовий був разименовиваются» виняток, не буде catchable взагалі . Це ніколи не правильно, і воно повинно припинити вашу програму, перш ніж вона заподіє більше шкоди . Вони повинні бути більше схожими на твердження.
Ерік Ліпперт,

2
@Backwards_Dave: помилкові твердження є поганими, а не істинними. Твердження дозволяють запускати дорогі перевірки перевірки, які ви не хотіли б запускати у виробництві. І якщо твердження порушується у виробництві, що з цим повинен робити кінцевий користувач?
Ерік Ліпперт,

17

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

Стверджує тест на умови, які "не можуть відбутися". Виняток становлять умови, які "не повинні відбуватися, але мають відбуватися".

Твердження корисні тим, що під час побудови (або навіть часу виконання) ви можете змінити їх поведінку. Наприклад, часто у збірках випусків твердження навіть не перевіряються, оскільки вони створюють непотрібні накладні витрати. З цим також слід бути обережним: ваші тести можуть навіть не виконуватися.

Якщо ви використовуєте винятки замість тверджень, ви втрачаєте деяке значення:

  1. Код є більш багатослівним, оскільки тестування та створення винятку складає принаймні два рядки, тоді як твердження - лише один.

  2. Ваш код тестування і скидання завжди працюватиме, тоді як твердження можна скомпілювати.

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

Більше тут: http://nedbatchelder.com/text/assert.html


Якщо цього "не може статися", то навіщо писати твердження. Хіба це не зайве? Якщо це насправді може статися, але не повинно, то хіба це не те саме, що "не повинно статися, але робити", що є виключенням?
Девід Клемпфнер

2
"Не може статися" в лапках з причини: це може статися лише в тому випадку, якщо програміст зробив щось не так в іншій частині програми. Твердження - це перевірка на помилки програміста.
Нед Батчелдер,

@NedBatchelder Термін програміст трохи неоднозначний, коли ви розробляєте бібліотеку. Чи правильно, що ті "неможливі ситуації" повинні бути неможливими для користувача бібліотеки, але можуть бути можливими, коли автор бібліотеки допустив помилку?
Бруно Зелл

12

РЕДАГУВАТИ: У відповідь на редагування / нотатку, яку ви зробили у своєму дописі: Схоже, використання винятків - це правильна річ, яку слід використовувати, а не використовувати твердження щодо того, що ви намагаєтесь виконати. Я думаю, що розумовий камінь спотикання, який ви вражаєте, полягає в тому, що ви розглядаєте винятки та твердження, щоб виконати ту саму мету, і тому ви намагаєтеся з’ясувати, який із них буде “правильним” використовувати. Незважаючи на те, що може існувати деяке накладання на те, як можна використовувати твердження та винятки, не плутайте, що для них вони є різними рішеннями однієї і тієї ж проблеми - вони не є. Кожне твердження та винятки мають своє призначення, сильні та слабкі сторони.

Я збирався набрати відповідь своїми словами, але це робить концепцію кращою, ніж я мав би:

C # Station: Твердження

Використання тверджень assert може бути ефективним способом виявлення помилок логіки програми під час виконання, і все ж вони легко відфільтровуються із виробничого коду. Після завершення розробки вартість виконання цих надлишкових тестів на помилки кодування може бути усунена простим визначенням символу препроцесора NDEBUG [який відключає всі твердження] під час компіляції. Проте пам’ятайте, пам’ятайте, що код, розміщений у самому затвердженні, буде опущений у виробничій версії.

Твердження найкраще використовувати для перевірки стану лише тоді, коли виконуються всі наведені нижче умови.

* the condition should never be false if the code is correct,
* the condition is not so trivial so as to obviously be always true, and
* the condition is in some sense internal to a body of software.

Твердження майже ніколи не слід використовувати для виявлення ситуацій, що виникають під час нормальної роботи програмного забезпечення. Наприклад, зазвичай твердження не слід використовувати для перевірки помилок у введенні користувачем даних. Однак може мати сенс використовувати твердження для підтвердження того, що абонент вже перевірив введення користувача.

В основному, використовуйте винятки для речей, які потрібно вловити / вирішити у виробничому додатку, використовуйте твердження для виконання логічних перевірок, які будуть корисні для розробки, але вимкнені у виробництві.


Я все це усвідомлюю. Але справа в тому, що те саме твердження, яке ви позначили як напівжирне, стосується і винятків. Тому, як я бачу це, замість твердження, я міг би просто викинути виняток (оскільки, якщо «ситуація, яка ніколи не повинна відбутися» трапляється у розгорнутій версії, я все одно хочу про це повідомити [плюс додаток може увійти в пошкоджений стан, тому я підходжу до винятку, можливо, я не хочу продовжувати нормальний потік виконання)

1
Твердження слід використовувати щодо інваріантів; слід використовувати винятки, коли, скажімо, щось не має бути нульовим, але воно буде (як параметр методу).
Ed S.

Я думаю, все зводиться до того, наскільки захисно ви хочете кодувати.
Нед Батчелдер,

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

7

Я думаю, що (надуманий) практичний приклад може допомогти висвітлити різницю:

(адаптоване з розширення Batch для MoreLinq )

// 'public facing' method
public int DoSomething(List<string> stuff, object doohickey, int limit) {

    // validate user input and report problems externally with exceptions

    if(stuff == null) throw new ArgumentNullException("stuff");
    if(doohickey == null) throw new ArgumentNullException("doohickey");
    if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0");

    return DoSomethingImpl(stuff, doohickey, limit);
}

// 'developer only' method
private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) {

    // validate input that should only come from other programming methods
    // which we have control over (e.g. we already validated user input in
    // the calling method above), so anything using this method shouldn't
    // need to report problems externally, and compilation mode can remove
    // this "unnecessary" check from production

    Debug.Assert(stuff != null);
    Debug.Assert(doohickey != null);
    Debug.Assert(limit > 0);

    /* now do the actual work... */
}

Отже, як сказали Ерік Ліпперт та інші, ви стверджуєте лише ті речі, які, на вашу думку, будуть правильними, на той випадок, якщо ви (розробник) випадково використали їх десь ще, тож ви можете виправити свій код. Ви в основному кидаєте винятки, коли ви не можете контролювати або не можете передбачити, що надходить, наприклад, для введення користувачем , так що все, що дало йому погані дані, могло відповісти належним чином (наприклад, користувач).


Чи не є Ваші 3 твердження повністю зайвими? Для їх параметрів неможливо оцінити як хибні.
Девід Клемпфнер,

1
У цьому суть - твердження існують для документування того, що неможливо. Чому ви це робите? Оскільки у вас може бути щось на зразок ReSharper, який попереджає вас усередині методу DoSomethingImpl про те, що "ви можете дереференціювати нуль тут", і ви хочете сказати йому "Я знаю, що я роблю, це ніколи не може бути нулем". Це також вказівка ​​для деяких пізніших програмістів, які можуть не відразу усвідомити зв’язок між DoSomething і DoSomethingImpl, особливо якщо вони розташовані на сотні рядків.
Марсель Попеску,

4

Ще один самородок від Code Complete :

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

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

Далі він додає деякі вказівки щодо того, що слід, а що не слід стверджувати.

З іншого боку, винятки:

"Використовуйте обробку винятків, щоб привернути увагу до несподіваних випадків. Виняткові випадки повинні оброблятися таким чином, щоб зробити їх очевидними під час розробки та відновити, коли працює виробничий код."

Якщо у вас немає цієї книги, вам слід її придбати.


2
Я прочитав книгу, вона чудова. Однак .. ви не відповіли на моє запитання :)

Ви маєте рацію, я не відповів. Мої відповіді - ні, я з вами не згоден. Твердження та винятки - це різні тварини, як описано вище, та деякі інші розміщені тут відповіді.
Ендрю Ковенговен,

0

Debug.Assert за замовчуванням працюватиме лише в налагоджувальних збірках, тому, якщо ви хочете виявити будь-яку погану несподівану поведінку у збірках випусків, вам доведеться використовувати винятки або вмикати константу налагодження у властивостях вашого проекту (що враховується в загалом, щоб не бути гарною ідеєю).


перше часткове речення відповідає дійсності, решта - загалом погана ідея: твердження - це припущення і відсутність перевірки (як зазначено вище), увімкнення налагодження у випуску насправді не є можливим.
Marc Wittke

0

Використовуйте твердження щодо речей, які МОЖЛИВІ, але не повинні відбуватися (якщо це було неможливо, чому б ви давали твердження?).

Хіба це не схоже на випадок використання Exception? Чому б ви використовували твердження замість Exception?

Тому що перед вашим твердженням повинен бути код, який викликається, що зупинить параметр твердження хибним.

Зазвичай перед вашим кодом не існує коду, Exceptionякий гарантує, що його не кинуть.

Чому це добре, що Debug.Assert()компілюється у прод? Якщо ви хочете знати про це під час налагодження, чи не хочете ви про це знати у prod?

Ви хочете цього лише під час розробки, тому що, знайшовши Debug.Assert(false)ситуації, ви пишете код, щоб гарантувати, що Debug.Assert(false)це не повториться. Після завершення розробки, припускаючи, що ви знайшли Debug.Assert(false)ситуації та виправили їх, їх Debug.Assert()можна сміливо скомпілювати, оскільки вони тепер зайві.


0

Припустимо, ви є членом досить великої команди, і кілька людей працюють над однією загальною базою коду, включаючи накладання класів. Ви можете створити метод, який викликається кількома іншими методами, і, щоб уникнути суперечки щодо блокування, ви не додаєте до нього окремий замок, а навпаки "припускаєте", що він раніше був заблокований методом виклику за допомогою певного блокування. Такі, як Debug.Assert (RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); Інші розробники можуть пропустити коментар, який говорить, що метод виклику повинен використовувати блокування, але вони не можуть ігнорувати це.

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