Programmēšana

Vai pārbaudītie izņēmumi ir labi vai slikti?

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 vai FALSE 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 fragmentu fgetc () lai lasītu rakstzīmi no faila pat tad, ja fopen () atgriežas NULL. Š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āt metieni 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 met IOException, 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:

$config[zx-auto] not found$config[zx-overlay] not found