Programiranje

Ali so preverjene izjeme dobre ali slabe?

Java podpira preverjene izjeme. Nekaterim je ta kontroverzna jezikovna značilnost všeč, drugi pa sovražijo do te mere, da se večina programskih jezikov izogiba preverjenim izjemam in podpira samo svoje nepreverjene kolege.

V tem prispevku preučujem polemiko glede preverjenih izjem. Najprej predstavim koncept izjem in na kratko opišem Javino jezikovno podporo izjemam, da bi začetnikom pomagal bolje razumeti spor.

Kaj so izjeme?

V idealnem svetu računalniški programi ne bi nikoli naleteli na kakršne koli težave: datoteke bi obstajale takrat, ko bi morale obstajati, omrežne povezave se ne bi nikoli nepričakovano zaprle, nikoli ne bi bilo poskusa priklicati metode prek ničelne reference, celoštevilčno deljenje-by - nič poskusov se ne bi zgodilo itd. Vendar naš svet še zdaleč ni idealen; te in druge izjeme do idealnega izvajanja programa so zelo razširjeni.

Zgodnji poskusi prepoznavanja izjem so vključevali vračanje posebnih vrednosti, ki kažejo na napako. Na primer jezik C fopen () funkcija vrne NIČ ko ne more odpreti datoteke. Tudi PHP mysql_query () funkcija vrne NAPAKA ko pride do napake SQL. Dejansko kodo napake morate poiskati drugje. Čeprav je enostaven za izvedbo, ima ta pristop k prepoznavanju izjem dve težavi:

  • Posebne vrednosti ne opisujejo izjeme. Kaj je NIČ ali NAPAKA res pomeni? Vse je odvisno od avtorja funkcije, ki vrne posebno vrednost. Poleg tega, kako povežete posebno vrednost s kontekstom programa, ko je prišlo do izjeme, tako da lahko uporabniku predstavite smiselno sporočilo?
  • Preveč enostavno je prezreti posebno vrednost. Na primer, int c; FILE * fp = fopen ("data.txt", "r"); c = fgetc (fp); je problematično, ker se ta fragment kode C izvede fgetc () za branje znaka iz datoteke, tudi kadar fopen () vrne NIČ. V tem primeru, fgetc () ne bo uspelo: imamo napako, ki jo je težko najti.

Prvo težavo rešimo z uporabo razredov za opis izjem. Ime razreda opredeljuje vrsto izjeme in njegova polja združujejo ustrezen programski kontekst za ugotavljanje (s klici metode), kaj je šlo narobe. Druga težava se reši tako, da prevajalnik prisili programerja, da se bodisi neposredno odzove na izjemo bodisi navede, da bo izjemo treba obravnavati drugje.

Nekatere izjeme so zelo resne. Program lahko na primer poskuša dodeliti nekaj pomnilnika, kadar ni na voljo prostega pomnilnika. Drug primer je neomejena rekurzija, ki izčrpa kup. Takšne izjeme so znane kot napake.

Izjeme in Java

Java uporablja razrede za opis izjem in napak. Ti razredi so organizirani v hierarhijo, ki temelji na java.lang.Hitljivo razred. (Razlog zakaj Mečljivo je bil izbran za poimenovanje tega posebnega razreda, bo kmalu razvidno.) Neposredno spodaj Mečljivo so java.lang.Exception in java.lang.Error razredi, ki opisujejo izjeme oziroma napake.

Na primer, knjižnica Java vključuje java.net.URISyntaxException, ki se razteza Izjema in označuje, da niza ni bilo mogoče razčleniti kot referenco enotnega identifikatorja vira. Upoštevajte to URISyntaxException sledi dogovoru o poimenovanju, v katerem se ime razreda izjeme konča z besedo Izjema. Podoben dogovor velja za imena razredov napak, kot je java.lang.OutOfMemoryError.

Izjema je podrazvrščeno z java.lang.RuntimeException, ki je superrazred tistih izjem, ki jih lahko vržemo med običajnim delovanjem Java Virtual Machine (JVM). Na primer, java.lang.ArithmeticException opisuje aritmetične napake, kot so poskusi delitve celih števil na celo število 0. java.lang.NullPointerException opisuje poskuse dostopa do članov objekta prek ničelne reference.

Še en način gledanja RuntimeException

Oddelek 11.1.1 specifikacije jezika Java 8 določa: RuntimeException je superrazred vseh izjem, ki jih lahko med vrednotenjem izraza vržemo iz številnih razlogov, vendar je izterjava morda še vedno mogoča.

Ko pride do izjeme ali napake, pride predmet iz ustreznega Izjema ali Napaka podrazred se ustvari in posreduje JVM. Dejanje podajanja predmeta je znano kot vrgel izjemo. Java ponuja vrgel izjavo za ta namen. Na primer, vrzi nov IOException ("datoteke ni mogoče prebrati"); ustvari novo java.io.IOException objekt, ki je inicializiran v določeno besedilo. Ta predmet se nato vrne v JVM.

Java ponuja poskusite izjava za razmejitev kode, iz katere se lahko vrže izjema. Ta izjava je sestavljena iz ključne besede poskusite čemur sledi blok, ločen z naramnicami. Naslednji fragment kode prikazuje poskusite in vrgel:

poskusite {method (); } // ... void method () {throw new NullPointerException ("nekaj besedila"); }

V tem fragmentu kode izvršitev vnese poskusite blokira in prikliče metoda (), ki vrže primerek NullPointerException.

JVM prejme mečljiv in išče sklad za klic metode za a vodnik za obravnavo izjeme. Izjeme, ki niso izpeljane iz RuntimeException z njimi se pogosto ravna; Izjeme in napake med izvajanjem se redko obravnavajo.

Zakaj se napake redko obravnavajo

Napake se redko obravnavajo, ker program Java pogosto ne more storiti ničesar, da bi se obnovil po napaki. Na primer, ko prosti pomnilnik izčrpa, program ne more dodeliti dodatnega pomnilnika. Če pa je napaka pri dodeljevanju posledica zadrževanja veliko pomnilnika, ki ga je treba sprostiti, lahko hander poskuša osvoboditi pomnilnik s pomočjo JVM. Čeprav se v tem kontekstu napake zdi, da je uporabnik uporaben, poskus morda ne bo uspel.

Vodnika opisuje a ulov blok, ki sledi poskusite blok. The ulov Blok vsebuje glavo, ki navaja vrste izjem, s katerimi je pripravljen. Če je vrsta metanja vključena na seznam, se metanje posreduje datoteki ulov blok, katerega koda se izvede. Koda se odzove na vzrok okvare tako, da povzroči nadaljevanje ali morebitno zaustavitev programa:

poskusite {method (); } catch (NullPointerException npe) {System.out.println ("poskus dostopa do člana predmeta prek ničelne reference"); } // ... void method () {throw new NullPointerException ("nekaj besedila"); }

V ta fragment kode sem dodal ulov blok do poskusite blok. Ko NullPointerException predmet je vržen iz metoda (), JVM najde in prenese izvedbo na ulov blok, ki prikaže sporočilo.

Končno bloki

A poskusite blok ali njegov končni ulov bloku lahko sledi a končno blok, ki se uporablja za izvajanje nalog čiščenja, na primer za sprostitev pridobljenih virov. Nimam več kaj povedati končno ker to ni pomembno za razpravo.

Izjeme, ki jih je opisal Izjema in njegovi podrazredi, razen RuntimeException in njegovi podrazredi so znani kot preverjene izjeme. Za vsakogar vrgel izjava, prevajalnik preuči vrsto predmeta izjeme. Če tip označi, da je preverjeno, prevajalnik preveri izvorno kodo, da zagotovi, da se izjema obravnava v metodi, kjer je vržena, ali je razglašena za nadaljnjo obdelavo klica metode. Vse druge izjeme so znane kot nepreverjene izjeme.

Java vam omogoča, da prijavite, da je preverjena izjema obdelana naprej v skladu s klicem metode tako, da dodate a meti klavzulo (ključna beseda meti čemur sledi seznam preverjenih imen razredov izjem z ločenimi vejicami) v glavo metode:

poskusite {method (); } catch (IOException ioe) {System.out.println ("V / I napaka"); } // ... void metoda () vrže IOException {vrže novo IOException ("nekaj besedila"); }

Ker IOException je preverjena vrsta izjeme, vržene primere te izjeme je treba obdelati v metodi, kjer so vrženi, ali pa jih je treba razglasiti za nadaljnjo obdelavo klica metode, tako da dodate meti v glavo vsake prizadete metode. V tem primeru a vrže IOException je dodana klavzula metoda ()je glava. Vrženi IOException objekt se posreduje JVM, ki najde in prenese izvajanje na ulov vodnik.

Trditev za in proti preverjenim izjemam

Izkazane izjeme so se izkazale za zelo kontroverzne. So dobra jezikovna značilnost ali so slabe? V tem poglavju predstavljam primere za in proti preverjenim izjemam.

Preverjene izjeme so dobre

James Gosling je ustvaril jezik Java. Vključil je preverjene izjeme, da bi spodbudil ustvarjanje močnejše programske opreme. Gosling je v pogovoru z Billom Vennersom leta 2003 poudaril, kako enostavno je ustvariti napačno kodo v jeziku C, tako da ignorira posebne vrednosti, ki jih vrnejo C-jeve datoteke usmerjene funkcije. Program na primer poskuša brati iz datoteke, ki ni bila uspešno odprta za branje.

Resnost nepreverjanja povratnih vrednosti

Nepreverjanje vrednosti vrnitve se morda ne zdi nič hudega, toda ta površnost ima lahko posledice za življenje ali smrt. Na primer, pomislite na takšno programsko opremo, ki nadzoruje sisteme za vodenje raket in avtomobile brez voznika.

Gosling je poudaril tudi, da tečaji univerzitetnega programiranja ne obravnavajo ustrezno obravnave napak (čeprav se je to morda spremenilo od leta 2003). Ko greš na fakulteto in opravljaš naloge, te samo prosijo, da kodiraš eno pravo pot [izvedbe, pri kateri neuspeh ni upoštevan]. Zagotovo nisem nikoli doživel univerzitetnega tečaja, kjer bi se sploh obravnavalo ravnanje z napakami. Prideš s fakultete in edina stvar, s katero si se moral spoprijeti, je resnična pot.

Če se osredotočimo samo na eno pravo pot, lenobo ali drug dejavnik, je bilo napisanih veliko napak. Označene izjeme zahtevajo, da programer upošteva zasnovo izvorne kode in upa, da bo dosegel bolj robustno programsko opremo.

Označene izjeme so slabe

Mnogi programerji sovražijo preverjene izjeme, ker so prisiljeni obravnavati API-je, ki jih prekomerno uporabljajo ali napačno določijo preverjene izjeme namesto nepreverjenih izjem kot del svojih pogodb. Na primer, metoda, ki nastavi vrednost senzorja, se prenese neveljavno število in vrže preverjeno izjemo namesto primerka nepreverjenega java.lang.IllegalArgumentException razred.

Tu je še nekaj razlogov, zakaj ne marajo preverjenih izjem; Izvlekel sem jih iz intervjujev Slashdot: Vprašajte Jamesa Goslinga o razpravi o Java in Ocean Exploring Robots:

  • Preverjene izjeme je enostavno prezreti, če jih znova vržemo kot RuntimeException primeri, torej kakšen smisel imajo ti? Izgubil sem število, kolikokrat sem napisal ta blok kode:
    poskusite {// narediti stvari} catch (NadleženException e) {metati nov RuntimeException (e); }

    99% časa ne morem storiti ničesar glede tega. Končno bloki opravijo potrebno čiščenje (ali pa bi vsaj morali).

  • Preverjene izjeme lahko prezrete tako, da jih pogoltnete, kaj je torej smisel, da jih imamo? Prav tako sem izgubil število, kolikokrat sem to videl:
    poskusite {// do stuff} catch (AnnoyingCheckedException e) {// ne naredite nič}

    Zakaj? Ker se je nekdo moral ukvarjati s tem in je bil len. Je bilo narobe? Seveda. Ali se to zgodi? Vsekakor. Kaj pa, če bi bila to nepreverjena izjema? Aplikacija bi pravkar umrla (kar je boljše kot pogoltniti izjemo).

  • Izbrane izjeme povzročijo več meti izjave. Problem preverjenih izjem je, da spodbujajo ljudi, da pogoltnejo pomembne podrobnosti (in sicer razred izjem). Če se odločite, da teh podrobnosti ne boste pogoltnili, jih morate še naprej dodajati meti izjave v celotni aplikaciji. To pomeni 1) da bo nova vrsta izjeme vplivala na veliko podpisov funkcij in 2) lahko boste zamudili določen primerek izjeme, ki jo dejansko želite ujeti (recimo, da odprete sekundarno datoteko za funkcijo, ki zapisuje podatke v Sekundarna datoteka ni obvezna, zato lahko prezrete njene napake, ampak ker podpis vrže IOException, tega je enostavno spregledati).
  • Označene izjeme v resnici niso izjeme. Stvar pri preverjenih izjemah je, da v resnici niso izjeme po običajnem razumevanju koncepta. Namesto tega gre za nadomestne povratne vrednosti API.

    Celotna ideja izjem je, da se napaka, vržena nekje navzdol po klicni verigi, lahko sproži in jo koda obravnava nekje bolj navzgor, ne da bi jo bilo treba vmesni kodi skrbeti. Po drugi strani preverjene izjeme zahtevajo, da se na vsaki ravni kode med metalcem in lovilcem izjavi, da pozna vse oblike izjem, ki jih lahko preidejo. V praksi se to v resnici malo razlikuje od tega, če bi bile preverjene izjeme le posebne povratne vrednosti, ki jih je moral klicatelj preveriti.

Poleg tega sem naletel na argument, da morajo aplikacije obravnavati veliko število preverjenih izjem, ki so ustvarjene iz več knjižnic, do katerih dostopajo. Vendar je to težavo mogoče odpraviti s pametno zasnovano fasado, ki izkorišča Javino možnost priklenjenih izjem in ponovnega vračanja izjem, da se močno zmanjša število izjem, ki jih je treba obravnavati, hkrati pa ohraniti prvotno izjemo, ki je bila vržena.

Zaključek

Ali so preverjene izjeme dobre ali so slabe? Z drugimi besedami, ali bi morali biti programerji prisiljeni ravnati s preverjenimi izjemami ali jim dati možnost, da jih prezrejo? Všeč mi je ideja o uveljavljanju bolj robustne programske opreme. Vendar pa tudi mislim, da se mora Javin mehanizem za obravnavo izjem uporabljati tako, da postane bolj prijazen programerjem. Tu je nekaj načinov za izboljšanje tega mehanizma:

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