Якісний код: перевірка даних обов'язкова

Дискусія, яка виникла в коментарях до посту про -555 тазиків, свідчить про те, що не для всіх очевидно як реагувати на некоректні дані, отримані від користувача.

Між тим, приклад з негативною кількістю товару, також як і різні XSS, SQL-injections - все це приватні випадки, що вимагають підходу, відомого як «Захисне (безпечне) програмування» (Secure programming).

Ця тема досить часто і детально розглядається в спеціалізованій літературі, наприклад, див. «Досконалий код» С. Макконнелла (Steve McConnell, Code Complete), Глава 8 «Захисне програмування».

Наведу невелику цитату з цієї книги (якщо хто не читав - рекомендую):

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

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

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

8.1. Захист програми від неправильних вхідних даних.

Ви, можливо, чули в школі вираз: «Сміття на вході - сміття на виході» («Garbage in, garbage out»). Це варіант застереження споживачеві від розробників ПЗ: нехай користувач остерігається.

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

Існує три основні способи обробки вхідних сміттєвих даних, перелічених далі.

Перевіряйте всі дані зовнішніх джерел

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

Перевіряйте значення всіх вхідних параметрів методу

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

Вирішіть, як обробляти неправильні вхідні дані

Що робити, якщо ви виявили некоректний параметр? Залежно від ситуації ви можете вибрати один з дюжини підходів, детально описаних у розділі 8.3

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

Механізм перевірки може бути різним і залежить від типу даних/мови/бібліотек/фреймворку тощо. Це можуть бути твердження (assertions), перелічені типи даних (enum), властивості тощо. Головне, щоб в архітектурі програми перевірки були обов'язковим елементом, логічно відокремленим від процесу обробки даних.

Щодо реакції на некоректні дані, отриманими через інтерфейс користувача, то, на мій погляд - потрібно бути чесним з юзером. Спроба передбачити, що хотів отримати користувач, вказавши неіснуючий поштовий індекс, негативну кількість товару або некоректний текст замість свого e-mail - буде самообманом. Ми ніколи не дізнаємося, про що дійсно думав відвідувач під час заповнення форми. А значить, не зможемо з'ясувати - яке значення передбачалося насправді.

Ймовірно, він опечатався, можливо, якийсь кулхацкер перевіряв - як тут все працює, а бути може це просто помилка в інтерфейсі.

У будь-якому випадку, якщо відбувається помилка при введенні даних - логічно призупинити виконання програми і повідомити користувача про те, що введені дані неправильні. (Природно, мова йде про зрозумілі для користувача повідомлення, наприклад, "населеного пункту з таким індексом не існує", замість "http/500, UE throw... incorrect zip… at class N»).

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

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

COM_SPPAGEBUILDER_NO_ITEMS_FOUND