JE_23 - Kontrolujte platnost parametrů

Metody a konstruktory zavádějí určitá omezení, jaké hodnoty mohou být předány v jejich parametrech. Například není neobvyklé, že indexy musejí být nezáporné a odkazy na objekty nesmějí být null. Všechna taková omezení byste měli jasně dokumentovat a vynutit si je kontrolami na začátku těla dané metody. Jedná se o speciální případ obecného principu - měli byste se snažit odhalovat chyby co nejdříve po jejich vzniku. Pokud to neučiníte, bude méně pravděpodobné odhalení takové chyby a bude také obtížnější odhalit zdroj chyby, kterou jste už detekovali.

Je-li nějaké hodnotě předána neplatná hodnota parametru a daná metoda si kontroluje svůj parametr ještě před vykonáním, rychle a čistě končí příslušnou výjimkou. Pokud si metoda své parametry neověří, pak může dojít k různým věcem. Metoda může selhat s matoucí výjimkou uprostřed svého vykonávání. Ještě hůře, metoda může normálně vrátit, ale tiše vypočítat nesprávný výsledek. Nejhůře, metoda může normálně vrátit, ale přitom ponechat nějaký objekt v narušeném stavu, který pak způsobí chybu v nějakém nesouvisejícím místě kódu někdy v neurčitelné budoucnosti.

U veřejných metod používejte k dokumentování výjimek vyvolaných při narušení omezení hodnot parametrů (rada 44) značku @throws Javadoc. Obvykle půjde o výjimku IllegalArgumentException, IndexOutOfBoundsException nebo NullPointerException (rada 42). Jakmile zdokumentujete omezení týkající se parametrů metody a máte-li rovněž zdokumentované výjimky vyvolané v případě narušení těchto omezení, pak si můžete daná omezení vynutit velmi jednoduše. Zde máme typický příklad:


/**
 * Vrací BigInteger, jehož hodnota je (this mod m). Tato metoda se 
 * liší od metody remainder v tom, že vždy vrací nezáporný BigInteger.
 * 
 * @param	m modulo které musí být kladné.
 * @return	this mod m.
 * @throws	ArithmeticException pokud m <= 0.
 */

public BigInteger mod(BigInteger m) {
	if (m.signum() <= 0)
		throw new ArithmeticException("Modulo není kladné");

	. . . // Vykonat výpočet
}

Pokud se jedná o nějakou neexportovanou metodu, apk vy jako autor balíčku řídíte podmínky, za kterých se daná metoda volá, takže můžete a měli byste zajistit za všech okolností předávání pouze platných hodnot parametrů. Neveřejné metody by tedy obecně měly kontrolovat své parametry pomocí aserce (tvrzení) a nikoli normálními způsoby. Používáte-li verzi platformy, která aserci podporuje (1.4 a novější), pak byste měli použít konstrukci assert; jinak využijte nějaký náhradní mechanismus aserce.

Zejména důležité je kontrolovat platnost parameterů, které daná metoda nepoužívá, ale ukládá je pro další použití. Zvažme například statickou tovární metodu z rady 16, která přebírá pole int a vrací pohled List na toto pole. Pokud by klient této metodě předal null, pak metoda vyvolá výjimku NullPointerException, protože v sobě obsahuje explicitní kontrolu. Jestliže by byla tato kontrola vynechána, tato metoda by vrátila odkaz na nově vytvořenou instanci List, která by okamžitě poté, co by se ji klient pokusil použít, vyvolala výjimku NullPointerException. V té době by však již mohlo být velmi obtížné určit původ dané instance List, což by značně komplikovalo ladění programu.

Konstruktory představují speciální případ principu kontroly platnosti parametrů ukládaných pro pozdější použití. Je velmi důležité kontrolovat platnost parametrů konstruktorů, abyste zabránili vytvoření nějakého objektu, který narušuje invarianty dané třídy.

Pravidlo kontroly parametrů metody před vykonáním jejího výpočtu má své výjimky. Důležitou výjimkou je případ, kdy by byla kontrola platnosti náročná nebo nepraktická a zároveò ke kontrole platnosti dochází implicitně během výpočtu. Zvažme například metodu, která řadí nějaký seznam objektů, například Collections.sort(List). Všechny objekty v seznamu musejí být navzájem porovnatelné. V procesu řazení seznamu se každý objekt v seznamu porovná s nějakým jiným objektem v seznamu. Nejsou-li nějaké objekty vzájemně porovnatelné, pak jedno z porovnání vyvolá výjimku ClassCastException, která je přesně tím, co by měla metoda řazení učinit. Proto nemá velký smysl předběžně testovat, že jsou všechny prvky v seznamu vzájemně porovnatelné. Neuvážené aplikování této techniky však může mít za následek ztrátu atomičnosti selhání (rada 46).

Občas nějaký výpočet implicitně vykonává požadovanou kontrolu platnosti určitého parametru, ale vyvolává nesprávnou výjimku. To znamená, že výjimka, kterou by přirozeně vyvolal výpočet v důsledku nesprávné hodnoty parametru, neodpovídá výjimce, kterou jste v dokumentaci označili. V takovém případě byste měli použít idiom překladu výjimek popsaný v radě 43 a převést přirozenou výjimku na tu správnou.

Nemějte z této rady dojem, že umělá omezení parametrů jsou dobrá. Naopak byste měli vytvářet metody co nejobecnější, pokud je to praktické. Čím méně omezení kladete na parametry, tím lépe, pokud tedy může daná metoda vykonat něco rozumného se všemi hodnotami parametrů, které přijímá. Často jsou však některá omezení daná podstatou dané implementované abstrakce.

Celkově lze říci, že kdykoli píšete nějakou metodu nebo konstruktor, měli byste přemýšlet o omezeních platných pro parametry. tato omezení byste měli zdokumentovat a vynutit si je explicitními kontrolami na začátku těla metody. Je důležité zvyknout si na tento postup; ta trocha práce navíc se vám i s úroky vrátí v okamžiku, kdy poprvé selže kontrola platnosti.