Programmering

Er sjekket unntak bra eller dårlig?

Java støtter kontrollerte unntak. Denne kontroversielle språkfunksjonen er elsket av noen og hatet av andre, til det punktet hvor de fleste programmeringsspråk unngår sjekket unntak og støtter bare deres ukontrollerte kolleger.

I dette innlegget undersøker jeg kontroversen rundt sjekket unntak. Jeg introduserer først unntakskonseptet og beskriver kort Java's språkstøtte for unntak for å hjelpe nybegynnere bedre å forstå kontroversen.

Hva er unntak?

I en ideell verden ville dataprogrammer aldri støte på noen problemer: filer ville eksistere når de skulle eksistere, nettverkstilkoblinger ville aldri stengt uventet, det ville aldri være et forsøk på å påkalle en metode via nullreferansen, heltall-divisjon-by -nullforsøk ville ikke forekomme, og så videre. Imidlertid er vår verden langt fra ideell; disse og andre unntak til ideell programutførelse er utbredt.

Tidlige forsøk på å gjenkjenne unntak inkluderte retur av spesielle verdier som indikerer feil. For eksempel C-språket fopen () funksjonen returnerer NULL når den ikke kan åpne en fil. Også PHP-er mysql_query () funksjonen returnerer FALSK når en SQL-feil oppstår. Du må se andre steder etter den faktiske feilkoden. Selv om det er enkelt å implementere, er det to problemer med denne "retur spesielle verdien" -tilnærmingen til å gjenkjenne unntak:

  • Spesielle verdier beskriver ikke unntaket. Hva gjør NULL eller FALSK veldig slemt? Alt avhenger av forfatteren av funksjonaliteten som returnerer spesialverdien. Videre, hvordan forholder du en spesiell verdi til programmets sammenheng når unntaket skjedde slik at du kan presentere en meningsfylt melding til brukeren?
  • Det er for lett å ignorere en spesiell verdi. For eksempel, int c; FIL * fp = fopen ("data.txt", "r"); c = fgetc (fp); er problematisk fordi dette C-kodefragmentet kjøres fgetc () å lese et tegn fra filen selv når fopen () returnerer NULL. I dette tilfellet, fgetc () vil ikke lykkes: vi har en feil som kan være vanskelig å finne.

Det første problemet løses ved å bruke klasser for å beskrive unntak. En klassens navn identifiserer unntakstypen, og feltene samler passende programkontekst for å bestemme (via metodeanrop) hva som gikk galt. Det andre problemet løses ved å la kompilatoren tvinge programmereren til enten å svare direkte på et unntak eller indikere at unntaket skal håndteres andre steder.

Noen unntak er veldig alvorlige. For eksempel kan et program forsøke å tildele noe minne når det ikke er ledig minne tilgjengelig. Grenseløs rekursjon som tømmer stabelen er et annet eksempel. Slike unntak er kjent som feil.

Unntak og Java

Java bruker klasser for å beskrive unntak og feil. Disse klassene er organisert i et hierarki som er forankret i java.lang. kastbar klasse. (Grunnen til Kastbar ble valgt for å gi navn til denne spesielle klassen, vil snart bli tydelig.) Direkte under Kastbar er java.lang. unntak og java.lang.Error klasser, som beskriver henholdsvis unntak og feil.

For eksempel inkluderer Java-biblioteket java.net.URISyntaxException, som strekker seg Unntak og indikerer at en streng ikke kunne analyseres som en Uniform Resource Identifier referanse. Noter det URISyntaxException følger en navngivningskonvensjon der et unntaksklassenavn slutter med ordet Unntak. En lignende konvensjon gjelder feilklassnavn, for eksempel java.lang.OutOfMemoryError.

Unntak er underklassert av java.lang.RuntimeException, som er superklassen med de unntakene som kan kastes under normal bruk av Java Virtual Machine (JVM). For eksempel, java.lang.ArithmeticException beskriver aritmetiske feil som forsøk på å dele heltall med heltall 0. Også, java.lang.NullPointerException beskriver forsøk på å få tilgang til objektmedlemmer via nullreferansen.

En annen måte å se på RuntimeException

Avsnitt 11.1.1 i Java 8 Language Specification sier: RuntimeException er superklassen med alle unntakene som kan kastes av mange grunner under uttrykksevaluering, men hvorfra gjenoppretting fremdeles er mulig.

Når et unntak eller feil oppstår, et objekt fra det aktuelle Unntak eller Feil underklasse opprettes og sendes til JVM. Handlingen med å passere objektet er kjent som kaster unntaket. Java tilbyr kaste uttalelse for dette formålet. For eksempel, kaste ny IOException ("kan ikke lese filen"); skaper et nytt java.io.IO Unntak objekt som er initialisert til den angitte teksten. Dette objektet blir deretter kastet til JVM.

Java tilbyr prøve uttalelse for avgrensning av kode som unntak kan kastes fra. Denne påstanden består av nøkkelord prøve etterfulgt av en avstivningsavgrenset blokk. Følgende kodefragment demonstrerer prøve og kaste:

prøv {metode (); } // ... ugyldig metode () {kast ny NullPointerException ("litt tekst"); }

I dette kodefragmentet kommer utførelsen inn i prøve blokkerer og påkaller metode(), som kaster en forekomst av NullPointerException.

JVM mottar kastbar og søker opp metoden-samtalestakken for en behandler å håndtere unntaket. Unntak ikke avledet fra RuntimeException blir ofte håndtert; runtime unntak og feil blir sjelden håndtert.

Hvorfor feil blir sjelden håndtert

Feil håndteres sjelden fordi det ofte ikke er noe som et Java-program kan gjøre for å gjenopprette feilen. For eksempel når ledig minne er oppbrukt, kan et program ikke tildele ekstra minne. Imidlertid, hvis tildelingsfeilen skyldes å holde på mye minne som skal frigjøres, kan en overleverer forsøke å frigjøre minnet med hjelp fra JVM. Selv om en behandler kan synes å være nyttig i denne feilkonteksten, kan det hende at forsøket ikke lykkes.

En handler er beskrevet av en å fange blokken som følger prøve blokkere. De å fange block gir en overskrift som viser hvilke typer unntak den er forberedt på å håndtere. Hvis kastekastens type er inkludert i listen, sendes kastekasten til å fange blokkere hvis kode kjøres. Koden svarer på årsaken til feil på en slik måte at programmet får fortsette, eller muligens avsluttes:

prøv {metode (); } fange (NullPointerException npe) {System.out.println ("forsøk på å få tilgang til objektmedlem via null referanse"); } // ... ugyldig metode () {kast ny NullPointerException ("litt tekst"); }

I dette kodefragmentet har jeg lagt til en å fange blokker til prøve blokkere. Når NullPointerException gjenstand kastes fra metode(), JVM finner og overfører kjøring til å fange blokk, som sender ut en melding.

Endelig blokkerer

EN prøve blokk eller den endelige å fange blokken kan følges av en endelig blokk som brukes til å utføre oppryddingsoppgaver, for eksempel å frigjøre ervervede ressurser. Jeg har ikke noe mer å si om endelig fordi det ikke er relevant for diskusjonen.

Unntak beskrevet av Unntak og dens underklasser bortsett fra RuntimeException og dens underklasser er kjent som sjekket unntak. For hver kaste uttalelse, undersøker kompilatoren unntakets objekttype. Hvis typen indikerer avkrysset, sjekker kompilatoren kildekoden for å sikre at unntaket håndteres i metoden der den kastes eller blir erklært håndtert lenger opp i metoden-samtalestakken. Alle andre unntak er kjent som ukontrollerte unntak.

Java lar deg erklære at et avkrysset unntak håndteres lenger opp i metoden-samtalestakken ved å legge til en kaster klausul (nøkkelord kaster etterfulgt av en komma-avgrenset liste over merkede unntaksklassenavn) til en metodetittel:

prøv {metode (); } fange (IOException ioe) {System.out.println ("I / O-feil"); } // ... ugyldig metode () kaster IOException {kast ny IOException ("litt tekst"); }

Fordi IO Unntak er en sjekket unntakstype, må kastede forekomster av dette unntaket håndteres i metoden der de kastes eller erklæres håndtert lenger opp i metoden-samtalestakken ved å legge til en kaster paragraf til hver berørte metodes overskrift. I dette tilfellet, a kaster IOException klausul er vedlagt metode()header. Den kastede IO Unntak objekt sendes til JVM, som lokaliserer og overfører kjøring til å fange behandler.

Argumenterer for og mot sjekket unntak

Sjekket unntak har vist seg å være veldig kontroversielt. Er de et godt språk eller er de dårlige? I denne delen presenterer jeg sakene for og mot kontrollerte unntak.

Sjekket unntak er bra

James Gosling opprettet Java-språket. Han inkluderte sjekket unntak for å oppmuntre til opprettelse av mer robust programvare. I en 2003-samtale med Bill Venners påpekte Gosling hvor enkelt det er å generere buggy-kode på C-språket ved å ignorere de spesielle verdiene som returneres fra Cs filorienterte funksjoner. For eksempel prøver et program å lese fra en fil som ikke ble åpnet for lesing.

Alvoret med å ikke sjekke returverdier

Ikke å sjekke returverdier kan virke som ingen stor sak, men denne slurv kan ha konsekvenser for liv eller død. Tenk for eksempel på slik buggy-programvare som styrer missilstyringssystemer og førerløse biler.

Gosling påpekte også at programmeringskurs for høyskoler ikke diskuterer feilhåndtering tilstrekkelig (selv om det kan ha endret seg siden 2003). Når du går gjennom college og gjør oppgaver, ber de deg bare om å kode opp den ene sanne veien [for utførelse der svikt ikke er en vurdering]. Jeg har absolutt aldri opplevd et høyskolekurs der feilhåndtering i det hele tatt ble diskutert. Du kommer ut av college og det eneste du har måttet forholde deg til er den ene sanne veien.

Å bare fokusere på den ene sanne banen, latskap eller en annen faktor har resultert i at det er skrevet mye buggy-kode. Sjekket unntak krever at programmereren vurderer kildekodens design og forhåpentligvis oppnår mer robust programvare.

Sjekket unntak er dårlig

Mange programmerere hater sjekket unntak fordi de er tvunget til å håndtere API-er som overbruker dem eller spesifikt angir merkede unntak i stedet for ukontrollerte unntak som en del av kontraktene. For eksempel blir en metode som angir en sensorverdi sendt et ugyldig tall og kaster et avkrysset unntak i stedet for en forekomst av det ukontrollerte java.lang.IllegalArgumentException klasse.

Her er noen andre grunner til at du ikke liker sjekket unntak; Jeg har hentet dem fra Slashdots intervjuer: Spør James Gosling om Java og Ocean Exploring Robots-diskusjonen:

  • Avmerkede unntak er enkle å ignorere ved å kaste dem som RuntimeException tilfeller, så hva er poenget med å ha dem? Jeg har mistet antall ganger jeg har skrevet denne kodeblokken:
    prøv {// gjør ting} fangst (Irriterende kontrollertException e) {kast ny RuntimeException (e); }

    99% av tiden kan jeg ikke gjøre noe med det. Til slutt gjør blokkering nødvendig opprydding (eller i det minste de burde).

  • Avmerkede unntak kan ignoreres ved å svelge dem, så hva er vitsen med å ha dem? Jeg har også mistet antall ganger jeg har sett dette:
    prøv {// gjør ting} fangst (AnnoyingCheckedException e) {// gjør ingenting}

    Hvorfor? Fordi noen måtte takle det og var lat. Var det feil? Sikker. Skjer det? Absolutt. Hva om dette i stedet var et ukontrollert unntak? Appen hadde nettopp dødd (som er å foretrekke fremfor å svelge et unntak).

  • Merkede unntak resulterer i flere kaster klausulerklæringer. Problemet med avmerkede unntak er at de oppfordrer folk til å svelge viktige detaljer (nemlig unntaksklassen). Hvis du velger å ikke svelge den detaljene, må du fortsette å legge til kaster erklæringer på tvers av hele appen din. Dette betyr 1) at en ny unntakstype vil påvirke mange funksjonssignaturer, og 2) du kan gå glipp av en spesifikk forekomst av unntaket du faktisk vil-fange (si at du åpner en sekundærfil for en funksjon som skriver data til en Den sekundære filen er valgfri, så du kan ignorere feilene, men fordi signaturen kaster IO Unntak, det er lett å overse dette).
  • Sjekket unntak er egentlig ikke unntak. Tingen med sjekket unntak er at de egentlig ikke er unntak av den vanlige forståelsen av konseptet. I stedet er de alternative returverdier for API.

    Hele ideen med unntak er at en feil kastet et sted nedover samtalekjeden kan boble opp og håndteres av kode et sted lenger oppe, uten at den mellomliggende koden trenger å bekymre seg for det. Avmerkede unntak krever derimot at alle nivåer av kode mellom kasteren og fangeren erklærer at de vet om alle former for unntak som kan gå gjennom dem. Dette er egentlig lite annerledes i praksis enn om avmerkede unntak bare var spesielle returverdier som innringeren måtte sjekke for.

I tillegg har jeg møtt argumentet om at applikasjoner må håndtere et stort antall avmerkede unntak som genereres fra flere biblioteker de får tilgang til. Imidlertid kan dette problemet overvinnes gjennom en smart utformet fasade som utnytter Javas kjedede unntaksanlegg og unntakskasting for å redusere antall unntak som må håndteres, samtidig som det opprinnelige unntaket som ble kastet bevares.

Konklusjon

Er sjekket unntak bra eller dårlig? Med andre ord, burde programmerere bli tvunget til å håndtere sjekket unntak eller få muligheten til å ignorere dem? Jeg liker ideen om å håndheve mer robust programvare. Imidlertid tror jeg også at Java sin unntakshåndteringsmekanisme må utvikle seg for å gjøre det mer programmerervennlig. Her er et par måter å forbedre denne mekanismen på:

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