Jesu li provjerene iznimke dobre ili loše?

Java podržava provjerene iznimke. Ovu kontroverznu jezičnu značajku neki vole, a drugi mrze do te mjere da većina programskih jezika izbjegava provjerene iznimke i podržava samo svoje neprovjerene kolege.

U ovom postu istražujem kontroverzu oko provjerenih iznimaka. Prvo uvodim koncept iznimki i ukratko opisujem jezičnu podršku za iznimke Javi kako bih pomogao početnicima da bolje razumiju kontroverzu.

Koji su izuzeci?

U idealnom svijetu računalni programi nikada ne bi naišli na bilo kakav problem: datoteke bi postojale kad bi trebale postojati, mrežne veze se nikad ne bi neočekivano zatvorile, nikada ne bi bilo pokušaja pozivanja metode putem nulte reference, integer-division-by -nero pokušaji se ne bi dogodili, i tako dalje. Međutim, naš je svijet daleko od ideala; ove i druge iznimke od idealnog izvršavanja programa su široko rasprostranjene.

Rani pokušaji prepoznavanja iznimaka uključivali su vraćanje posebnih vrijednosti koje ukazuju na neuspjeh. Na primjer, fopen()funkcija jezika C vraća se NULLkada ne može otvoriti datoteku. Također, mysql_query()funkcija PHP-a vraća se FALSEkada se dogodi SQL neuspjeh. Stvarni kod kvara morate potražiti drugdje. Iako je jednostavan za provedbu, postoje dva problema s ovim pristupom prepoznavanja iznimaka "vratite posebnu vrijednost":

  • Posebne vrijednosti ne opisuju iznimku. Što znači NULLili FALSEstvarno znači? Sve ovisi o autoru funkcionalnosti koja vraća posebnu vrijednost. Nadalje, kako povezati posebnu vrijednost s kontekstom programa kada se dogodila iznimka kako biste korisniku mogli predstaviti značajnu poruku?
  • Prejednostavno je zanemariti posebnu vrijednost. Na primjer, int c; FILE *fp = fopen("data.txt", "r"); c = fgetc(fp);problematično je jer se ovaj fragment C koda izvršava fgetc()za čitanje znaka iz datoteke čak i kada se fopen()vraća NULL. U ovom slučaju fgetc()neće uspjeti: imamo programsku pogrešku koju je možda teško pronaći.

Prvi se problem rješava korištenjem klasa za opisivanje iznimaka. Ime klase identificira vrstu iznimke i njena polja agregiraju odgovarajući programski kontekst za utvrđivanje (putem poziva metode) što je pošlo po zlu. Drugi je problem riješen tako što će prevodilac natjerati programera da ili izravno odgovori na iznimku ili da naznači da se s iznimkom treba raditi negdje drugdje.

Neke su iznimke vrlo ozbiljne. Na primjer, program može pokušati dodijeliti nešto memorije kad nema slobodne memorije. Drugi primjer je bezgranična rekurzija koja iscrpljuje hrpu. Takve su iznimke poznate kao pogreške .

Iznimke i Java

Java koristi klase za opisivanje iznimaka i pogrešaka. Ti su razredi organizirani u hijerarhiju koja je ukorijenjena u java.lang.Throwableklasi. (Razlog zašto Throwableje izabran u ime ove posebne klase će postati očito uskoro.) Odmah ispod Throwablesu java.lang.Exceptioni java.lang.Errorklase koje opisuju iznimke i pogreške, odnosno.

Na primjer, Java knjižnica uključuje java.net.URISyntaxException, koja se proširuje Exceptioni ukazuje da se niz ne može raščlaniti kao referenca jedinstvenog identifikatora resursa. Imajte na umu da URISyntaxExceptionslijedi konvencija imenovanja u kojoj naziv klase iznimke završava riječju Exception. Slična se konvencija odnosi na nazive klasa pogrešaka, kao što su java.lang.OutOfMemoryError.

Exceptionje podklasirano sa java.lang.RuntimeException, što je superklasa onih iznimki koje se mogu izbaciti tijekom normalnog rada Java virtualnog stroja (JVM). Na primjer, java.lang.ArithmeticExceptionopisuje aritmetičke neuspjehe kao što su pokušaji dijeljenja cijelih brojeva s cijelim brojem 0. Također, java.lang.NullPointerExceptionopisuje pokušaje pristupa članovima objekta putem null reference.

Drugi način gledanja RuntimeException

Odjeljak 11.1.1 Specifikacije jezika Java 8 glasi: RuntimeExceptionje superklasa svih iznimki koje se mogu iz mnogih razloga izbaciti tijekom ocjene izraza, ali od kojih je oporavak i dalje moguć.

Kada se dogodi iznimka ili pogreška, objekt iz odgovarajućeg Exceptionili Errorpodklase kreira se i prosljeđuje JVM-u. Čin dodavanja predmeta poznat je kao bacanje iznimke . Java daje throwizjavu u tu svrhu. Na primjer, throw new IOException("unable to read file");kreira novi java.io.IOExceptionobjekt koji je inicijaliziran za navedeni tekst. Taj se objekt naknadno baca JVM-u.

Java nudi tryizjavu za razgraničenje koda iz kojeg se može izuzeti. Ova se izjava sastoji od ključne riječi trynakon koje slijedi blok razgraničen zagradama. Sljedeći fragment koda prikazuje tryi throw:

try { method(); } // ... void method() { throw new NullPointerException("some text"); }

U ovom fragmentu koda, izvršenje ulazi u tryblok i poziva method(), što baca instancu NullPointerException.

JVM prima Throwable i traži hrpu metoda-poziv za rukovatelj za obradu iznimku. RuntimeExceptionČesto se rješavaju iznimke koje nisu izvedene iz ; Iznimke i pogreške tijekom izvođenja rijetko se obrađuju.

Zašto se pogreške rijetko rješavaju

Rijetko se rješavaju pogreške jer Java program često ne može učiniti da se oporavi od pogreške. Na primjer, kada se iscrpi slobodna memorija, program ne može dodijeliti dodatnu memoriju. Međutim, ako je neuspjeh dodjele posljedica zadržavanja puno memorije koju treba osloboditi, hander može pokušati osloboditi memoriju uz pomoć JVM-a. Iako se čini da je rukovatelj korisnim u ovom kontekstu pogreške, pokušaj možda neće uspjeti.

Rukovatelj je opisan catchblokom koji slijedi tryblok. catchBlok pruža zaglavlje koje nabraja vrste iznimaka da je spreman nositi. Ako je vrsta bacanja uključena na popis, bacanje se prenosi catchbloku čiji se kôd izvršava. Kôd reagira na uzrok kvara na takav način da uzrokuje nastavak programa ili ga možda prekida:

try { method(); } catch (NullPointerException npe) { System.out.println("attempt to access object member via null reference"); } // ... void method() { throw new NullPointerException("some text"); }

U ovom fragmentu koda dodao sam catchblok trybloku. Kada se NullPointerExceptionobjekt izbaci method(), JVM locira i prosljeđuje izvršavanje catchbloku koji izbacuje poruku.

Napokon blokovi

tryBlok ili njegov završni catchblok može se slijedi finallyblok koji se koristi za obavljanje pročišćavanja zadatke, kao što su ispuštanje stečena sredstva. Nemam više o čemu reći, finallyjer to nije bitno za raspravu.

Iznimke opisane od Exceptioni njegove podrazrede osim RuntimeExceptioni podklase poznate su kao provjerene iznimke . Za svaku throwizjavu, prevodilac ispituje vrstu objekta iznimke. Ako tip označava provjereno, kompajler provjerava izvorni kôd kako bi osigurao da se iznimka obrađuje u metodi gdje je bačena ili je deklarirana da se obrađuje dalje u steku poziva-metoda. Sve ostale iznimke poznate su kao neprovjerene iznimke .

Java vam omogućuje da izjavite da se s provjerenom iznimkom postupa dalje prema steku poziva metoda dodavanjem throwsklauzule (ključne riječi koju throwsslijedi popis provjerenih naziva klasa provjerenih iznimki) u zaglavlje metode:

try { method(); } catch (IOException ioe) { System.out.println("I/O failure"); } // ... void method() throws IOException { throw new IOException("some text"); }

Budući da IOExceptionje provjerena vrsta iznimke, bačenim primjercima ove iznimke mora se rukovati u metodi gdje se bacaju ili se mora proglasiti da se njima rukuje dalje nizom poziva poziva metode dodavanjem throwsklauzule zaglavlju svake pogođene metode. U ovom slučaju, zaglavlju throws IOExceptionse dodaje klauzula method(). Bačeni IOExceptionobjekt prosljeđuje se JVM-u, koji pronalazi i prenosi izvršenje catchobrađivaču.

Argumentiranje za i protiv provjerenih iznimaka

Checked exceptions have proven to be very controversial. Are they a good language feature or are they bad? In this section, I present the cases for and against checked exceptions.

Checked exceptions are good

James Gosling created the Java language. He included checked exceptions to encourage the creation of more robust software. In a 2003 conversation with Bill Venners, Gosling pointed out how easy it is to generate buggy code in the C language by ignoring the special values that are returned from C's file-oriented functions. For example, a program attempts to read from a file that wasn't successfully opened for reading.

The seriousness of not checking return values

Not checking return values might seem like no big deal, but this sloppiness can have life-or-death consequences. For example, think about such buggy software controlling missile guidance systems and driverless cars.

Gosling also pointed out that college programming courses don't adequately discuss error handling (although that may have changed since 2003). When you go through college and you're doing assignments, they just ask you to code up the one true path [of execution where failure isn't a consideration]. I certainly never experienced a college course where error handling was at all discussed. You come out of college and the only stuff you've had to deal with is the one true path.

Focusing only on the one true path, laziness, or another factor has resulted in a lot of buggy code being written. Checked exceptions require the programmer to consider the source code's design and hopefully achieve more robust software.

Checked exceptions are bad

Many programmers hate checked exceptions because they're forced to deal with APIs that overuse them or incorrectly specify checked exceptions instead of unchecked exceptions as part of their contracts. For example, a method that sets a sensor's value is passed an invalid number and throws a checked exception instead of an instance of the unchecked java.lang.IllegalArgumentException class.

Here are a few other reasons for disliking checked exceptions; I've excerpted them from Slashdot's Interviews: Ask James Gosling About Java and Ocean Exploring Robots discussion:

  • Checked exceptions are easy to ignore by rethrowing them as RuntimeException instances, so what's the point of having them? I've lost count of the number of times I've written this block of code:
    try { // do stuff } catch (AnnoyingcheckedException e) { throw new RuntimeException(e); }

    99% of the time I can't do anything about it. Finally blocks do any necessary cleanup (or at least they should).

  • Checked exceptions can be ignored by swallowing them, so what's the point of having them? I've also lost count of the number of times I've seen this:
    try { // do stuff } catch (AnnoyingCheckedException e) { // do nothing }

    Why? Because someone had to deal with it and was lazy. Was it wrong? Sure. Does it happen? Absolutely. What if this were an unchecked exception instead? The app would've just died (which is preferable to swallowing an exception).

  • Checked exceptions result in multiple throws clause declarations. The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException, it's easy to overlook this).
  • Checked exceptions are not really exceptions. The thing about checked exceptions is that they are not really exceptions by the usual understanding of the concept. Instead, they are API alternative return values.

    The whole idea of exceptions is that an error thrown somewhere way down the call chain can bubble up and be handled by code somewhere further up, without the intervening code having to worry about it. Checked exceptions, on the other hand, require every level of code between the thrower and the catcher to declare they know about all forms of exception that can go through them. This is really little different in practice to if checked exceptions were simply special return values which the caller had to check for.

Uz to, naišao sam na argument oko toga da aplikacije moraju obrađivati ​​velik broj provjerenih iznimaka generiranih iz više knjižnica kojima pristupaju. Međutim, ovaj se problem može prevladati pametno dizajniranom fasadom koja koristi Javinu mogućnost lančanog izuzimanja i ponovnog uvođenja iznimki kako bi se u velikoj mjeri smanjio broj iznimki s kojima se mora postupati, a sačuvati izvornu izuzetak koja je bačena.

Zaključak

Jesu li provjerene iznimke dobre ili su loše? Drugim riječima, bi li programere trebalo prisiliti da postupaju s provjerenim iznimkama ili im pružiti priliku da ih ignoriraju? Sviđa mi se ideja o primjeni robusnijeg softvera. Međutim, također mislim da se Java-ov mehanizam za rukovanje iznimkama mora razvijati kako bi ga učinio prilagodljivijim programerima. Evo nekoliko načina za poboljšanje ovog mehanizma: