„Java“ palaiko pažymėtas išimtis. Ši prieštaringai vertinama kalbos ypatybė yra vienų pamėgta, o kitų nekenčiama tiek, kad dauguma programavimo kalbų vengia patikrintų išimčių ir palaiko tik jų netikrintus partnerius.
Šiame pranešime nagrinėju ginčus dėl patikrintų išimčių. Pirmiausia pristatau išimčių sąvoką ir trumpai aprašau „Java“ kalbų palaikymą išimtims, kad pradedantieji galėtų geriau suprasti ginčus.
Kokios yra išimtys?
Idealiame pasaulyje kompiuterinės programos niekada nesusidurtų su jokiomis problemomis: failai egzistuotų tada, kai manoma, kad jie egzistuoja, tinklo ryšiai niekada netikėtai neužsidarys, niekada nebus bandoma iškviesti metodo per nulinę nuorodą, sveikasis skaičius padalijimas -nulio bandymai neįvyks ir pan. Tačiau mūsų pasaulis toli gražu nėra idealus; šie ir kiti išimtys idealiam programos vykdymui yra plačiai paplitę.
Ankstyvi bandymai atpažinti išimtis apėmė specialių verčių, rodančių nesėkmę, grąžinimą. Pavyzdžiui, C kalba fopen ()
funkcija grąžinama NULL
kai ji negali atidaryti failo. Be to, PHP mysql_query ()
funkcija grąžinama NETIESA
įvykus SQL gedimui. Tikrojo gedimo kodo turite ieškoti kitur. Nors šis „grąžinti ypatingą vertę“ požiūris yra lengvai įgyvendinamas, atpažįstant išimtis yra dvi problemos:
- Specialios vertės neapibūdina išimties. Ką daro
NULL
arbaNETIESA
tikrai reiškia? Viskas priklauso nuo funkcionalumo, kuris grąžina specialią vertę, autoriaus. Be to, kaip susieti ypatingą vertę su programos kontekstu, kai įvyko išimtis, kad vartotojui galėtumėte pateikti prasmingą pranešimą? - Nepaisyti ypatingos vertybės yra per lengva. Pavyzdžiui,
int c; FILE * fp = fopen ("duomenys.txt", "r"); c = fgetc (fp);
yra problemiška, nes šis C kodo fragmentas vykdomasfgetc ()
skaityti simbolį iš failo net tada, kaifopen ()
grįžtaNULL
. Tokiu atveju,fgetc ()
nepavyks: turime klaidą, kurią gali būti sunku rasti.
Pirmoji problema išspręsta naudojant klases apibūdinant išimtis. Klasės pavadinimas nurodo išimties rūšį, o jos laukai kaupia tinkamą programos kontekstą, kad būtų galima nustatyti (per metodo iškvietimus), kas nutiko. Antroji problema išspręsta kompiliatoriui priverčus programuotoją tiesiogiai reaguoti į išimtį arba nurodyti, kad išimtis turi būti tvarkoma kitur.
Kai kurios išimtys yra labai rimtos. Pvz., Programa gali bandyti skirti šiek tiek atminties, kai laisvos atminties nėra. Dar vienas pavyzdys yra beribis rekursija, išeikvojanti kaminą. Tokios išimtys yra žinomos kaip klaidos.
Išimtys ir Java
„Java“ naudoja klases apibūdinti išimtis ir klaidas. Šios klasės yra suskirstytos į hierarchiją, kurios šaknys yra java.lang.Metamas
klasė. (Priežastis kodėl Metamas
buvo pasirinkta pavadinti šią specialią klasę, paaiškės netrukus.) Tiesiogiai po juo Metamas
yra java.lang. Išimtis
ir java.lang.Klaida
klasės, kuriose aprašomos atitinkamai išimtys ir klaidos.
Pavyzdžiui, „Java“ bibliotekoje yra java.net.URISyntaxException
, kuri tęsiasi Išimtis
ir nurodo, kad eilutės nepavyko išanalizuoti kaip vienodo išteklių identifikatoriaus nuorodos. Prisimink tai URISyntaxException
vadovaujasi vardų suteikimo tvarka, kai išimties klasės pavadinimas baigiasi žodžiu Išimtis
. Panaši sutartis taikoma klaidų klasių pavadinimams, pvz java.lang.OutOfMemoryError
.
Išimtis
yra subklasė java.lang.RuntimeException
, kuris yra superklasė tų išimčių, kurias galima išmesti įprastai veikiant „Java Virtual Machine“ (JVM). Pavyzdžiui, java.lang.ArithmeticException
apibūdina aritmetines nesėkmes, tokias kaip bandymai padalyti sveikus skaičius iš skaičiaus 0. Taip pat java.lang.NullPointerException
aprašo bandymus pasiekti objekto narius per nulinę nuorodą.
Kitas būdas pažvelgti RuntimeException
„Java 8“ kalbos specifikacijos 11.1.1 skirsnyje teigiama: RuntimeException
yra visų išimčių, kurios gali būti išmestos dėl daugelio priežasčių atliekant išraiškos vertinimą, klasė, iš kurios vis tiek įmanoma atsigauti.
Kai atsiranda išimtis ar klaida, objektas iš atitinkamo Išimtis
arba Klaida
subklasė sukuriama ir perduodama JVM. Objekto perdavimas yra žinomas kaip mesti išimtį. „Java“ teikia mesti
pareiškimas šiam tikslui. Pavyzdžiui, mesti naują IOException („negaliu nuskaityti failo“);
sukuria naują java.io.IOException
objektas, kuris inicijuojamas pagal nurodytą tekstą. Šis objektas vėliau išmetamas į JVM.
„Java“ teikia bandyti
sakinys, nurodantis kodą, iš kurio gali būti išimtis. Šis teiginys susideda iš raktinio žodžio bandyti
po to - breketais atskirtas blokelis. Šis kodo fragmentas rodo bandyti
ir mesti
:
išbandyti {metodą (); } // ... void metodas () {mest naują NullPointerException ("šiek tiek teksto"); }
Šiame kodo fragmente vykdymas įveda bandyti
užblokuoti ir iškviesti metodas ()
, kuris išmeta „NullPointerException“
.
JVM gauna mesti ir ieško „method-call“ kamino vedlys tvarkyti išimtį. Išimtys, neišvestos iš RuntimeException
dažnai tvarkomi; vykdymo laiko išimtys ir klaidos yra retai tvarkomos.
Kodėl klaidos tvarkomos retai
Klaidos tvarkomos retai, nes dažnai „Java“ programa negali padaryti, kad atsistatytų po klaidos. Pavyzdžiui, kai laisva atmintis yra išeikvota, programa negali skirti papildomos atminties. Tačiau jei paskirstymo gedimas įvyko dėl to, kad laikėte daug atminties, kuri turėtų būti atlaisvinta, paslaugų teikėjas gali bandyti atlaisvinti atmintį naudodamas JVM. Nors tvarkytojas gali pasirodyti naudingas šiame klaidos kontekste, bandymas gali būti nesėkmingas.
Prižiūrėtoją apibūdina a pagauti
blokas, kuris seka bandyti
blokuoti. pagauti
blokas pateikia antraštę, kurioje išvardyti išimčių tipai, kuriuos ji yra pasirengusi tvarkyti. Jei metimo tipas yra įtrauktas į sąrašą, metamas yra perduodamas pagauti
blokas, kurio kodas vykdomas. Kodas reaguoja į gedimo priežastį taip, kad paskatintų programą tęsti arba galbūt nutraukti:
išbandyti {metodą (); } catch (NullPointerException npe) {System.out.println ("bandymas pasiekti objekto narį per nulinę nuorodą"); } // ... void metodas () {mest naują NullPointerException ("šiek tiek teksto"); }
Šiame kodo fragmente aš pridėjau a pagauti
užblokuoti bandyti
blokuoti. Kai „NullPointerException“
objektas yra išmestas iš metodas ()
, JVM nustato ir perduoda vykdymą pagauti
blokas, kuris išleidžia pranešimą.
Pagaliau blokuoja
A bandyti
blokas arba jo galutinis pagauti
po blokelio gali būti a pagaliau
blokas, naudojamas valymo užduotims atlikti, pavyzdžiui, išleisti įsigytus išteklius. Daugiau neturiu apie ką pasakyti pagaliau
nes tai neaktualu diskusijai.
Išimtys, aprašytos Išimtis
ir jo poklasiai, išskyrus RuntimeException
ir jo poklasiai yra žinomi kaip patikrintos išimtys. Kiekvienam mesti
sakinį, kompiliatorius tiria išimties objekto tipą. Jei tipas rodo pažymėtą, kompiliatorius patikrina šaltinio kodą, kad įsitikintų, ar išimtis yra apdorojama tame metode, kur jis išmetamas, arba yra paskelbta, kad ji bus tvarkoma toliau į viršų metodo iškvietimo krūva. Visos kitos išimtys yra žinomos kaip nepatikrintos išimtys.
„Java“ leidžia jums paskelbti, kad pažymėta išimtis tvarkoma toliau metodų skambučių rietuvėje, pridedant a metimai
sąlyga (raktinis žodis metimai
kableliais atskirtas patikrintų išimčių klasių pavadinimų sąrašas) prie metodo antraštės:
išbandyti {metodą (); } gaudyti (IOException ioe) {System.out.println ("I / O gedimas"); } // ... void metodas () išmeta IOException {meta naują IOException ("šiek tiek teksto"); }
Nes IOException
yra patikrintas išimties tipas, šios išimties atvejai turi būti tvarkomi pagal metodą, kuriame jie yra išmetami arba paskelbiami tvarkomi toliau į viršų metodo iškvietimo kamino, pridedant metimai
kiekvieno paveikto metodo antraštės sąlyga. Šiuo atveju a meta IOException
punktas pridedamas metodas ()
antraštė. Išmestas IOException
objektas perduodamas JVM, kuris suranda ir perkelia vykdymą į pagauti
vedlys.
Argumentavimas už ir prieš patikrintas išimtis
Patikrintos išimtys pasirodė esančios labai prieštaringos. Ar jie yra geri kalbos bruožai, ar jie yra blogi? Šiame skyriuje aš pristatau už ir prieš patikrintas išimtis.
Patikrintos išimtys yra geros
Jamesas Goslingas sukūrė „Java“ kalbą. Jis įtraukė patikrintas išimtis, kad paskatintų kurti tvirtesnę programinę įrangą. 2003 m. Pokalbyje su Billu Vennersu Goslingas pabrėžė, kaip lengva generuoti klaidų kodą C kalba, nepaisant specialių reikšmių, kurios yra grąžintos iš C į failą orientuotų funkcijų. Pavyzdžiui, programa bando skaityti iš failo, kuris nebuvo sėkmingai atidarytas skaityti.
Grąžinimo verčių netikrinimo rimtumas
Gali atrodyti, kad netikrinti grąžinimo verčių nėra nieko blogo, tačiau šis atsainumas gali turėti gyvybės ar mirties pasekmių. Pavyzdžiui, pagalvokite apie tokią bagių programinę įrangą, valdančią raketų valdymo sistemas ir automobilius be vairuotojų.
Goslingas taip pat pabrėžė, kad kolegijų programavimo kursuose nėra tinkamai aptarinėjamas klaidų tvarkymas (nors tai gali pasikeisti nuo 2003 m.). Kai eini per koledžą ir atlieki užduotis, jie tiesiog tavęs paprašo užkoduoti vieną tikrąjį kelią [vykdymą, kai nesėkmė nėra svarstymas]. Aš tikrai niekada nepatyriau kolegijos kursų, kur klaidų tvarkymas apskritai buvo aptartas. Išėjote iš kolegijos ir vienintelis dalykas, su kuriuo teko susidurti, yra vienas tikras kelias.
Sutelkiant dėmesį tik į vieną tikrąjį kelią, tingumą ar kitą veiksnį, buvo parašyta daugybė klaidų kodų. Pažymėtos išimtys reikalauja, kad programuotojas apsvarstytų šaltinio kodo dizainą ir, tikiuosi, pasieks patikimesnę programinę įrangą.
Patikrintos išimtys yra blogos
Daugelis programuotojų nekenčia patikrintų išimčių, nes yra priversti elgtis su API, kurios jas per daug naudoja arba neteisingai nurodo patikrintas išimtis, o ne tikrinamas išimtis kaip savo sutarčių dalį. Pavyzdžiui, metodas, nustatantis jutiklio vertę, perduodamas neteisingam skaičiui ir meta pažymėtą išimtį, o ne pažymėtą java.lang.IllegalArgumentException
klasė.
Štai keletas kitų priežasčių, kodėl nepatinka pažymėtos išimtys; Aš ištraukiau juos iš „Slashdot“ interviu: Klauskite Jameso Goslingo apie „Java“ ir „Ocean Exploring Robots“ diskusijas:
- Pažymėtų išimčių lengva nepaisyti, perrašant jas kaip
RuntimeException
atvejų, tad kokia prasmė juos turėti? Aš praradau skaičių kartų, kai parašiau šį kodo bloką:pabandyk {// do stuff} sugauti (AnnoyingcheckedException e) {mesti naują RuntimeException (e); }
99% laiko aš nieko negaliu padaryti. Galiausiai blokai atlieka bet kokį reikalingą valymą (arba bent jau turėtų).
- Pažymėtų išimčių galima nepaisyti jas prarijus, tad kokia prasmė jas turėti? Taip pat neteko skaičiuoti, kiek kartų tai mačiau:
pabandykite {// do stuff} sugauti (AnnoyingCheckedException e) {// nedaryti nieko}
Kodėl? Nes kažkas turėjo su tuo susidurti ir tingėjo. Ar tai buvo neteisinga? Aišku. Ar taip nutinka? Visiškai. Kas būtų, jei tai būtų nepatikrinta išimtis? Programa būtų ką tik mirusi (geriau, nei nuryti išimtį).
- Pažymėtos išimtys gali būti kelios
metimai
punkto deklaracijos. Pažymėtų išimčių problema yra ta, kad jie skatina žmones nuryti svarbias detales (būtent išimčių klasę). Jei nuspręsite neprarasti šios detalės, turite nuolat pridėtimetimai
deklaracijų visoje jūsų programoje. Tai reiškia, kad 1) naujas išimties tipas paveiks daugybę funkcijų parašų, ir 2) galite praleisti konkretų išimties, kurią iš tikrųjų norite sugauti, egzempliorių (tarkime, atidarote antrinį failą funkcijai, kuri rašo duomenis į failas. Antrinis failas yra neprivalomas, todėl galite nepaisyti jo klaidų, bet dėl to, kad parašas išmetaIOException
, lengva to nepastebėti). - Pažymėtos išimtys iš tikrųjų nėra išimtys. Pažymėtos išimtys yra tai, kad jos nėra išimtys įprastu sąvokos supratimu. Vietoj to, jos yra alternatyvios API grąžinimo vertės.
Visa išimčių idėja yra ta, kad klaidos, išmestos kažkur žemyn skambučių grandine, gali burbuliuoti ir būti tvarkomos kodu kažkur toliau aukštyn, be to, kad įsikišusiam kodui nereikėtų dėl to jaudintis. Kita vertus, pažymėtos išimtys reikalauja, kad kiekvienas metiko ir gaudytojo kodo lygis praneštų, kad žino apie visas išimčių formas, kurios gali joms pasireikšti. Tai praktiškai mažai skiriasi nuo patikrintų išimčių, kurios buvo tiesiog specialios grąžinimo vertės, kurias skambintojas turėjo patikrinti.
Be to, susidūriau su argumentu, kad programos turi tvarkyti daugybę patikrintų išimčių, sugeneruotų iš kelių bibliotekų, prie kurių jos prisijungia. Tačiau šią problemą galima įveikti sumaniai suprojektuotu fasadu, kuris panaudoja „Java“ grandinių išimčių galimybę ir išimčių perrašymą, kad būtų žymiai sumažintas išimčių, kurias reikia tvarkyti, išsaugant pradinę išimtį, kuri buvo padaryta, skaičius.
Išvada
Ar patikrintos išimtys yra geros, ar blogos? Kitaip tariant, ar programuotojai turėtų būti priversti elgtis su patikrintomis išimtimis arba suteikti jiems galimybę jų nepaisyti? Man patinka idėja įdiegti tvirtesnę programinę įrangą. Tačiau aš taip pat manau, kad „Java“ išimčių valdymo mechanizmas turi tobulėti, kad jis taptų patogesnis programuotojams. Štai keli būdai, kaip patobulinti šį mechanizmą: