Безпека виняткових ситуацій

Гарантії безпеки виняткових ситуацій це набір договірних принципів програмування для реалізації клієнтських бібліотек, які були сформульовані Девідом Абрахамсом[1][2], стосовно міркувань про безпеку обробки виняткових ситуацій в мовах програмування, які використовують механізм виключних ситуацій, зокрема C++.

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

Рівні безпеки

Існує декілька рівнів безпеки виняткових ситуацій (в неспадному порядку безпечності):[3]

  1. Гарантія відсутності виняткових ситуацій, або прозорість неполадок: Гарантовано, що операції завершуються вдало і задовільняють всі вимоги навіть під час виключних ситуацій. Якщо виникають виняткові ситуації, вони будуть оброблятись всередині і не будуть видні клієнтам.
  2. Посилена безпека, або семантика комітів або відкатів: Операції можуть зазнати невдачі, але невдалі операції не мають завдавати шкоди, так що всі дані збережуть свої первинні значення.[4]
  3. Базова, або з гарантією відсутності витоків: Часткове виконання або невдалі операції можуть мати побічні ефекти, але всі інваріанти зберігаються і немає витоків ресурсів. Будь-які збережені дані будуть мати правильні значення, навіть якщо вони відрізнятимуться від тих, що були перед виникненням виключної ситуації.
  4. Відсутність безпеки виняткових ситуацій: Немає ніяких гарантій.

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

Техніки виконання безпеки виняткових ситуацій

Основними засобами мови програмування для написання безпечного коду є:

Основною ідеєю техніки/шаблону проектування «Resource Acquisition Is Initialization» (RAII) є те, що володіння ресурсом віддається об'єкту з обмеженою областю видимості. Зазвичай такий об'єкт створює (відкриває, виділяє пам'ять, і так далі) ресурс в своєму конструкторі. Таким чином, деструктор об'єкта може звільнити ресурс в кінці свого життя незалежно від того, що призвело до закінчення його життєвого циклу, вихід за межі видимості чи виняткова ситуація.[5]

Міфи і забобони

«Взаємодія між шаблонами і винятковими ситуаціями (exceptions) погано зрозуміла.» Цей міф легко спростовується тим, що ніякої взаємодії не існує. Шаблон, після створення його екземпляру, в усіх відносинах працює як звичайний клас чи функція. Для того, щоб передбачити поведінку шаблона при виключних ситуаціях, слід думати про нього як про конкретний екземпляр, який даний шаблон реалізує. Врешті решт, узагальненість шаблонів не має викликати особливих занепокоєнь. Хоча клієнтська частина програми поставляє компоненту частину реалізації (яка може, якщо не прописана інша поведінка, генерувати довільні виняткові ситуації), те ж саме виникає і при використанні віртуальних функцій або просто роботі із вказівниками на функції.

«Неможливо написати безпечний узагальнений контейнер». Це твердження часто виникає з посиланням на статтю Тома Каргіла (Tom Cargill)[6], в якій він досліджує проблему безпеки виключних ситуацій для узагальнених стекових шаблонів. В своїй статті, він підіймає багато корисних питань, але на жаль не може дати вирішення його проблеми. Тому він робить припущення, що рішення не існує. Але пісня публікації статті, послідувало багато прикладів безпечних узагальнених компонентів, серед яких контейнери стандартної бібліотеки C++.

«Наявність коду з генеруванням і обробкою виключних ситуацій сповільнює програму, а шаблони використовуються спеціально для отримання найкращої можливої продуктивності.» Хороша реалізація C++ не призведе до жодного циклу команд для обробки виняткових ситуацій, до моменту їх генерування, а при обробці швидкість виконання коду буде порівняна з викликом функції. Програма, яка містить код обробки виняткових ситуацій буде мати таку саму швидкодію, як і програма яка ігнорує можливість виникнення помилок. Використання виняткових ситуацій (exceptions) може привести до прискорення програми, в порівнянні з «традиційним» способом обробки помилок з ряду причин. По-перше, блок catch явно вказує компілятору, який код відноситься до ситуацій обробки помилок; і він може виноситись окремо із звичайного ходу виконання програми, підвищуючи компактність посилань. По-друге, код із «традиційним» способом обробки помилок має зазвичай має повертати коди помилок, а код який їх викликає повинен постійно їх перевіряти після кожного виклику такої функції; використання винятків повністю позбавляє від цих накладних витрат.

«Винятки ускладнюють розуміння поведінки програми.» Зазвичай це приводять на підтримку міфу про «приховані» шляхи виконання програми, які відбувається під час знищення стеку (stack-unwinding). Приховані шляхи виконання програми не нове поняття для програміста C++, який завжди очікує, що локальні (автоматичні) змінні підлягають знищенню після виходу із функції:

ErrorCode f( int& result )         // 1 
{                                  // 2 
    X x;                           // 3 
    ErrorCode err = x.g( result ); // 4 
    if ( err != kNoError )         // 5 
        return err;                // 6 
    // ...Будь-який інший код слідує тут... 
    return kNoError;               // 7 
}

У прикладі наведеному вище, відбувається «прихований» виклик деструктору X::~X() у 6-ій і 7-ій строчці коду. Завдяки, використанню винятків, немає явного коду, який присвячений обробці помилки:

int f()                 // 1 
{                       // 2 
    X x;                // 3 
    int result = x.g(); // 4 
    // ...Будь-який інший код слідує тут...  
    return result;      // 5 
}

Для більшості програмістів, які знайомі з механізмом обробки виняткових ситуацій, другий приклад насправді є більш читабельним і зрозумілим, ніж перший. «Приховані» шляхи виконання коду мають ті самі виклики деструкторів локальних змінних. Крім того, вони слідують тому самому методу, який працює так само ніби там би були потенційні команди виходу return після виклику кожної функції, які генерують виключні ситуації. Читабельність такого коду краща, оскільки код виконання логіки програми не змішується з кодом обробки помилок, а функція може використовувати можливість повернення значень природним чином на свій розсуд.

Примітки

Література


This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.