JE_12 - Minimalizujte přístupnost tříd a členů

Jednoznačně nejdůležitějším faktorem, který odlišuje dobře navržený modul od špatně navrženého, je stupe�, do jakého daný modul skrývá svá interní data a další implementační podrobnosti před dalšími moduly. Dobře navržený modul skrývá všechny své implemntační detaily a jasně odděluje své API od své implementace. Moduly pak vzájemně komunikují jen prostřednictvím svých API a nevědí nic o dalších vnitřních procesech. Tento koncept označovaný za skrývání informací neboli zapouzdřování je jedním ze základních principů návrhu softwaru.

Skrývání informací je důležité z mnoha důvodů. Většina pramení ze skutečnosti, že ve svém důsledku odděluje moduly tvořící systém a umož�uje vývoj, testování, optimalizování, používání, chápání a změnu jednotlivých modulů. Tím se zrychluje vývojsystému, protože moduly lze vyvíjet paralelně. Snižují se náklady na správu, protože moduly lze rychle pochopit a odladit a není zapotřebí obávat se poškození dalších modulů. Třebaže skrývání informací samo o sobě nezajišťuje dobrou výkonnost, umož�uje ladění výkonnosti. Jakmile je systém kompletní a profilování odhalí, které moduly způsobují problémy s výkonem (rada 37), lze tyto moduly optimalizovat, aniž by došlo k narušení korektnosti ostatních modulů. Skrývání informací zvyšuje opakovanou použitelnost softwaru, protože jednotlivé moduly na sobě vzájemně nazávisejí a často se projeví jako užitečné i v jiných kontextech, než pro které byly vyvíjeny. Skrývání informací snižuje riziko při vytváření rozsáhlých systémů; jednotlivé moduly mohou být úspěšné, i když systém samotný úspěšný není.

Programovací jazyk Java má mnoho prostředků napomáhajících skrývání informací. Jedním takovým prostředekm je mechanismus řízení přístupu, který určuje přístupnost tříd, rozhraní a členů. Přístupnost entity je určena místem její deklarace a modifikátory přístupu (private, protected a public), pokud se nějaké vyskytují, v její deklaraci. Správné používání těchto modifikátorů je pro skrývání informací zásadní.

základní pravidlo zní, že byste měli každou třídu a každého člena učinit co nejméně přístupným. Jinými slovy, měli byste používat nejnižší možnou úrove� přístupu potřebnou ke správnému fungování software. který píšete.

U tříd a rozhraní nejvyšší úrovně (nevnořených), existují jen dvě možné úrovně přístupu: přátelský a veřejný. Deklarujete-li třídu nebo rozhraní nejvyšší úrovně s modifikátoem public, pak bude veřejná; jinak bude přátelská (soukromá pro balíček neboli s výchozím přístupem). Je-li možné učinit třídu nebo rozhraní nejvyšší úrovně přátelskými, pak by měly takovými být. Když je učiníte přátelskými, činíte je součástí implementace balíčku a nikoli jeho exportovaným API. V další verzi softwaru je můžete změnit, nahradit nebo odstranit a nemusíte se přitom obávat poškození již existujících klientů. Učiníte-li je veřejnými, budete je muset podporovat už navěky, aby zůstala zachována kompatibilita.

Pokud se přátelská třída (nebo rozhraní) používá pouze v jediné třídě, zvažte její (jeho) změnu na soukromou vnořenou třídu (nebo rozhraní) třídy, v níž se používá (rada 18). Tím se dále omezuje její (jeho) přístupnost. To však není tak důležité, jako učinit zbytečně veřejno třídu přátelskou, protože přátelská třída je již součástí implemntace balíčku a nikoli jeho API.

Členové (atributy, meotdy, vnořené třídy a vnořená rozhraní) mohou mít čtyři různé úrovně přístupu, které jsou v pořadí přístupnosti uvedené dále:

Po pečlivém navržení veřejného API své třídy byste měli reflexivně učinit všechny ostatní členy soukromými. Pouze pokud jiná třída v témže balíčku opravdu potřebuje přistupovat k nějakému členu, pak byste měli odstranit modifikátor private a učinit daného člena přátelským. Když zjistíte, že tak činíte často, měli byste se znovu pdívat na návrh svého systému a zjistit, zda by vám jiné rozčlenění neposkytlo třídy, které od sebe budou lépe vzájemně oddělené. Ještě si řekněme, že jak soukromí tak i přátelští členové jsou součástí implementace třídy a obvykle nemají vliv na její exportované API. Tyto atributy však mohou "pronikat" do exportovaného API, jestliže daná třída implementuje Serializable (rada 54, rada 55).

V případě členů veřejných tříd dojde k obrovskému nárůstu přístupnosti, když se úrove� přístupnsti změní z přátelské na chráněnou. Chráněný člen je součástí exportovaného API třídy a musíte jej navždy podporovat. Navíc představuje chráněný člen exportované třídy veřejný závazek k implementačnímu detailu (rada 15). Chránění členové jsou však zapotřebí jen výjimečně.

Existuje jedno pravidlo, které omezuje vaši schopnost snižovat přístupnost metod. Pokud nějaká metoda překrývá metodu nadtřídy, nemůže mít v podtřídě nižší úrove� přístupu, než má v nadtřídě. To je zapotřebí zajistit proto, aby byla instance dané podtřídy použitelná, kdekoli se může vyskytnout instance příslušné nadtřídy. Porušíte-li toto pravidlo, pak při pokusu o překlad podtřídy vygeneruje kompilátor chybové hlášení. Speciálním případem tooto pravidla je situace, kdy nějaká třída implementuje určité rozhraní - pak musejí být všechny metody třídy, které se v tomto rozhraní rovněž vyskytují deklarovány jako veřejné. Je to proto, že všechny metody v rozhraní jsou implicitně veřejné.

Veřejné třídy by měly mít jen výjimečně, pokud vůbec někdy, veřejné atributy (oproti veřejným metodám). Je-li nějaký atribut nefinální nebo je finálním odkazem na měnitelný objekt, pak se učiněním daného atributu veřejným vzdáváte možnosti omezit hodnoty, které v něm mohou být uloženy. Také se vzdáváte možnosti vykonat nějakou akci, když dojde ke změně tohoto atributu. Jednoduchým důsledkem je to, že třídy s veřejnými měnitelnými atributy nejsou zabezpečeny z hlediska vláken. I když je atribut finální a neodkazuje se na měnitelný objekt, učiníte-li jej veřejným, vzdáváte se možnosti přechodu na novou interní reprezentaci dat, v níž by tento atribut neexistoval.

Existuje jedna výjimka z pravidla, že veřejné třídy by neměly mít veřejné atributy. Třídy mají možnost vystavovat konstanty prostřednictvím veřejných statických finálních atributů. Konvečně mají takové atributy názvy z velkých písmen, přičemž slova jsou oddělena podtržítky (rada 38). Je důležité, aby tyto atributy obsahovaly buď primitivní hodnoty nebo odkazy na neměnitelné objekty (rada 13). Finální atribut obsahující odkaz na měnitelný objekt má všechny nevýhody nefinálního aributu. Třebaže odkaz samotný nelze změnit, lze změnit odkazovaný objekt, což může mít katastrofické následky.

Nezapome�te, že pole nenulové délky je vždy měnitelné, takže je téměř vždy nesprávné mít veřejný statický finální atribut typu pole. Má-li nějaká třída takový atribut, budou moci klient měnit obsah daného pole. To je častý zdroj bezpečnostních děr:

// Možná bezpečnostní díra
public static final Type[] VALUES = { . . . };

Takové veřejné pole je zapotřebí nahradit soukromým polem a veřejným neměnitelným seznamem:

private static final Type[] PRIVATE_VALUES = { . . . };
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

Vyžadujete-li zabezpečení typů během kompilování a jste-li ochotni tolerovat snížení výkonnosti, můžete nahradit veřejný atribut typu pole veřejnou metodou, která vracíkopii soukromého pole:

private static final Type[] PRIVATE_VALUES = { . . . };

public static final Type[] values() {
	return (Type[]) PRIVATE_VALUES.clone();
}

Závěrem lze říci, že byste vždy měli mezovat přístupnost do co největší možné míry. Po pečlivém návrhu minimálního veřejného API byste měli zabránit všem zbloudilým třídám, rozhraním nebo členům v tom, aby se staly součástí tohoto API. S výjimou veřejných statických finálních atributů by neměly mít veřejné třídy žádné veřejné atributy. Zajistěte, aby byly objekty odkazované veřejnými statickými finálními atributy neměnitelné.