Чи стверджує зло? [зачинено]


199

На Goмові творці пишуть :

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

Яка ваша думка з цього приводу?


4
дотична: Go - це незвично виражена мова. Це не обов'язково погано. Однак це означає, що ви повинні приймати його думки з більшим зерном солі. Це також означає, що якщо ви не погоджуєтесь, ви збираєтеся скрекати зуби, використовуючи мову. Як доказ того, як Го чіпляється за свою думку, незважаючи на реальність, врахуйте, що вам потрібно вдатися до магії роздумів, щоб визначити, чи однакові дві колекції.
allyourcode

@allyourcode Якщо ви посилаєтесь на нього reflect.DeepEqual, вам це точно не потрібно . Це зручно, але ціною продуктивності (одиничні тести - хороший випадок використання). В іншому випадку ви можете здійснити будь-яку перевірку рівності, яка підходить для вашої "колекції" без особливих проблем.
Ігор Дубінський

1
Ні, це не те, про що я говорю. Не існує такого поняття, як slice1 == slice2 без роздумів. Усі інші мови мають еквівалент цієї супер основної операції. Єдина причина, яку Го не робить, - це упередження.
allyourcode

Можна порівняти два фрагменти без відображення, використовуючи forпетлю в Go (так само, як C). Було б дуже приємно проводити загальні операції з фрагментами, хоча порівняння ускладнюється, коли задіяні покажчики та структури.
kbolino

Відповіді:


321

Ні, в цьому немає нічого поганого assert, якщо ви використовуєте його за призначенням.

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

  • Затвердження: Збій у самій логіці програми.
  • Поводження з помилками: помилка введення або стан системи, не пов’язана з помилкою в програмі.

109

Ні, ні , gotoні assertне є злом. Але обидва можна зловживати.

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


як gotoрозумно користуватися?
ar2015

1
@ ar2015 Знайдіть одну з нерозумно надуманих моделей, яку деякі люди рекомендують уникати gotoз чисто релігійних причин, тоді просто використовуйте, gotoа не заплутуйте те, що ви робите. Іншими словами: якщо ви зможете довести, що вам справді потрібно goto, і єдиною альтернативою є прийняття вантажу безглуздих лісів, що в кінцевому рахунку робить те саме, але не змінюючи поліції Гото ... тоді просто використовуйте goto. Звичайно, передумовою цього є біт "якщо ти можеш довести, що тобі справді потрібно goto". Часто люди цього не роблять. Це все ще не означає, що це по суті погана річ.
підкреслюю_d

2
gotoвикористовується в ядрі Linux для очищення коду
malat

61

За цією логікою, точки прориву теж є злими.

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

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

Вони не мають нічого спільного з поводженням з помилками, але, на жаль, деякі програмісти зловживають ними як такими, а потім оголошують їх "злими".


40

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

Через твердження я витрачаю набагато менше часу на програми кодування / налагодження.

Я також помітив, що твердження допомагають мені думати про багато речей, які можуть порушити мої програми.


31

В якості додаткової інформації go надає вбудовану функцію panic. Це можна використовувати замість assert. Напр

if x < 0 {
    panic("x is less than 0");
}

panicбуде надрукувати слід стека, тож якимось чином це має на меті assert.


30

Їх слід використовувати для виявлення помилок у програмі. Непогане введення користувачем.

Якщо їх правильно використовувати, вони не є злими.


13

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

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

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

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


8

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

Особисто мені подобається використовувати твердження, тому що вони документують припущення, що я, можливо, зробив під час написання свого коду. Якщо ці припущення порушені при збереженні коду, проблема може бути виявлена ​​під час тесту. Однак я роблю сенс викреслювати кожне твердження з мого коду під час створення збірки виробництва (тобто, використовуючи #ifdefs). Викреслюючи твердження у виробництві, я усуваю ризик того, що хтось зловживає ними як милицею.

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

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


5

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

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


5

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

Порушити в налагоджувачі умову та мати всю інформацію про файл / рядок корисно, окрім точного виразу та точного значення.

Стверджувати, що "оцінюватимуть стан лише за допомогою налагодження", може бути оптимізація продуктивності, і як така корисна лише у 0,0001% програм - де люди знають, що роблять. У всіх інших випадках це шкідливо, оскільки вираз може фактично змінити стан програми:

assert(2 == ShroedingersCat.GetNumEars()); змусив би програму робити різні речі при налагодженні та випуску.

Ми розробили набір макросів затвердження, який би викинув виняток, і робимо це як у відладці, так і у версії версії. Наприклад, THROW_UNLESS_EQ(a, 20);викине виняток із повідомленням what (), що має і файл, і рядок, і фактичні значення а, і так далі. Лише макрос мав би для цього силу. Відладчик може бути налаштований на перерву при "кидці" конкретного типу виключення.


4
90% відсотків статистики, використаної в аргументах, помилкові.
Жоао Портела

5

Мені не подобається твердження. Я не хотів би сказати, що вони злі.

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

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

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


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


3

Нещодавно я почав додавати у свій код кілька тверджень, і ось як це робив:

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

Внутрішній код - це все інше. Наприклад, функція, яка встановлює змінну в моєму класі, може бути визначена як

void Class::f (int value) {
    assert (value < end);
    member = value;
}

і функція, яка отримує вхід з мережі, може читати як таку:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

Це дає мені два шари перевірок. Все, де дані визначаються під час виконання, завжди отримує виняток або негайну обробку помилок. Однак додаткова реєстрація Class::fіз assertзаявою означає, що якщо якийсь внутрішній код коли-небудь зателефонує Class::f, у мене все-таки перевіряється обгрунтованість. Мій внутрішній код може не передати дійсний аргумент (тому що я, можливо, був обчислений valueз якоїсь складної серії функцій), тому мені подобається, що у функції налаштування є твердження, щоб документувати, що незалежно від того, хто викликає функцію, valueне повинно бути більше або дорівнює end.

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


2

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

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

Деякі альтернативи твердженням:

  • Використовуючи налагоджувач,
  • Консоль / база даних / інші журнали
  • Винятки
  • Інші типи обробки помилок

Деякі посилання:

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

Ця особа каже, що твердження повинні використовуватися, коли в модулі є потенційно пошкоджені дані, які зберігаються після викидання виключення: http://www.advogato.org/article/949.html . Це, безумовно, розумний момент, однак, зовнішній модуль ніколи не повинен прописувати, наскільки важливими є пошкоджені дані для викликової програми (виходячи з них "для"). Правильний спосіб впоратися з цим полягає у видаленні винятку, який дає зрозуміти, що програма зараз може знаходитись у непослідовному стані. А оскільки хороші програми здебільшого складаються з модулів (з невеликим кодом клею в головному виконуваному файлі), твердження майже завжди є неправильним.


1

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

З іншого боку, зловживати дуже легко assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

Правильна, правильна версія буде чимось на зразок:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

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


1

assert піддається поводженню з помилками, оскільки воно менше вводить текст.

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


1
Не надто погано :-) Винятки чи ні, твердження чи ні, шанувальники Go все ще говорять про те, наскільки короткі коди.
Моше Рева

1

Я почув, як би стукав автору по голові, коли побачив це.

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

Винятки також легше поєднуються з виробничим кодом, який мені не подобається. Затвердження легше помітити, ніжthrow new Exception("Some generic msg or 'pretend i am an assert'");


1

Моя проблема в цих відповідях, що захищають сцвердження, - ніхто чітко не вказує, чим він відрізняється від звичайної фатальної помилки , і чому артарт не може бути підмножиною винятку . Тепер, маючи на цьому сказане, що робити, якщо виняток ніколи не буде спійманий? Це робить це твердженням за номенклатурою? І чому ви хочете коли-небудь накладати обмеження на мову, що може бути виняток, який / нічого / не може впоратися?


Якщо ви подивитесь на мою відповідь. Моє використання полягає в тому, щоб розмежувати "винятки" (твердження), які я хочу позбутися, щоб використовувати для налагодження проти винятків, які я зберігаю. Чому я хотів би позбутися їх? бо без них робота не була б повною. Наприклад, якщо я обробляю 3 випадки, а 4-й - тодо. Я можу легко шукати ствердження, щоб знайти їх у коді та знати його неповне, а потім використати виняток, який може бути випадково схоплений (іншим програмістом) або важко сказати, чи я його виняток чи логічна перевірка, яку я повинен вирішити в коді.

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

Я вирішив, що найкращі приклади сценаріїв. Ось простий. int func (int i) {if (i> = 0) {console.write ("Число позитивне {0}", i); } else {assert (false); // лінуватися робити негативи ATM} return i * 2; <- Як би я це зробив без тверджень і чи справді виняток є кращим? і пам’ятайте, що цей код буде реалізований до випуску.

Звичайно, винятки є кращими, скажімо, я приймаю введення користувача та дзвоню func()з негативним номером. Тепер раптом із вашими твердженнями ви витягнули килим з-під мене і не дали мені можливості видужати, а не чемно сказати мені, що я прошу, не можна зробити. Немає нічого поганого в тому, що звинувачували програму в недобросовісній поведінці та видавали цитату: проблема полягає в тому, що ви розмиєте акт виконання закону та виправдаєте покарання.
Еван Керролл

У цьому суть, ви ДОЛЖЕНЕ сказати, що користувач хоче зробити, що не може бути зроблено. Додаток повинен закінчуватися з помилкою msg, і хто б не налагоджував, запам'ятав би його щось, що БУДЕ ЗРОБИТИ, але хіба не. Ви НЕ хочете, щоб це оброблялося. Ви хочете припинення, і, щоб нагадати, ви не розглядали цю справу. і надзвичайно легко зрозуміти, які випадки залишилися, оскільки все, що вам потрібно зробити, - це пошук ствердження в коді. Поводження з помилкою було б неправильним, оскільки код повинен бути в змозі зробити це після готовності до виробництва. Дозволити програмісту зловити його і зробити щось інше - неправильно.

1

Так, твердження - це зло.

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

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

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

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

Про це я більше писав у своєму блозі ще в 2005 році тут: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

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


5
Аствердити добре для оголошення попередніх умов у верхній частині функції, і якщо це чітко написано, виступає частиною документації функції.
Пітер Кордес

0

я ніколи не використовую assert (), приклади зазвичай показують щось подібне:

int* ptr = new int[10];
assert(ptr);

Це погано, я ніколи цього не роблю, що робити, якщо моя гра виділяє купу монстрів? чому я повинен збоїти гру, замість цього ви повинні граціозно поводитися з помилками, тому робіть щось на кшталт:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

14
newніколи не повертається nullptr, це кидає.
Девід Стоун

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