JE_46 - Snažte se dosáhnout atomičnosti selhání

Jakmile nějaký objekt vyvolá výjimku, pak je obecně vhodné, když objekt zůstane v dobře definovaném, využitelném stavu, i když k selhání dojde uprostřed vykonávání nějaké operace. To platí zejména pro kontrolované výjimky, z nichž by se měl volající zotavit. Obecně by mělo volání selhavší metody ponechat objekt ve stavu, v němž se nacházel před daným voláním. O metodě s touto vlastností se říká, že napl�uje atomičnost selhání.

Existuje několik možností dosažení tohoto efektu. Nejjednodušší je vvtvářet neměnitelné objekty (rada 13). Je-li nějaký objekt neměnitelný, pak získáváte atomičnost selhání zadarmo. Pokud nějaká operace selže, nemusí sice dojít k vytvoření nového objektu, nikdy však nezůstane nějaký existující objekt v nekonzistentním stavu, protože stav každého objektu je konzistentní při jeho vytvoření a nelze jej později změnit.

U metod pracujících s měnitelnými objekty se nejčastěji dosahuje atomičnosti selhání kontrolou platnosti parametrů před vykonáním operace (rada 23). Dojde tak k vyvolání výjimky, ještě než se začne s úpravami objektu. Zvažme například metodu Stack.pop z rady 5:

public Object pop() {
	if (size == 0) 
		throw new EmptyStackException();
	Object result = elements[--size];
	elements[size] = null; // Zrušení strých odkazů
	return result;
}

Pokud by byla odstraněna počáteční kontrola velikosti, pak by metoda sice stále vyvolávala výjimku, když by se pokusila převzít prvek z prázdného zásobníku, ponechala by však atribut velikosti v nekonzistentním (záporném) stavu, takže všechna následná volání metod tohoto objektu by selhala. Navíc by byla výjimka vyvolaná metodou pop nevhodná vzhledem k dané abstrakci (rada 43).

Úzce souvisejícím přístupem k dosažení atomičnosti selhání je seřadit výpočet tak, aby se všechny jeho čísti, kde může dojít k selhání, nacházely před všemi částmi, které objekt mění. Tento přístup je přirozeným rozšířením toho předchozího, když nelze argumenty kontrolovat bez vykonání části výpočtu. Zvažme například případ třídy treemap, jejíž prvky jsou řazeny v nějakém pořadí. Aby bylo možné přidat do TreeMap nějaký prvek, musí být daný element typu, jenž může být porovnáván s využitím řazení třídy TreeMap. Pokus o vložení nesprávného typového prvku selže s výjimkou ClassCastException, což bude přirozený výsledek hledání daného prvku ve stromu ještě před jakoukoli úpravou daného stromu.

Třetím a mnohem méně obvyklým přístupem k dosažení atomičnosti selhání je napsat zotavovací kód, který odchytí selhání vzniklé uprostřed nějaké operace a způsobí vrácení objektu do stavu, v němž se nacházel na začátku operace. Tento přístup se používá především ve stálých datových strukturách.

Poslední přístup k zajištění atomičnosti selhání spočívá ve vykonání dané operace na dočasné kopii objektu a náhradě objektu dočasnou kopií, jakmile je operace dokončena. Tento přístup je přirozený, pokud lze výpočet vykonat rychleji, jakmile jsou data uložena v nějaké dočasné datové struktuře. Například Collections.sort před řazením převádí svůj vstupní seznam na pole, čímž snižuje náklady na přístup k prvkům ve vnitřní smyčce řazení. Dělá to kvůli výkonnosti, ale navíc je zajištěno, že vstupní seznam zůstane beze změny, pokud řazení selže.

Třebaže zajištění atomičnosti selhání je obecně žádoucí, není vždy dosažitelné. Pokud se například dvě vlákna pokusí souběžně upravit jeden objekt bez řádné synchronizace, může objekt zůstat v nekonzostentním stavu. Proto by bylo chybné po zachycení výjimky ConcurrentModificationException předpokládat, že je objekt stále použitelný. Chyby jsou (narozdíl od výjimek) obvykle nezotavitelné a metody se ani nemusejí snažit zachovávat atmičnost selhání při vyvolávání chyb.

I v situacích, kdy je atomičnost selhání dosažitelná, nemusí být vždy žádoucí. U některých operací by se výrazně zvýšily náklady nebo jejich složitost. Často však lze dosáhnout atomičnosti selhání zadarmo i snadno, jakmile jste si tohto problému vědomi. Obecně platí, že každá výjimka, která je součástí specifikace metody, by měla zanechat objekt v témže stavu, v jakém byl před zavoláním metody. Je-li toto pravidlo narušeno, měla by dokumentace jasně říkat, v jakém stavu objekt skončí. Bohužel však spousta současné dokumentace API tohoto ideálu nedosahuje.