Як мені обробляти недійсний ввід користувача?


12

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

У мене, як правило, дуже захисний стиль програмування. Мій типовий блок або метод виглядає так:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

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

Якщо говорити більш абстрактно, то мій підхід такий

if all input is OK --> compute result
else               --> do not compute result, notify problem

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

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

Узагальнення

Який із двох підходів до поводження з неправильним введенням ви б порадили?

Inconsistent input --> no action + notification

або

Inconsistent input --> undefined behaviour or crash

Редагувати

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

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

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


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

Сьогодні я виправив помилку, пов’язану з відсутністю перевірки вказівника NULL. Деякий об’єкт створений під час виходу з програми, і конструктор використовував getter для доступу до іншого об'єкта, якого вже не було. Об'єкт не мав бути створений у той момент. Він був створений через іншу помилку: якийсь таймер не зупинявся під час виходу з системи -> був відправлений сигнал -> одержувач намагався створити об'єкт -> конструктор запитував і використовував інший об'єкт -> покажчик NULL -> збій ). Мені дуже не хотілося б, щоб така неприємна ситуація руйнувала мою заявку.
Джорджіо

1
Правило ремонту: коли ви повинні відмовити, відмовте шумно і якнайшвидше.
deadalnix

"Правило ремонту: коли ви повинні відмовити, відмовте шумно і якнайшвидше.": Я думаю, що всі ці BSOD для Windows є застосуванням цього правила. :-)
Джорджіо

Відповіді:


8

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

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

Коли мене запитують номер (наприклад, ціна в доларах чи кількість одиниць), мені подобається ввести "1e9" і подивитися, що робить код. Це може статися.

Чотири десятиліття тому, отримуючи ступінь бакалавра з комп'ютерних наук від UCBerkeley, нам сказали, що хороша програма - це 50% обробка помилок. Будь параноїком.


Так, ІМХО - це одна з небагатьох ситуацій, в якій бути параноїком - це особливість, а не проблема.
Джорджіо

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

Так, але - моя думка полягає в тому, що програма повинна ВИЗНАЧИТИ недійсний вхід і впоратися з ним. Якщо вхід не перевіряється, він буде працювати, це шлях до системи, і неприємні речі з’являться пізніше. Навіть крах краще за це!
Енді Кенфілд

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

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

7

Ви вже маєте правильну ідею

Який із двох підходів до поводження з неправильним введенням ви б порадили?

Невідповідний ввід -> без дій + повідомлення

або краще

Невідповідний вхід -> відповідна дія

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

Догматизм вдачі з прагматизмом.

Стів МакКоннелл сказав це найкраще

Стів МакКоннелл дуже багато написав книгу ( Code Complete ) про оборонне програмування, і це був один із методів, який він радив, що ви завжди повинні перевірити свої дані.

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


2
Замість загальнодоступних, я б запропонував усі неприватні методи захищати мови, які захищали, поділяли або не мали концепції обмеження доступу (усе публічно, неявно).
JustinC

3

Тут немає «правильної» відповіді, особливо не вказуючи мову, тип коду та тип продукту, у який може потрапити код. Поміркуйте:

  • Мова має значення. У Objective-C часто нормально надсилати повідомлення до нуля; нічого не відбувається, але і програма не виходить з ладу. У Java немає явних покажчиків, тому нульові покажчики там не викликають особливих проблем. У С потрібно бути трохи обережнішим.

  • Бути параноїком - це необгрунтована, необгрунтована підозра або недовіра. Це, мабуть, не краще для програмного забезпечення, ніж для людей.

  • Ваш рівень стурбованості повинен відповідати рівню ризику в коді та ймовірній складності виявлення будь-яких проблем, які виявляються. Що відбувається в гіршому випадку? Користувач перезапускає програму і продовжує там, де зупинився? Компанія втрачає мільйони доларів?

  • Ви не завжди можете визначити поганий вхід. Ви можете релігійно порівнювати свої покажчики на нуль, але це лише одне з 2 ^ 32 можливих значень, майже все з яких є поганими.

  • Існує маса різних механізмів поводження з помилками. Знову ж, це певною мірою залежить від мови. Ви можете використовувати макроси затвердження, умовні висловлювання, одиничні тести, обробку винятків, ретельне проектування та інші методи. Жоден з них не є надійним, і жоден не підходить для кожної ситуації.

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


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

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

1

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

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

У більш примітці, що стосується кінцевих користувачів, найкраще повернути щось описове, але просте, щоб хтось міг це зрозуміти. Якщо ваш клієнт зателефонував і сказав, що "програма вийшла з ладу, виправте її", вам належить багато працювати над відстеженням того, що пішло не так і чому, і сподіваючись, що ви зможете відтворити проблему. Використання правильного поводження з винятками може не лише запобігти аварії, але й надати цінну інформацію. Зателефонувавши клієнту, який сказав: "Програма дає мені помилку. У ній написано:" XYZ не є коректним вводом для методу M, тому що Z занадто великий ", або щось таке, навіть якщо вони не мають поняття, що це означає, ви точно знаю, де шукати. Крім того, залежно від ділової практики вашої / вашої компанії, ви можете не виправляти ці проблеми, тому краще залишити їх гарною картою.

Отже, коротка версія моєї відповіді полягає в тому, що ваш перший варіант найкращий.

Inconsistent input -> no action + notify caller

1

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

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

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


0

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

Що стосується перевірки вхідних параметрів у загальнодоступних / захищених методах, я вважаю за краще працювати оборонно і перевіряти параметри та кидати InvalidArgumentException тощо. Саме тому тут є для. Це також залежить від того, ви пишете API чи ні. Якщо це API, і навіть більше, якщо це закрите джерело, краще перевірити все, щоб розробники точно знали, що пішло не так. В іншому випадку, якщо джерело доступне для інших розробників, воно не є чорним / білим. Просто відповідайте своєму вибору.

Редагувати: просто додати, що якщо ви подивитесь, наприклад, на Oracle JDK, ви побачите, що вони ніколи не перевіряють "null" і дозволяють код збій. Оскільки він все одно буде кидати NullPointerException, навіщо турбуватися перевіряти наявність null та викидати явний виняток. Я думаю, це має певний сенс.


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