Java atbalsta pārbaudītus izņēmumus. Vienu šo pretrunīgi vērtēto valodas funkciju mīl un citi ienīst līdz vietai, kur lielākā daļa programmēšanas valodu izvairās no pārbaudītiem izņēmumiem un atbalsta tikai viņu nepārbaudītos kolēģus.
Šajā amatā es aplūkoju strīdus par pārbaudītajiem izņēmumiem. Vispirms es iepazīstinu ar izņēmumu jēdzienu un īsi raksturoju Java valodas atbalstu izņēmumiem, lai palīdzētu iesācējiem labāk izprast domstarpības.
Kādi ir izņēmumi?
Ideālā pasaulē datorprogrammām nekad nerastos nekādas problēmas: faili pastāvētu, ja tiem vajadzētu pastāvēt, tīkla savienojumi nekad netiktu negaidīti aizvērti, nekad netiktu mēģināts izsaukt metodi, izmantojot nulles atsauci, vesels skaitlis dalījums -nulle mēģinājumi nenotiks utt. Tomēr mūsu pasaule nebūt nav ideāla; šie un citi izņēmumiem ideāla programmas izpilde ir plaši izplatīta.
Pirmie mēģinājumi atpazīt izņēmumus ietvēra īpašu vērtību atgriešanu, kas norāda uz neveiksmi. Piemēram, C valodas fopen ()
funkcija atgriežas NULL
kad tā nevar atvērt failu. Arī PHP mysql_query ()
funkcija atgriežas FALSE
kad rodas SQL kļūme. Faktiskais kļūmes kods ir jāmeklē citur. Lai arī šo pieeju "atgriezt īpašu vērtību" ir viegli īstenot, izņēmumu atpazīšanai ir divas problēmas:
- Īpašās vērtības neapraksta izņēmumu. Ko dara
NULL
vaiFALSE
tiešām nozīmē? Viss ir atkarīgs no tā funkcionalitātes autora, kurš atgriež īpašo vērtību. Turklāt kā jūs saistāt īpašu vērtību ar programmas kontekstu, kad radās izņēmums, lai jūs varētu lietotājam sniegt nozīmīgu ziņojumu? - Ir pārāk viegli ignorēt īpašu vērtību. Piemēram,
int c; FILE * fp = fopen ("dati.txt", "r"); c = fgetc (fp);
ir problemātiska, jo izpilda šo C koda fragmentufgetc ()
lai lasītu rakstzīmi no faila pat tad, jafopen ()
atgriežasNULL
. Šajā gadījumā,fgetc ()
neizdosies: mums ir kļūda, kuru, iespējams, ir grūti atrast.
Pirmā problēma tiek atrisināta, izmantojot klases, lai aprakstītu izņēmumus. Klases nosaukums identificē izņēmuma veidu, un tā lauki apkopo atbilstošu programmas kontekstu, lai noteiktu (izmantojot metodes izsaukumus), kas notika nepareizi. Otra problēma tiek atrisināta, liekot kompilatoram piespiest programmētāju vai nu tieši atbildēt uz izņēmumu, vai arī norādīt, ka izņēmums ir jārisina citur.
Daži izņēmumi ir ļoti nopietni. Piemēram, programma var mēģināt piešķirt nedaudz atmiņas, ja nav pieejama brīva atmiņa. Vēl viens piemērs ir neierobežota rekursija, kas iztukšo kaudzi. Šādi izņēmumi ir pazīstami kā kļūdas.
Izņēmumi un Java
Java izmanto klases, lai aprakstītu izņēmumus un kļūdas. Šīs klases ir sakārtotas hierarhijā, kas sakņojas java.lang.Metams
klasē. (Iemesls, kāpēc Metams
tika izvēlēts, lai nosauktu šo īpašo klasi, kļūs redzams drīz.) Tieši zem tā Metams
ir java.lang. Izņēmums
un java.lang.Error
klases, kurās attiecīgi aprakstīti izņēmumi un kļūdas.
Piemēram, Java bibliotēka ietver java.net.URISyntaxException
, kas stiepjas Izņēmums
un norāda, ka virkni nevarēja parsēt kā vienota resursa identifikatora atsauci. Pieraksti to URISyntaxException
seko nosaukšanas kārtībai, kurā izņēmuma klases nosaukums beidzas ar vārdu Izņēmums
. Līdzīga kārtība attiecas uz kļūdu klases nosaukumiem, piemēram, java.lang.OutOfMemoryError
.
Izņēmums
apakšklasē java.lang.RuntimeException
, kas ir to izņēmumu virsklase, kurus var izmest Java Virtual Machine (JVM) normālas darbības laikā. Piemēram, java.lang.ArithmeticException
apraksta aritmētiskās kļūmes, piemēram, mēģinājumus sadalīt veselus skaitļus ar veselu skaitli 0. java.lang.NullPointerException
apraksta mēģinājumus piekļūt objekta dalībniekiem, izmantojot nulles atsauci.
Vēl viens veids, kā paskatīties RuntimeException
Java 8 valodas specifikācijas 11.1.1. Sadaļā noteikts: RuntimeException
ir visu to izņēmumu virsklase, kurus var izteikt daudzu iemeslu dēļ izteiksmes novērtēšanas laikā, bet no kuriem atgūšana joprojām ir iespējama.
Kad rodas izņēmums vai kļūda, objekts no atbilstošā Izņēmums
vai Kļūda
apakšklase tiek izveidota un nodota JVM. Objekta nodošanas akts ir pazīstams kā izmetot izņēmumu. Java nodrošina mest
paziņojums šim nolūkam. Piemēram, mest jaunu IOException ("nevar lasīt failu");
rada jaunu java.io.IOException
objekts, kas tiek inicializēts norādītajā tekstā. Šis objekts pēc tam tiek izmests JVM.
Java nodrošina mēģiniet
paziņojums par koda norobežošanu, no kura var tikt izņēmums. Šis paziņojums sastāv no atslēgvārda mēģiniet
kam seko ar breketēm norobežots bloks. Šis koda fragments parāda mēģiniet
un mest
:
izmēģināt {metodi (); } // ... void metode () {mest jaunu NullPointerException ("kāds teksts"); }
Šajā koda fragmentā izpilde ievada mēģiniet
bloķēt un izsaukt metode ()
, kas izmet instanci NullPointerException
.
JVM saņem izmetams un metodzvanu kaudzītē meklē a apdarinātājs rīkoties ar izņēmumu. Izņēmumi, kas nav iegūti no RuntimeException
bieži tiek apstrādāti; izpildlaika izņēmumi un kļūdas tiek reti apstrādāti.
Kāpēc kļūdas tiek apstrādātas reti?
Ar kļūdām rīkojas reti, jo bieži vien Java programma neko nevar darīt, lai atgūtu kļūdu. Piemēram, kad ir iztukšota brīva atmiņa, programma nevar piešķirt papildu atmiņu. Tomēr, ja sadalīšanas kļūme ir saistīta ar daudzas atmiņas turēšanu, kas būtu jāatbrīvo, pārraugs varētu mēģināt atbrīvot atmiņu ar JVM palīdzību. Lai arī apstrādātājs var izrādīties noderīgs šajā kļūdas kontekstā, mēģinājums var neizdoties.
Apstrādātāju apraksta a noķert
bloks, kas seko mēģiniet
bloķēt. The noķert
bloks nodrošina galveni, kurā uzskaitīti to izņēmumu veidi, kurus tā ir gatava apstrādāt. Ja metamā tips ir iekļauts sarakstā, metamais tiek nodots noķert
bloks, kura kods tiek izpildīts. Kods reaģē uz kļūmes cēloni tā, lai liktu programmai turpināt vai, iespējams, pārtraukt:
izmēģināt {metodi (); } catch (NullPointerException npe) {System.out.println ("mēģinājums piekļūt objekta dalībniekam, izmantojot nulles atsauci"); } // ... void method () {mest jaunu NullPointerException ("kāds teksts"); }
Šajā koda fragmentā esmu pievienojis a noķert
bloķēt mēģiniet
bloķēt. Kad NullPointerException
objekts tiek izmests no metode ()
, JVM atrod un nodod izpildi noķert
bloks, kas izvada ziņojumu.
Visbeidzot bloķē
A mēģiniet
bloku vai tā galīgo noķert
blokam var sekot a beidzot
bloks, kas tiek izmantots tīrīšanas uzdevumu veikšanai, piemēram, iegūto resursu atbrīvošanai. Man vairs nav ko teikt beidzot
jo tas nav saistīts ar diskusiju.
Izņēmumi, kurus aprakstījis Izņēmums
un tās apakšklases, izņemot RuntimeException
un tā apakšklases ir pazīstamas kā pārbaudīti izņēmumi. Katram mest
paziņojumu, sastādītājs pārbauda izņēmuma objekta tipu. Ja tips norāda atzīmēts kā pārbaudīts, sastādītājs pārbauda pirmkodu, lai pārliecinātos, ka izņēmums tiek apstrādāts metodē, kurā tas tiek izmests, vai tiek deklarēts, ka tas tiek apstrādāts augšup pa metodi izsaukuma steku. Visi pārējie izņēmumi ir pazīstami kā nepārbaudīti izņēmumi.
Java ļauj paziņot, ka pārbaudītais izņēmums tiek apstrādāts tālāk pa metodi-izsaukuma kaudzīti, pievienojot a metieni
klauzula (atslēgvārds metieni
seko ar komatu atdalīts pārbaudīto izņēmumu klases nosaukumu saraksts) metodes galvenei:
izmēģināt {metodi (); } catch (IOException ioe) {System.out.println ("I / O kļūda"); } // ... void method () throws IOException {mest jaunu IOException ("kāds teksts"); }
Tā kā IOException
ir pārbaudīts izņēmuma veids, šī izņēmuma gadījumi ir jāapstrādā metodē, kurā tie tiek izmesti vai jāpaziņo, ka tie tiek apstrādāti augšup pa metodi-izsaukuma steku, pievienojot metieni
klauzula katras ietekmētās metodes galvenē. Šajā gadījumā a iemet IOException
klauzula ir pievienota metode ()
galvene. Iemests IOException
objekts tiek nodots JVM, kas atrod un pārsūta izpildi noķert
apdarinātājs.
Argumentējot par un pret pārbaudītiem izņēmumiem
Pārbaudītie izņēmumi ir izrādījušies ļoti pretrunīgi. Vai tās ir labas valodas īpašības vai sliktas? Šajā sadaļā es izklāstu gadījumus par un pret pārbaudītiem izņēmumiem.
Pārbaudītie izņēmumi ir labi
Džeimss Goslings izveidoja Java valodu. Viņš mudināja pārbaudīt izņēmumus, lai veicinātu stabilākas programmatūras izveidi. 2003. gada sarunā ar Billu Vennersu Goslings norādīja, cik viegli ir ģenerēt kļūdainu kodu C valodā, ignorējot īpašās vērtības, kas tiek atgrieztas no C faila orientētajām funkcijām. Piemēram, programma mēģina lasīt no faila, kas nav veiksmīgi atvērts lasīšanai.
Atgriešanās vērtību nepārbaudīšanas nopietnība
Atgriešanās vērtību nepārbaudīšana varētu šķist nekas īpašs, taču šai paviršībai var būt sekas dzīvībai vai nāvei. Piemēram, padomājiet par šādu kļūdainu programmatūru, kas kontrolē raķešu vadības sistēmas un automašīnas bez vadītāja.
Goslings arī norādīja, ka koledžas programmēšanas kursos netiek pietiekami apspriesta kļūdu apstrāde (lai gan tas, iespējams, ir mainījies kopš 2003. gada). Kad jūs ejat cauri koledžai un veicat uzdevumus, viņi vienkārši lūdz jūs kodēt vienu patieso ceļu [izpilde, kur neveiksme nav apsvērums]. Es noteikti nekad neesmu pieredzējis koledžas kursu, kur vispār tika apspriesta kļūdu apstrāde. Jūs iznākat no koledžas, un vienīgais, ar ko jums nācās saskarties, ir vienīgais patiesais ceļš.
Koncentrēšanās tikai uz vienu patieso ceļu, slinkumu vai citu faktoru ir radījusi daudz kļūdainu kodu. Pārbaudītie izņēmumi liek programmētājam apsvērt avota koda dizainu un, cerams, panākt stabilāku programmatūru.
Pārbaudītie izņēmumi ir slikti
Daudzi programmētāji ienīst pārbaudītos izņēmumus, jo viņi ir spiesti rīkoties ar API, kas tos pārmērīgi izmanto vai nepareizi norāda pārbaudītos izņēmumus, nevis kā daļu no līgumiem. Piemēram, metodei, kas nosaka sensora vērtību, tiek nodots nederīgs skaitlis un tiek pārbaudīts izņēmums, nevis pārbaudītā gadījuma eksemplārs. java.lang.IllegalArgumentException
klasē.
Šeit ir daži citi iemesli, kāpēc nepatika pārbaudītie izņēmumi; Esmu tos izvilkis no Slashdot intervijām: Jautājiet Džeimsam Goslingam par Java un Ocean Exploring Robots diskusiju:
- Pārbaudītos izņēmumus ir viegli ignorēt, atkārtoti ierakstot tos kā
RuntimeException
gadījumi, tad kāda jēga no tiem? Esmu zaudējis skaitu reižu, kad esmu uzrakstījis šo koda bloku:mēģini {// do stuff} noķert (AnnoyingcheckedException e) {mest jaunu RuntimeException (e); }
99% gadījumu es neko nevaru darīt. Visbeidzot, bloki veic nepieciešamo tīrīšanu (vai vismaz vajadzētu).
- Pārbaudītos izņēmumus var neņemt vērā, norijot tos, tad kāda jēga no tiem? Esmu zaudējis arī to reižu skaitu, kad esmu to redzējis:
izmēģiniet {// do stuff} catch (AnnoyingCheckedException e) {// nedarīt neko}
Kāpēc? Jo kādam ar to bija jātiek galā un viņš bija slinks. Vai tas bija nepareizi? Protams. Vai tas notiek? Pilnīgi. Kā būtu, ja tā vietā būtu nepārbaudīts izņēmums? Lietotne būtu tikko nomirusi (kas ir labāk, nekā norīt izņēmumu).
- Pārbaudītie izņēmumi rada vairākus
metieni
klauzulas deklarācijas. Pārbaudīto izņēmumu problēma ir tā, ka tie mudina cilvēkus norīt svarīgas detaļas (proti, izņēmuma klasi). Ja jūs izvēlaties nenorīt šo detaļu, jums tas jāturpina papildinātmetieni
deklarācijas visā jūsu lietotnē. Tas nozīmē, 1) ka jauns izņēmuma veids ietekmēs daudz funkciju parakstu, un 2) jūs varat palaist garām konkrētu izņēmuma gadījumu, kuru jūs patiešām vēlaties noķert (teiksim, atverat sekundāro failu funkcijai, kas datus raksta Sekundārais fails nav obligāts, tāpēc jūs varat ignorēt tā kļūdas, bet tāpēc, ka paraksts metIOException
, to ir viegli nepamanīt). - Pārbaudītie izņēmumi patiesībā nav izņēmumi. Pārbaudītie izņēmumi ir tādi, ka tie parasti nav izņēmumi parastajā jēdziena izpratnē. Tā vietā tās ir API alternatīvās atgriešanās vērtības.
Visa izņēmumu ideja ir tāda, ka kļūda, kas tiek izmesta kaut kur zvanu ķēdē, var burbuļot uz augšu un tikt galā ar kodu kaut kur tālāk uz augšu, bez iejaukšanās koda jāuztraucas par to. Savukārt pārbaudītie izņēmumi prasa, lai visos koda līmeņos starp metēju un ķērāju tiktu paziņots, ka viņi zina par visiem izņēmuma veidiem, kas tiem var iziet. Praksē tas tiešām nedaudz atšķiras no tā, ja pārbaudītie izņēmumi bija vienkārši īpašas atgriešanās vērtības, kuras zvanītājam bija jāpārbauda.
Turklāt esmu saskāries ar argumentu, ka lietojumprogrammām ir jāapstrādā liels skaits pārbaudītu izņēmumu, kas tiek ģenerēti no vairākām bibliotēkām, kurām tie piekļūst. Tomēr šo problēmu var pārvarēt, izmantojot gudri izveidotu fasādi, kas izmanto Java ķēdes izņēmuma iespēju un izņēmumu atjaunošanu, lai ievērojami samazinātu izņēmumu skaitu, kas jārīkojas, saglabājot sākotnējo izņēmumu, kas tika izmests.
Secinājums
Vai pārbaudītie izņēmumi ir labi vai slikti? Citiem vārdiem sakot, vai programmētājiem vajadzētu piespiest rīkoties ar pārbaudītiem izņēmumiem vai dot viņiem iespēju tos ignorēt? Man patīk ideja ieviest stingrāku programmatūru. Tomēr es arī domāju, ka Java izņēmumu apstrādes mehānismam ir jāattīstās, lai padarītu to programmētājam draudzīgāku. Šeit ir pāris veidi, kā uzlabot šo mehānismu: