Управление на изключения

от Уикипедия, свободната енциклопедия
Направо към навигацията Направо към търсенето

Управлението на изключения е процес за справяне с възникващите по време на изпълнение изключения – аномалии или необикновени условия, изискващи специална обработка, често променящи нормалното протичане на изпълнението на програмата. Управлението на изключенията се осигурява от специализирани конструктори в езиците за програмиране или механизми на хардуера.

Като цяло, изключение се улавя (обработва) като се запазва текущото състояние на изпълнението на предварително зададено място и се превключва към изпълнение на специфична подпрограма, известна като манипулатор на изключения. Ако изключения са продължаващи, манипулаторът може по-късно да възобнови изпълнението на първоначалното местоположение, с помощта на запазената информация. Например, изключение за стойност с плаваща запетая разделена на нула, по подразбиране, ще позволи на програмата да бъде възобновена, докато недостатъчното състояние на паметта не може да бъде разрешено толкова лесно.

Алтернативни подходи за справяне с изключения в софтуера са проверките за грешки, които поддържат нормалното изпълнение на програмата и по-късно проверяват за отчетени непредвидени случаи, с помощта на специални стойности на връщане или някои спомагателни глобални променливи като errno в C, флагове с плаваща запетая, или валидация на входящите данни за превантивно филтриране на случаите, пораждащи изключения.

Някои програмисти пишат софтуер с функции, докладващи грешки, които събират детайли и могат да бъдат полезни при разрешаването на проблем като покажат тези детайли на екрана или ги съхранят във файл.

Управление на изключения в хардуера[редактиране | редактиране на кода]

Механизмите за управление на изключения в хардуера се обработват от процесора. Едно от функциите му е да открива грешки и да пренасочва потока на програмата към управлението им. Състоянието преди възникването на изключението се запазва в стека.

Управление на изключения в хардуера: IEEE 754 стандарт[редактиране | редактиране на кода]

Управлението на изключения в аритметичния хардуерен стандарт с плаваща запетая IEEE 754 главно се отнася до изключителни условия и дефинира изключението като „събитие, което се случва, когато една операция върху някои конкретни операнди няма изход, подходящ за всяко логическо приложение. Тази операция може да сигнализира едно или повече изключения, като извика управлението по подразбиране или, ако е изрично поискано, алтернативно, дефинирано от езика управление на изключението.“

По подразбиране, IEEE 754 изключенията са възобновими и се обработват чрез заместване на предварително определена стойност за различните изключения, например изключения при безкрайност или делене на нула, предоставяне на флагове за състояние или проверка за настъпване на изключение (виж езика за програмиране C99 за типични примери за управление на IEEE 754 изключения). 

Управление на изключения в софтуера[редактиране | редактиране на кода]

Обработката на софтуерни изключения и подкрепата, предоставена от софтуерните инструменти, се различават в известна степен от това, което се разбира под изключения на хардуера, но въпреки това имат подобни концепции. В механизмите за управление на изключения в езиците за програмиране, терминът изключение обикновено се използва за означаване на структура от данни, която съхранява информация за изключителен случай. Един механизъм за прехвърляне на контрол, или предизвикване на изключение, е известен като throw . Ако името на изключението е еквивалентно на това в throw, изпълнението се прехвърля в „catch“.

Предизвикването на изключение е практичен начин за сигнализиране, за това, че програмата не може да се изпълни нормално – например, когато входните данни са невалидни (стойността е извън зададените интервали) или когато ресурс, на който програмата разчита не е достъпен (като липсващ файл, грешка на твърдия диск, или грешка в паметта). В системи, в които няма изключения, програмите ще трябва да върнат специален код за грешка. Това понякога се усложнява от семантични проблеми, при които се изисква допълнителен код, за разграничаване на нормално върнатите стойности от грешните.

Според сравняваща статия през 2006 г. от Joseph R. Kiniry, програмните езици се различават значително в тяхната представа за това, какво е изключение. Според Kiniry, съвременните езици могат да бъдат грубо разделени на две групи:

  • Тези езици, в които изключенията „са създадени, като структури, които контролират входния поток от данни“; според тази статия Ada, C++, Java, Modula-3, ML, OCaml, Python, и Ruby спадат към тази категория
  • Тези езици, в които изключенията „са създадени, за да контролират нетипични, непредсказуеми или грешни ситуации“; според тази статия това включва: C#, Common Lisp, Eiffel, и Modula-2

Kiniry също отбелязва "Език създаден само частично оказва влияние върху управлението на изключения, следователно, и върху начина, по който той се справя с частична или цялостна грешка по време на изпълнение на системата. Другото основно влияние са примерите за използване, обикновено в основните библиотеки и примерите на код в техническите книги, статии, онлайн дискусионни форуми и в стандартите на организацията на код.

Съвременните приложения са изправени пред много предизвикателства при проектиране, когато се обмислят стратегиите за обработка на изключенията. Особено в новите приложения на ниво начинания, изключенията често присъстват в процеса. Част от разработването на солидна стратегия за управление на изключения е разпознаване, кога един процес не работи, както и момента в който не може да бъде икономически спасен от софтуерната част от процеса.

Управлението на изключения често не е изпълнено коректно в софтуера, особено, ако има многобройни изключения; анализирането на потока на данни на 5 милиона реда от Java код, показва над 1300 дефекта при улавяне на изключения.

История[редактиране | редактиране на кода]

Управлението на изключения е разработено в Lisp през 1960-те и 1970-те години. Първоначално се появява в LISP 1.5 (1962), къде изключенията се улавят с помощта на ключовата дума ERRSET, която връща стойност NIL в случай на грешка, вместо да прекрати програмата или да включи дебъгера. Предизвикването на изключения е въведено в MacLisp през 60-те години с ключовата дума ERR. Това бързо започва да се използва не само за предизвикване на изключения, но и за контрол на входящия поток и е определено с две нови ключови думи, CATCH и THROW (MacLisp, юни 1972 г.), запазвайки ERRSET и ERR за управление на изключения. Почистващото поведение, което сега се нарича „finally“ е представено първоначално в NIL (New Implementation of LISP) от средата до края на 1970-те като UNWIND-PROTECT. След това е прието и в Common Lisp. По същото време се използва dynamic-wind в Scheme, като метод за справяне с изключенията при затваряне. Първите книги на тема структурирано управление на изключения са Goodenough (1975a) и Goodenough (1975b). През 80-те управлението на изключения се приема широко от редица езици за програмиране.

Първоначално управлението на софтуерни изключения включва както възобновими изключения (семантика на възобновяване), като повечето хардуерни изключения, така и невъзобновими изключения (семантика на прекратяване). Възобновяването обаче е сметнато за неефективно в практиката през 1970-те и 1980-те години (виж дискусията за C++ стандартизация, цитирана по-долу), и вече не се употреба често, макар, че се поддържа от езици за програмиране, като Common Lisp и Dylan.

Семантика на прекратяване[редактиране | редактиране на кода]

Механизмите за обработка на изключения в съвременните езици обикновено са невъзобновими („семантика на прекратяване“), за разлика от хардуерните изключения, при които има възможност за възобновяване. Това се базира на опит от използване и на двете, тъй като има теоретични и практически аргументи в полза и на двете решения; те са широко обсъждани по време на дискусиите за C++ стандартизация през 1989 – 1991 г., което довежда до окончателно решение за семантика на прекратяване. По повод мотивите за такъв дизайн за C++ механизма, Строуструп отбелязва:

На срещата за обсъждане на C++ стандартизацията в Пало Алто през ноември 1991 г., чухме брилянтно кратко изложение на мотивите в полза на семантиката на прекратяване, подкрепени както с личен опит, така и с данни от Джим Мичъл (от Sun, някога от Xerox PARC). Джим бе използвал управление на изключения в половин дузина езици в продължение на период от 20 години и е ранен привърженик на семантиката на възобновяване, като един от основните дизайнери и изпълнители на Cedar/Mesa системата на Xerox. Неговото послание беше:

„Прекратяването се предпочита през възобновяването; това не е въпрос на мнение, а въпрос на години опит. Възобновяване е съблазнително, но не е валидно.“

Той подкрепи това твърдение с данни от няколко операционни системи. Ключовият пример беше Cedar/Mesa: Тя е написана от хора, които обичат и използват възобновяване, но след десет години използване, има само една останала употреба на възобновяване в системата състояща се от половин милион реда – и това е контекстовото запитване. Тъй като не е имало реална необходимост от възобновяването за такова контекстово запитване, те го отстраняват и е установяват значително увеличение на скоростта в тази част на системата. Във всеки случай, в който е използвано възобновяване, през тези десет години, в бъдещето се превръща в проблем, който е заменен от по-подходящ дизайн. На практика, всяка употреба на възобновяване е представена като неспазване на отделните нива на абстракция.

Критика[редактиране | редактиране на кода]

Контрастен поглед върху безопасността на управление на изключения е даден от Ч.А.Р. Хоаре през 1980 г., описващ езика за програмиране Ada като имащи"... множество функции и условни конвенции, много от тях ненужни и някои от тях, като управление на изключения, дори опасни. [...] да не се допуска този език в сегашното си състояние да се използва в приложения, където надеждността е от решаващо значение [...]. следващата заблудена ракета в резултат на грешка на езика за програмиране може да не е на проучвателна космическа ракета на безвредно пътуване до Венера: Тя може да бъде ядрена бойна глава, експлодираща над един от нашите собствени градове ".

Позовавайки се на множество предишни изследвания от други (1999 – 2004 г.) и на собствените си резултати, Ваймар и Некула пишат, че значителен проблем с изключения е, че те „създаде скрити пътеки за контрол на потоци, които са трудни за разбиране от програмистите“.

Поддръжка на изключенията в езиците за програмиране[редактиране | редактиране на кода]

Вижте също: синтаксис за отработване на изключения

Много компютърни езици имат вградена поддръжка за изключения и управление на изключения. Те включват ActionScript, Ada, BlitzMax, C ++, C #, D, ECMAScript, Eiffel, Java, ML, Object Pascal (например Delphi, Free Pascal, и други подобни), PowerBuilder, Objective-C, OCaml, PHP (от версия 5), PL/1, PL/SQL, Prolog, Python, REALbasic, Ruby, Scala, Seed7, Tcl, Visual Prolog и повечето .NET езици. Отработването на изключения обикновено не предлага възможността за продължаване на изпълнението на кода при тези езици, и когато едно изключние бива хвърлено, програмата търси обратно през стека с викане на функции, докато се намери код, който обработва изключението.

При някои езици се наблюдава изпразване на стека, докато това търсене прогресира. Например, ако функцията f, съдържаща манипулатор H за изключение E, призовава функция g, която от своя страна изисква функция h, и изключение E се хвърля в h, след функции g и h могат да бъдат прекратени, и Н в f ще се обработи E.

Eзик за управление на изключения, без това изпразване на стека е Common Lisp с неговата условна система. Common Lisp вика манипулатора на изключения и не изпразва стека. Това позволява на програмата да продължи изчисляването на точно същото място, където е възникнала грешката (например, когато по-рано липсващ файл е станал наличен). Имплементацията без stack на езика за програмиране Mythryl поддържа постоянна управление на изключения, без изпразване на стека.

С изключение на незначителни синтактични различия, има само няколко стилове за управление на изключения в употреба. В най-популярния стил, изключение се инициализира със специален израз (throw или raise) с обект изключение (например при Java или Object Pascal) или стойност на специален разширяващ се енумериран тип (например при Ada). Обхватът на манупилатора на изключения започва с маркираща клауза(try или специфична за езика ключова дума като begin) и завършва в началото на първата клаузана манипулатор (catch, except, rescue). Няколко клаузи на манипулатора могат да последват, и всяка може да уточнява кой вид изключение обработва и какво използва за обекта на изключение.

Няколко езици също позволяват клауза (else), която се използва в случай, че не е хвърлено изключение преди края на обхвата на манипулатора.

По-често е свързано с клауза (finally или ensure), който се изпълнява без значение дали изключение е хвърлено или не, обикновено за да се освободят ресурси, придобити в рамките на тялото на блока за управление на изключения. Трябва да се отбележи, че C ++ не предоставя тази конструкция, тъй като тя насърчава техниката „Придобиването на ресурси е инициализация“ (RAII), която освобождава ресурси, използвайки унищожители.

В своята цялост, код за управление на изключения може да изглежда така (при Java-подобен Псевдокод; имайте предвид, че изключение от тип EmptyLineException трябва да бъде декларирано някъде в кода.

try {
    line = console.readLine();

    if (line.length() == 0) {
        throw new EmptyLineException("The line read from console was empty!");
    }

    console.printLine("Hello %s!" % line);
    console.printLine("The program ran successfully");
}
catch (EmptyLineException e) {
    console.printLine("Hello!");
}
catch (Exception e) {
    console.printLine("Error: " + e.message());
}
finally {
    console.printLine("The program terminates now");
}

Като малка вариация, някои езици използват една единствена клауза манипулатор, който се занимава с класа на изключението вътрешно.

Според статия от 2008 г. на Уесли Ваймар и Джордж Некула, синтаксисът на try ... finally блоковете в Java е фактор, допринасящ за софтуерни дефекти. Когато един метод трябва да се справят с придобиването и освобождаването на 3 – 5 ресурса, програмистите явно не желаят да влага достатъчно блокове, поради опасения за четимостта, дори когато това би било правилното решение. Възможно е да се използва един единствен trycatch блок, дори когато се занимават с множество ресурси, но това изисква правилна употреба на контролните стойности, което е още един общ източник на грешки за този вид проблем. Относно семантиката на try-catch-finally конструкциите като цяло, Ваймар и Некула пишат, че "Докато try-catch-finally концептуално просто, то е описанието с най-сложно изпълнение при спецификациите на езика и изисква четири нива на вложени „if“ в своето официално описание на английски. Накратко, то съдържа голям брой специални случаи, които програмистите често пренебрегват "

C поддържа различни начини за проверка за грешки, но като цяло не се счита за език, поддържащ „управление на изключения.“ Perl има допълнителна поддръжка за структурирана управление на изключения.

В контраст с това, поддръжката на Python за управление на изключения е всеместна и последователна. Трудно е да се напише стабилна програма на Python, без да използват неговите try и except ключови думи.

Имплементация на обработването на изключения[редактиране | редактиране на кода]

Изпълнението на управление на изключения в езиците за програмиране обикновено включва голяма доза поддръжка от генератор на код и системата за изпълнение, придружаващи един компилатор. (Точно с добавянето на управление на изключенията при C ++, приключва полезният живот на оригиналния C ++ компилатор, Cfront.) Две схеми са най-често срещаните. Първата, динамична регистрация, генерира код, който непрекъснато актуализира структури за състоянието на програмата по отношение на управлениета на изключения. [16] Обикновено това добавя нов елемент към оформлението на стека, който знае какви манипулатори са на разположение на функцията или метода, свързани с този отрязък от стека; ако изключение бива хвърлено, указател в оформлението насочва изпълнението до съответния манипулатор на кода. Този подход е компактен по отношение на пространството, но утежнява изпълнението при влизане в отрязъка и при излизане. Той обикновено се използва в много Ada имплементации, например, при случаи, където сложна поддръжка за генериране и вече е необходима за много други особености на езика. Динамичната регистрация, бъдейки сравнително лесна за дефиниране, се поддава на доказване на коректността.

Втората схема, тази която е имплеметирана при много качествени C ++ компилатори, е базирания на таблица подход. Той създава статични таблици време на компилация и навръзване, които свързват части на програмния брояч към състоянието на програмата и съответния манипулатор на изключения. След това, ако изключение бъде хвърлено, системата за изпълнение намира текущото място на инструкцията в таблиците и определя кои манипулатори са на разположение и какво трябва да се направи. Този подход намалява утежняването на изпълнение за случаи, когато изключение не се хвърля. Това се случва с цената на малко пространство, но това пространство може да бъде разпределено в специално сектри от данни само за четене, които не се зареждат или алокират, докато не се хвърли изключение. Този втори подход е също по-добър по отношение на постигане на безопасност при многонишково програмиране.

Предлагат се и други схеми за определяне и импленентация. За езици, които поддържат метапрограмиране, подходи, които не са свързани с никакво утежняване са в напреднала разработка.

Управление на изключения на базата на дизайн по предназначение[редактиране | редактиране на кода]

Един различен поглед върху изключения се основава на принципите на дизайн по предназначение и се поддържа по-специално чрез езика Eiffel. Идеята е да се осигури по-строг основа за управление на изключения, като определя точно какво е „нормално“ и „ненормално“ поведение. По-конкретно, подходът се основава на две понятия:

  • Провал: невъзможността на дадена операция, за да изпълни предназначението си. Например, едно събиране може да предизвика аритметично препълване (операцията не изпълнява задължението си да изчислители достатъчно добро приближение до математическата сума); или рутина може да не успее да изпълни своето следусловие.
  • Изключение: ненормално събитие, настъпило по време на изпълнение на рутина (която рутина е „получател“ на изключението). Такива ненормални събития резултират от провала на операция, извикана от рутината.

„Принципът на безопасно отработване на грешки“(„Safe Exception Handling principle“) въведен от Бертран Майер в обектно-ориентирано построяване на софтуер твърди, че има само два смислени начина една рутина да може да реагира при възникване на изключение:

  • Провал или „организираната паника“: Рутината поправя състоянието на обекта чрез повторно установяване на непроменлива, (това е „организиран“ част), и след това се проваля (паника), което задейства изключение при своя повиквател (така, че извънредното събитие да не бъде игнорира).
  • Повторен опит: Рутината опитва алгоритъма отново, обикновено след смяна на някои стойности, така че следващия опит да има по-голям шанс за успех.

По-специално, просто игнориране на изключение, не е разрешено; един блок код трябва или да бъде изпълнен повторно и успешно завършен, или да предаде изключението на повиквателя му.

Ето един пример, изразена в Айфел синтакс. Той предполага, че рутинна send_fast обикновено е по-добър начин да се изпрати съобщение, но може да се провали, като предизвика изключение; ако е така, алгоритъмът следващия използва send_slow, който се проваля по-рядко. Ако send_slow не успее, рутината send като цяло трябва да се провали, карайки повиквателя да получи изключение.

send (m: MESSAGE) is
  -- Send m through fast link, if possible, otherwise through slow link.
local
  tried_fast, tried_slow: BOOLEAN
do
  if tried_fast then
     tried_slow := True
     send_slow (m)
  else
     tried_fast := True
     send_fast (m)
  end
rescue
  if not tried_slow then
     retry
  end
end

Булевите локалните променливи се инициализират да са False в началото. Ако send_fast не успее, тялото (do clause) ще бъде изпълнена отново, причинявайки изпълнение на send_slow. Ако това изпълнение на send_slow не успее, rescue клаузата ще се изпълни до края, без повторен опит (няма else клауза при финалния if), карайки изпълнението на рутината като цяло да се провали.

Този подход има предимството да дефинира ясно кой случай е „нормален“ и „ненормален“: ненормален случай, причинявайки изключение, е такъв, при който рутината не е в състояние да изпълни предназначението си. Той дефинира ясно разпределение на ролите: do клаузата (нормално тяло код) е отговорна за постигане, или се опитва да постигне, предназначението на рутината; rescue клаузата е отговорна за създаване на контекст и рестартиране на процеса, ако това има шанс за успех, но не и за извършване на действително изчисление.

Въпреки че изключенията в Айфел имат доста ясна философия, Кинири (2006) критикува имплементаципта им, защото " изключенията, които са част от дефиницията на език са представени от целочислени стойности, докато тези дефинирани от разработчик от стрингови стойности. [...] Освен това, тъй като те са основни стойности, а не обекти, те нямат присъщата семантика отвъд това, което се изразява в една помощна рутина, който не е задължително да може да бъде имунизирана от грешки, поради ефекта на представителството пренатоварване (например, човек не може да разграничи две числа на една и съща стойност). "

Неуловени изключения[редактиране | редактиране на кода]

Ако изключение е хвърлено и не се улови (оперативно, изключение се хвърля, когато няма посочен подходящ манипулатор), неуловеното изключение се обработва от изпълнението на програмата; рутината, която прави това, се нарича манипулатор неуловени изключения. Най-често срещаното поведение по подразбиране е да прекрати програмата и отпечата съобщение за грешка на конзолата, обикновено включително информация за отстраняване на грешки, като например стрингово представяне на изключението и проследяването на стека. Това често се избягва, имайки манипулатор най-високо ниво (на ниво приложение) (например в цикъл на събитие), което хваща изключения, преди те да достигнат до изпълнението.

Трябва да се отбележе, че въпреки че необработено изключение може да доведе до прекратяване на програмата анормално (програмата може да не е правилна, ако изключението не е хванато, най-вече като не върне обратно частично завършени транзакции, или не освободи ресурси), процесът се прекратява обикновено (ако приемем, че изпълнението работи правилно), тъй като изпълнението (което се контролира изпълнението на програмата) може да гарантира правилното терминиране на процеса.

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

Стандартният манупулатор на изключения може да бъде пренабрегнат, или глобално, или по нишка, например, за да се осигури алтернативно записване на грешката или докладване на крайния потребител за неуловените изключения, или да се рестартира нишката, прекратена поради неуловено изключение. Например, в Java това се прави за една нишка чрез Thread.setUncaughtExceptionHandler и в глобално чрез Thread.setDefaultUncaughtExceptionHandler; в Python това се прави чрез модифициране на sys.excepthook.

Статична проверка на изключения[редактиране | редактиране на кода]

Проверени изключения[редактиране | редактиране на кода]

Дизайнерите на Java създават проверени изключения, които са специален набор от изключения. Проверените изключенията, които един метод може да причини, са част от сигнатурата на метода. Например, ако даден метод може да хвърли IOException, той трябва да декларира този факт изрично в сигнатурата си. Неизпълнението на това условие хвърля грешка по време на компилацията.

Кинири (2006) отбелязва, че библиотеките на Java (такива, каквито са били през 2006) често са били непоследователни в подхода си за репортинг за грешки, защото “Не всички грешки в Java се представляват чрез изключения. Много методи връщат специални стойности, които индикират за грешка в кода на полето константа на свързаните класове".

Проверените изключения са свързани с инструменти за проверка на изключения, които съществуват за OCaml програмен език. Външният инструмент за OCaml е едновременно невидим (т.е. не изисква никакви синтактични анотации) и по избор (т.е. възможно е да компилираш и изпълниш програмата без да си направил проверка на изключенията, въпреки че това не е препоръчително).

Програмният език CLU има функция с интерфейс, близък до този, който Java пускат малко по-късно. Функцията може да повика само изключения, които са изброени в нейния вид, но всички останали изключения извикани от функции, ще се превърнат в изключения по време на изпълнение, failure , вместо в грешка по време на компилация.

По-късно Modula-3 излизат с подобни функции. Тези функции не включват проверка по време на компилиране, което е основно в концепцията за проверени изключения и (от 2006) не е интегрирано в нито един голям програмен език, с изключение на Java.

Езикът за програмиране C++ въвежда незадължителен механизъм за проверка на изключения, наречен exception specifications. По подразбиране, всяка функция може да хвърли всякако изключение, но това може да бъде ограничено от throw клауза добавена към подписа на функцията, която оказва кое изключение функцията може да хвърли. Exception specifications не се инфорсват по време на компилиране. Нарушенията са резултат на глобалната функция std: unexpected. Може да се получи празна exception specification, което индикира, че функцията няма да даде изключение. Това не е направено по подразбиране, когато управлението на изключенията са добавени в езика, защото ще изиска твърде много модификация на съществуващия код, би попречило на взаимодействието с код писан на друг език и ще изкушава програмистите да пишат прекалено много манипулации на локално ниво. Изричното използване на празни exception specifications обаче, може да предостави на C++ компилаторите възможността да обработят голямо количество код и stack layout оптимизации, които трябва да се поддтискат, когато обработката на изключението се случва във функция. Някои анализатори виждат, че правилната употреба на exception specifications в C++ е трудна за постигане. В последните C++ стандарти (C++11) използването на exception specifications, също както е специфицирано в предната версия на стандарта (C++3), е отхвърлено.

За разлика от Java, езици като C# не насилват улавянето на изключенията. Според Ханспетер Мьосенбьок, невъзможността да се разграничи проверено изключение от непроверено изключение, прави писането на програми по-удобно, но по-малко стабилно, тъй като непроверено изключение води до прекъсване със stack trace. Кинири отбелязва, че JDK (версия 1.4.1) на Java, хвърля голям брой непроверени изключения: едно за всеки 140 линии код, докато Айфел ги използва много по-пестеливо, едно на всеки 4600 реда код. Кинири също пише „Както всеки Java програмист знае, обемът на 'try catch“ кода във всяко едно типично Java приложение е, в повечето случаи по-голям, от кода необходим за изричния официален параметър и проверка на върнатата стойност в други езици, отколкото да нямаш проверени изключения. Всъщност, общото заключение измежду всички Java програмисти е, че обработката на проверени изключения е също толкова неприятна, колкото и писането на документация. И така много програмисти казват, че „мразят“ проверените изключения. Това води до изобилие от „проверени, но игнорнати изключения“. Кинри също отбелязва, че C# програмистите очевидно са били повлияни от този вид потребителски опит със следния цитат, който се приписва на тях (чрез Ерик Гънърсън):

„Изледването на малки програми води до извода, че необходимостта от exception specifications, може едновременно да подобри продуктивността на програмиста и да подобри качеството на кода, но опитът с големи софтуерни проекти, показва различен резултат – намаляване на продуктивността и малко или никакво повишаване на качеството на кода.“

Според Андреас Хейлсберг имало широко съгласие в тяхната дизайн група да нямат проверени изключения като функционалност в C#. Хайсберг обяснява в интервю, че:

„Throws“ клаузата, по начина, по който се имплементира в Java, не те задължава да обработваш изключенията, но ако не ги обработваш, те задължава да опишеш точно какви изключения могат да преминат. Задължава те или да хванеш декларирано изключение, или да го вкараш в своя собствена „throws“ клауза. За да заобиколят това изискване хората правят смешни неща. Например те декорират всеки метод с „throws exception“. Това напълно обезсмисля функцията и кара програмиста да пише „врели некипели“ . Това не помага на никого.“

Възможности за използване[редактиране | редактиране на кода]

Проверени изключение по време на компилация намаляват честотата на необработени изключения, които излизат по време на изпълнение на програмата. Непроверени изключения (като JavaobjectsRuntimeException and Error) остават необработени.

Въпреки това, проверените изключения могат да изискват големи throws декларации, разкриващи подробности по изпълнението и намаляване на енкапсулирането или да насърчават писането на лошо съставени try/catch блокове, които могат да скрият легитимни изключения от техните потребители. Интерфейс може да бъде деклариран, за да хвърля изключения X и Y. В по-късна версия на кода, ако някой иска да хвърли изключение Z, това ще направи новия код несъвместим с предишните му ползи. Още повече, с „adapter pattern“, където една част от кода декларира интерфейс, който след това се имплементира от друга част на кода, за да може кодът да бъде добавян и извикван от първата му част. „Adapter code“ може да има голям набор от изключения, с който да опише проблемите, но е принуден да използва типовете изключения декларирани от интерфейса.

Възможно е да се намали броя на декларираните изключения или като се декларира суперклас, на всички потенциални изключения, или като се дефинират и декларират типове изключения, подходящи за нивото на абстракция на повикания метод и мапването на изключения от по-долно ниво към тези типове, за предпочитане обвързани използвайки навръзването на изключения, за да се запази основната причина. В допълнение е възможна в примера по-горе за навръзваните интерфейси необходимостта от промяна на кода, тъй като в някои случаи изключенията, които извикват метода са част от самия метод. Използвайки декларации за throws изключения или catch, обикновено е достатъчно за проверките в Java. Докато това може да има някаква полза, то обикновено заобикаля механизма за проверените изключения, което Oracle обезсърчава.

Типовете непроверени изключения обикновено не трябва да се обработват с изключение, може би, на най-външните нива на обхват. Те често представляват сценарии, които не позволяват възстановяването на „RuntimeExceptions“, които често рефлектират на дефекти и ерори в програмата, най-често са невъзстановяеми JVM грешки. Дори в език, който поддържа проверени изключения, има случаи, в които използването на концепция за проверени изключения, не е подходящо.

Динамична проверка на изключения[редактиране | редактиране на кода]

Смисълът на обработването на изключения не е да гарантира, че кодът може да се справи с грешката. За да установим, че механизмите за управление на изключения са достатъчно надеждни, е необходимо да представим кода с широк спектър от невалидни или неочаквани входящи данни. Такива могат да бъдат създадени чрез софтуер „fault injection“ и „mutation testing“. Един от най-трудните типове софтуер, за който да пише методи за управление на изключения, е протоколен софтуер, тъй като надеждно имплементиране на протокол, трябва да бъде подготвено за получаване на входни данни, които не съответстват на релевантните спецификации.

С цел гарантирането на смислен регресионен анализ по време на целия жизнен процес на разработка, всеки тест за обработка на изключение трябва да бъде силно автоматизиран и тест случаите трябва да бъдат генерирани по научен и повторим метод. Съществуват няколко платени системи, които могат да правят такива тестове.

В среди като Java или .NET, съществуват инструменти, които могат да бъдат прикачени към времето на изпълнение на програмата и всеки път, когато се получи изключение, те записват debugging информацията, която съществува в RAM паметта по времето, в което изключението се е появило. Тези инструменти се наричат „автоматични методи за управление на изключения“ или инструменти за прихващане на грешки и даващи информация за основната причина за изключението.

Синхронизация на изключенията[редактиране | редактиране на кода]

Свързано с концепцията за проверени изключения е синхронизацията на изключенията. Синхронизирани изключения се получават по време на конкретно изявление на програмата, докато несинхронните изключения могат да се проявят практически навсякъде. От това следва, че обраотка на несинхронни изключения не може да се изисква от компилатора, а и също така са много трудни за програмиране. Пример за нормално несинхронно събитие е натискането на Ctrl+C за прекъсването на програма и получаването на сигнал „stop“ или „suspend“.

Програмните езици обикновено се борят с това чрез ограничаване на несинхронността, например Java отхвърля използването на нейното „ThreadDeath“ изключение, което позволява един процес да бъде спрян от друг. Вместо това, може да има полу-синхронизирани изключения, които се повдигат само на подходящото местоположение в програмата.

Системи на състоянието[редактиране | редактиране на кода]

Lips, Dylan и Smalltalk имат система на състоянието, която обхваща гореспоменатата система за управление на изключения. В тези езици или среди появата на състояние предполага изискването на функция и само в края на обработката на изключението може да се вземе решение за разпускане на стак-а. Състоянията са обобщение на изключенията. При възникване на състояние подходяща обработка на състоянието се избира. Състояние, което не представлява грешка, може да не бъде обработвано изобщо. Тяхната единствена цел може да бъде да подава съвети или предупреждения към потребителя.

Продължаващи изключения[редактиране | редактиране на кода]

Те са свързани с така наречения възобновяващ модел на управление на изключенията, в който някои изключения се наричат „продължаващи“: позволено е да се връща на израза, който е сигнализирал за изключение, след като е предприето правилно действие от обработващия изключението. Системата за състоянието е обобщена, т.е. в манипулатора за несериозните състояния (повтарящите се изключения) е възможно да се скача на предефинирани точки за рестарт, които стоят между сигналния израз и обработката на условието. Такива точки са функции затворени в някоя лексикална среда, които позволяват на програмиста да поправи тази среда преди да излезе от обработката на състоянието.

Точки за рестарт, отделен механизъм от политика (policy)[редактиране | редактиране на кода]

Обработката на състояние, освен това, осигурява разделение между механизъм и политика. Рестартиращите точки предоставят различни възможности за възстановяване от грешки, но не избират кой механизъм е подходящ в дадена ситуация. Това е задължение на обработката на състояния, което (тъй като се намира в по-долно ниво код), има достъп до по-широк изглед.

Пример: да приемем, че има функция библиотека, чиято цел е да обработи единичен запис в syslog файл. Какво трябва тази функция да направи, ако записът е деформиран? Няма единен верен отговор, защото една и съща библиотека може да бъде използвана в програми с различни цели. В интерактивен браузър на лог файлове правилното действие може да бъде изобразяване на необработения запис, за да може потребителят да го види, но в автоматизирана програма за обработка на логове, правилното решение може да бъде просто да се покаже записът с нулева стойност за нечетимите полета, но да се прекрати операцията с грешка, ако прекалено много записи са деформирани.

Така да се каже, на въпроса може да се отговори, само от гледна точка на по-широките цели на програмата, които не са известни на функцията на библиотеката. С не по-малко значение, излизането със съобщение за грешка, е рядко правилният отговор. Затова вместо излизането със съобщение за грешка, функцията може да установи рестартиращи точки, които осигуряват много начини за продължение, например да прескочи записа в лога, да предостави нулева стойност или такава по подразбиране на нечетимите полета, да поиска липсващите стойности от потребителя и др. Предлаганите точки за рестартиране представляват възможните механизми за възстановяване от грешка. 

Криейтив Комънс - Признание - Споделяне на споделеното Лиценз за свободна документация на ГНУ Тази страница частично или изцяло представлява превод на страницата „Exception handling“ в Уикипедия на английски. Оригиналният текст, както и този превод, са защитени от Лиценза „Криейтив Комънс - Признание - Споделяне на споделеното“, а за съдържание, създадено преди юни 2009 година — от Лиценза за свободна документация на ГНУ. Прегледайте историята на редакциите на оригиналната страница, както и на преводната страница. Вижте източниците на оригиналната статия, състоянието ѝ при превода и списъка на съавторите.