Programavimas

Ar patikrintos išimtys yra geros, ar blogos?

„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 arba NETIESA 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 vykdomas fgetc () skaityti simbolį iš failo net tada, kai fopen () grįžta NULL. 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ėti metimai 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šmeta IOException, 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ą:

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