Java a výjimky – pokročilé techniky

21. února 2005

Cílem článku bude nastínit techniky, které práci s výjimkami usnadňují a dovolují nám výjimky efektivně využívat. Ukážeme si řetězení výjimek (exception chaining), rozšíření Java API ve verzi 1.4 a standardní běhové výjimky Javy.

Řetězení výjimek

Potřeba mechanismu řetězení výjimek (exception chaining) vyvstala postupně a jeho podpora se v API Javy objevila ve verzi 1.4. Řetězení výjimek vzniká vložením zachycené výjimky do výjimky jiné. Takto nově vytvořená výjimka se propaguje dále zcela standardním mechanismem. Díky tomuto přístupu nemusíme při rozšíření kódu propagovat nový typ výjimky. Zvažme následující kód:

public void doSomethingFishy() throws HighLevelException{
    try{
        …
        …
    }catch(LowLevelException e){
        throw new HighLevelException();
    }
}

Tento kód nedělá nic prostšího, než překlad kontrolované výjimky na jinou kontrolovanou výjimku, která je definována v rámci metody. Nevýhoda tohoto řešení je zřejmá, přijdeme o všechny důležité informace, které v sobě nese LowLevelException. Díky tomu už nemusí být dohledání příčiny vzniku HighLevelException snadné.

Mnohem lepší by bylo příčinu (LowLevelException) uschovat, respektive zřetězit do HighLevelException. Díky tomuto přístupu vyhovíme kontraktu metody a zároveň neztratíme důležité informace o příčině vzniku HighLevelException. Následující kód ilustruje zřetězení výjimky:

public void doSomethingFishy() throws HighLevelException{
    try{
        …
        …
    }catch(LowLevelException e){
        throw new HighLevelException(e);
    }
}

Řetězení výjimek a podpora v API

Jak bylo zmíněno na začátku článku, Java 1.4 jde tomuto přístupu naproti. API se v případě předka všech výjimek Throwable rozšířilo o následující metody:

  • Throwable(Throwable)
  • Throwable(Throwable, String)
  • getCause()
  • initCause(Throwable)
  • printStackTrace
  • get/setStackTrace()

Jak je vidět, rozšíření se dočkaly konstruktory, které umožňují výjimku přímo zřetězit do nové výjimky a přidat zprávu výjimky. Metoda getCause() vrací zřetězenou výjimku a initCause() naopak dovoluje řetězenou výjimku nastavit.

Rád bych se ještě zmínil o metodě printStackTrace(). Volání PrintStackTrace() způsobí výpis stacktracu i výjimky zřetězené. V našem případě by volání HighLevelException.printStackTrace způsobilo zároveň volání metody LowLevelException.printStackTrace.

Typickým příkladem pro řetězení výjimek je situace, kdy mámě několik relativně vzdálených typů výjimek, které nemůžeme seskupit do objektové hierarchie, jak jsme si naznačili v předchozím článku.

public void configure() throws ConfigurationException{
    try{
        …
        …
    }catch(FileNotFoundException e){
        throw new ConfigurationException(„Konfigurační soubor nenalezen“,e);
    }catch(PropertyMissingException e){
        throw new ConfigurationException(„Chybějící konfigurační atribut“,e);
    }catch(InvalidFormatException e){
        throw new ConfigurationException(„Neznámý formát konfigurace“,e);
    }
}

Jak je vidět z tohoto příkladu, máme tři různé typy výjimek. Tyto výjimky bychom museli deklarovat v kontraktu metody, ale my jsme je zřetězili do obecnější výjimky. V případě zpracování ConfigurationException známe příčinu vzniku (zřetězená výjimka) a máme popisující text. Tyto informace minimálně postačují k dohledání chyby a k rozumné informaci pro uživatele.

Užitečné postupy

Po řetězení výjimek se podíváme na rady, které jsou obecnějšího charakteru.

Poskytujte doplňující informace

Ve většině případů se hodí, pokud výjimka poskytuje další informace, nejen zprávu, kterou by měla obsahovat vždy. Tyto informace nám pomohou k lepšímu odhalení příčiny a zároveň mohou posloužit i uživatelům. Řekněme, že máme výjimku, která informuje o tom, že osoba nebyla nalezena. Taková výjimka by měla obsahovat nejen zprávu o tom, že osoba nebyla nalezena, ale i data, podle kterých byla osoba hledána, například rodné číslo. Tento případ ilustruje následující definice výjimky:

public class OsobaNotFoundException() extends Exception{
    private String rc = null;
    public OsobaNotFoundException(){
        super();
    }
    public OsobaNotFoundException(String message){
        super(message);
    }
    public OsobaNotFoundException(String message, String rc){    
    super(message);
        this.rc = rc;
    }
    public void setRodneCislo(String rc){
        this.rc = rc;
    }
    public void getRodneCislo(){
        return this.rc;
    }
}

Využívejte standardní výjimky

API Javy nabízí několik užitečných běhových výjimek, jejichž použití je vhodné zvážit. Díky obecné známosti jejich významu učiníte váš kód transparentnějším.

  • NullPointerException
  • IllegalStateException
  • UnsupportedOperationException
  • IllegalArgumentException

Výjimku NullPointerException se vyplatí vyhazovat na těch místech, kde potřebujeme pracovat s instancí objektu. Představme si kód, který odebírá uživatele ze skupiny uživatelů podle osobního čísla:

Set group = …
public void removeUser(Integer oscis){
    User u = findUserByOscis(oscis);
    group.remove(u);
}

V tomto případě je vhodné před odebráním uživatele zkontrolovat, jestli není osobní číslo null:

public void removeUser(Integer oscis){
    if(oscis == null){
        throw new NullPointerException(„Osobní číslo uživatele musí být zadáno.“);
    }
    User u = findUserByOscis(oscis);
    group.remove(u);
}

Výjimka IllegalStateException se používá, pokud je volání metody neočekávané a objekt je ve nekonzistentním stavu pro provedení dané operace, například není inicializován.

Výjimku UnsupportedOperationException použijte v případě, že daná operace není podporována. Může se jednat o případ, kdy máme implementované obecné rozhraní a jeho specializované metody nepotřebujeme, a proto při jejich volání vyhazujeme tuto výjimku.

Výjimku IllegalArgumentException použijte v případě neočekávaného argumentu, respektive jeho hodnoty. Pokud například budete mít kód, který nastavuje měsíc, a někdo zadá „14“, vyhoďte tuto výjimku.

Dokumentujte jak kontrolované, tak běhové výjimky

Dokumentací výjimek se nemyslí nic jiného, než komentář na úrovni zdrojového kódu JavaDoc, ze kterého se generuje API dokumentace. Základní pravidlo zní: Dokumentujte příčinu vzniku výjimky.

/**
 * Odebere uživatele určeného osobním číslem.
 * @param oscis osobní číslo uživatele
 * @throws NullPointerException v případě, že je osobní číslo null
 */
public void removeUser(Integer oscis){
    if(oscis == null){
        throw new NullPointerException(„Osobní číslo uživatele musí být zadáno.“);
    }
    User u = findUserByOscis(oscis);
    group.remove(u);
}

Vytvořte si objektovou hierarchii výjimek

Pro každý projekt se snažte vytvořit silnou hierarchii výjimek. Na začátku nadefinujte základní kontrolovanou a běhovou výjimku, od kterých dále odvozujte ostatní výjimky. Díky tomuto přístupu se mohou výjimky „bezbolestně“ propagovat z nižších aplikačních úrovní do vyšších, bez nutnosti změny kódu na vyšších vrstvách.

Závěr s pootevřenými dvířky

Práce s výjimkami, respektive jejich efektivní využití, je běh na dlouhou trať. Cílem této série článků nebylo poskytnout ultimátní řešení všech problémů, které mohou při práci s výjimkami vyvstat. Jsou další mnohem specifičtější témata, o kterých stojí za to přemýšlet – logování, lokalizace nebo vizualizace výjimek, ale o tom snad někdy příště. Doufám, že vám tyto články pomohly pootevřít problematiku výjimek, vyvarovat se špatných návyků a naopak získat obecnou představu o korektních postupech.

Pozn. aut.: Za odbornou a jazykovou korekturu děkuji Tomáši Záluskému.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *