Закидання винятку всередину нарешті


27

Аналізатори статичного коду, такі як Fortify, "скаржаться", коли виняток може бути кинутий всередині finallyблоку, кажучи про це Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. Зазвичай я з цим згоден. Але останнім часом я натрапив на цей код:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

Тепер, якщо writerнеможливо закрити належним чином, writer.close()метод видасть виняток. Виняток слід кинути, оскільки (швидше за все) файл не був збережений після запису.

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

Які недоліки кидання виключення всередині finallyблоку?


4
Якщо це Java, і ви можете використовувати Java 7, перевірте, чи блоки ARM можуть вирішити вашу проблему.
Ландей

@Landei, це вирішує це, але, на жаль, ми не використовуємо Java 7.
superM

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

@Mike, я використав стандартний підсумок, який показує Fortify, але прямо чи опосередковано є виняток, кинутий усередині нарешті.
superM

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

Відповіді:


18

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

Деякі люди дотримуються бридкої схеми вкладених обробників виключень, ковтаючи будь-які винятки, кинуті в finallyблок.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

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


1
+1 Хоча я серед тих, хто використовує цей неприємний код. Але на своє виправдання я скажу, що я роблю це не завжди, лише коли виняток не є критичним.
superM

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

+1 також від мене; ви в основному сказали саме те, що я збирався, але краще. Варто зауважити, що деякі з реалізацій Stream на Java фактично не можуть викидати Винятки на close (), але інтерфейс оголошує це, оскільки деякі з них так і роблять. Отже, за деяких обставин ви можете додати блок лову, який ніколи насправді не знадобиться.
vaughandroid

1
Що ж такого неприємного у використанні блоку спробу / лову в остаточному блоці? Я б скоріше це зробив, аніж змусив увесь свій код, змусивши обернути всі мої з'єднання та потоки тощо, просто так, щоб їх можна було тихо закрити - що саме по собі вводить нову проблему невідомого закриття з'єднання чи потоку ( чи що завгодно) спричиняє виняток - це те, що ви насправді хотіли б знати чи щось робити ...
заборона-геоінженерія

10

Що сказав Тревіс Паркс, це правда, що винятки в finallyблоці будуть споживати будь-які повернені значення або виключення з try...catchблоків.

Однак, якщо ви використовуєте Java 7, проблему можна вирішити за допомогою блоку спробу використання ресурсів . Згідно з документами, поки ваш ресурс реалізується java.lang.AutoCloseable(більшість письменників / читачів бібліотеки зараз роблять), блок "пробні ресурси" закриє його для вас. Додаткова перевага тут полягає в тому, що будь-яке виняток, що виникає під час його закриття, буде придушене, що дозволяє початковому поверненому значенню чи винятку пропускати.

З

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

До

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
Іноді це корисно. Але в тому випадку, коли мені потрібно викинути виняток, коли файл неможливо закрити, такий підхід приховує проблеми.
superM

1
@superM Власне, тестові ресурси не приховують винятку з close(). "будь-який виняток, що виникає під час його закриття, буде придушений, що дозволяє початковому поверненому значенню або винятку переходити" - це просто не відповідає дійсності . Виняток із close()методу буде придушено лише тоді, коли інший виняток буде викинуто з блоку try / catch. Отже, якщо tryблок не кидає жодного винятку, close()буде викинуто виняток із методу (і значення повернення не повернеться). Його навіть можна схопити з поточного catchблоку.
Руслан Стельмаченко

0

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


0

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

Відновлення помилок не вдається

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

У випадку відмови finallyфіксує логіку, яка виконується в середині відновлення після помилки, до того, як вона буде повністю відновлена ​​(до того, як ми дістанемося до catchмісця призначення).

Уявіть концептуальну проблему, яку вона представляє, щоб зіткнутися з помилкою в середині відновлення після помилки.

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

Ця концептуальна проблема існує будь-якою мовою, яка стосується помилок, будь то C з ручним поширенням коду помилок, або C ++ з винятками та деструкторами, або Java з винятками та finally.

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

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

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

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

Приклад: Ведення журналів

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

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

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

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

SomeFileWriter

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

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

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