ÄLÄ sieppaa sitä poikkeusta! ÄLÄ!

Heh, luin taas artikkelin joka muistutti yhdestä asiasta jota olen alkanut koulutuksissa painottamaan itsekin nähtyäni paljon huonoa koodia jossa on ongelmia. Se liittyy virheiden käsittelyyn.

Java- kielihän jakaa virhetilanteiden aiheuttamat poikkeukset kahteen kategoriaan: checked ja unchecked. Checked poikkeukset ovat niitä joita on pakko käsitellä, ja niitä käytetään osiin kuten tiedostonkäsittely, verkko- ja tietokanta-ohjelmointi, jne. Ja kun ohjelmoija törmää Eclipsessä käännövirheeseen joka muistuttaa lisäämään try-catch lohkon näiden osien ympärille, mitä tapahtuu? Useat klikkaavat pikanappeja jotka luovat geneerisen try-catch lohkon joka tulostaa virheen konsoliin/logiin. Jota ei kukaan ehkä koskaan lue. Ja ei tee mitään muuta. Kuten esim. kerro käyttäjälle virheestä, hälyytä ylläpitoa korjaamaan se, tms. Käsitelty poikkeus ei myöskään keskeytä suoritusta vaan jatkaa virheellisessä tilassa eteenpäin ellei sitten itse kirjoita lisää koodia joka estää sen. Tämä poikii lisää mehukkaita virhetiloja ja vaikeuttaa alkuperäisen ongelman löytämistä.

Tuossa mm. esimerkki pätkästä joka löytyy googlesta haulla jdbc sample code:

try{
  // Tässä riveittäin virheellistä ja surkeasti kirjoitettua
  //  JDBC moskaa joka voi heittää poikkeuksia.
}catch(SQLException se){
      //Handle errors for JDBC
      se.printStackTrace();
   }catch(Exception e){
      //Handle errors for Class.forName
      e.printStackTrace();
   }finally{
      //finally block used to close resources
      try{
         if(stmt!=null)
            stmt.close();
      }catch(SQLException se2){
      }// nothing we can do
      try{
         if(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }//end finally try
   }//end try

Ennenaikainen poikkeusten käsittely on perisynti. Ja tätä syntiä on harrastettu koodipohjissa siellä sun täällä. Pahin mitä olen nähnyt on poikkeuskäsittely jossa käsittelylohko on jätetty tyhjäksi. Toisin sanoen: Piilota kaikki ongelmat mitä ilmenee. Piilota ne niin syvälle että kukaan ei mitenkään pysty ymmärtämään mikä virheen alunperin aiheutti. Klassinen esimerkki asuntolainavetoisesta kehityksestä (MDD).

try {
  // ota yhteys kantaan, hae tietoja, muodosta tiedoista json-sanoma,
  // ja palauta se kutsujalle
} catch (Exception ex) { }

Jep jep. Tuollaisen näin tuotantojärjestelmässä ja valitettavasti en vain yhtä kertaa. Konsulttina saan usein kutsun katsomaan kun koodit eivät toimi tai projekti ei etene. Valitettavasti se tarkoittaa että näen pääosin surkeaa koodia ja harvoin kaunista, hienoa ja hyvin toimivaa. Se alkaa masentamaan aikaa myöden. 😉

Miten sitten poikkeukset tulee käsitellä? Yksinkertaista, älä käsittele. Älä käsittele. Ja sitten käsittele kun on sen aika. Sen aika on kun voit:

– toipua virheestä automaattisesti

– viestiä käyttäjän aiheuttaman virheen käyttäjälle ja opastaa käyttäjää korjaamaan sen ja pääsemään takaisin tuottavalle polulle

– viestiä odottamattoman virheen käyttäjälle ja tallettaa sen yksityiskohdat logiin, ja hälyttää jonkun korjaamaan virheen

Käsittelyn voi tehdä heti jos pystyt toipumaan virheestä. Jos et pysty toipumaan, virhe kannattaa yleensä kuplittaa käyttöliittymään/palvelurajapintaan asti ja tuoda selkeästi käyttäjän tietoon ja tarvittaessa myös ylläpidon (jos käyttäjä syötti emailiosoitteensa väärin siitä ei ehkä kannata spammata logia – tai ehkä kannattaisi, virhevetoinen ui suunnittelu voisi myös olla kova juttu).

Jos rajapinnoissa näkyvät poikkeukset aiheuttavat sinulle mielipahaa, voit tietysti ottaa poikkeukset kiinni ja kietaista ne uusien poikkeusten sisään ja heittää ne uudelleen, siten että poikkeus kuplii edelleen kunnes se voidaan käsitellä, Tästä ollaan kuitenkin montaa mieltä – se mm. aiheuttaa suorituskykymielessä jonkun verran enemmän rasitusta. Toisaalta, suorituskykyyn kannattaa koskea vasta kun tarpeen, ensisijainen prioriteetti tulee aina olla koodin selkeys ja käytettävyys. Avainkysymys: Haluatko nähdä SQLException tyypin rajapinnoissa? Onko suorituskyky vai arkkitehtuurin puhtaus tärkeämpää?

Mutta nyrkkisääntö siis pelkistetysti tässä: Poikkeus kertoo virheestä. Voit käsitellä poikkeuksen kun pystyt korjaamaan virheen.

Muitakin poikkeussyntejä löytyy. Catch-all lohko eli lohko jossa sieppaat kiinni Throwable tai Exception kantatyypin (eli väität kykeneväsi käsittelemään kaikki poikkeukset) on useimmiten huono idea, koska se piilottaa yksityiskohtia, ja saattaa kätkeä ongelmia. Mutta kaikille säännöille on poikkeuksensa, tätäkin voi käyttää rajoitetusti, esim. kun kaikki spesifiset poikkeukset on jo käsitelty ja ollaan UI kerroksessa tekemässä viimeistä huolittelua. Lopputuotteessa et toki halua asiakkaan näkevän Java nullpointerexception tai sockettimeoutexception viestejä vaan: Järjestelmässä on tilapäinen käyttöhäiriö. Yritä uudelleen hetken päästä tai ota yhteys ylläpitoon osoitteessa blahblah@blahblah.com.

Yllätys, yllätys… Modernit ratkaisut käyttävät yhä vähemmän checked exception tyyliä ja yhä enemmän runtime-exception tyyliä. Esim. EJB 3, Spring, Scala, Groovy, ovat siirtyneet pois mallista jossa poikkeukset on pakko käsitellä. Saa-käsitellä tyyppiset poikkeukset ovat mukavampia koska koodaajat eivät kirjoittele boilerplate koodia vaan poikkeukset käsitellään kun siitä on jotain hyötyä. Joka on juuri kuten sen olisi alunperin pitänytkin olla.

Tässä tämän viikon hajatelmat.

Update: Sivusin aihetta Mortgage-Driven Development ja Refuctoring, aiheeseen liittyvä Matt Wynnen maukas videoesitys löytyy täältä:

http://vimeo.com/39660655

 

Mainokset

Vastaa

Täytä tietosi alle tai klikkaa kuvaketta kirjautuaksesi sisään:

WordPress.com-logo

Olet kommentoimassa WordPress.com -tilin nimissä. Log Out / Muuta )

Twitter-kuva

Olet kommentoimassa Twitter -tilin nimissä. Log Out / Muuta )

Facebook-kuva

Olet kommentoimassa Facebook -tilin nimissä. Log Out / Muuta )

Google+ photo

Olet kommentoimassa Google+ -tilin nimissä. Log Out / Muuta )

Muodostetaan yhteyttä palveluun %s