25. května 2009

JavaDoc nedostatky

V poslední době často pracuji s cizím kódem a je to opravdu zázrak narazit na kvalitně napsaný a okomentovaný kód. Největší problém komentářů je ten, že buď vůbec nejsou a nebo jsou, ale jen papouškují to, co je hned zřejmé ze samotného kódu. O přínosu komentování jsem již psal, dnes bych rád uvedl několik nedostatků v JavaDoc komentářích, na které jsem měl možnost narazit (malá poznámka pro upřesnění - i když často používám slovíčko komentáře, tak v tomto článku dále vždy myslím JavaDoc komentáře, ne komentáře přímo v kódu):

  • míchání češtiny (někdy i s diakritikou) a angličtiny - dříve jsem preferoval pouze angličtinu pro psaní komentářů, ta čeština mi tam prostě neseděla. Dnes jsem spíše pro používání češtiny z těchto důvodů: čeština vždy bude pro nás čitelnější než jakýkoliv jiný jazyk a i psaní komentářů je jednodušší, větší přínos z pohledu poskytované informace. Osobně s angličtinou nemám problém, ale sám jsem zjistil, že v češtině jsem schopen se vyjádřit o dost lépe. Dělám nyní většinou na interních projektech bez zahraničních kolegů, takže není problém s mateřským jazykem. Každopádně když už, tak zvolit jeden jazyk, aby to nevypadalo jako na jednom projektu, kde bylo možné narazit na české, slovenské, anglické a dokonce německé komentáře.

  • zvýraznění první věty komentáře - První věta komentáře má větší význam oproti dalším větám - jednak je to první informace, kterou uživatel čte a jednak je používaná pro stručný popis v přehledu metod. První věta (z pohledu JavaDoc nástroje) končí tečkou, kterou následuje mezera, tabulátor, znak konce řádku a nebo první blokový tag. Takto tedy ne:

    /**
    * <p>
    * If is set, it use special ScrollableResultsDecorator, that enable or
    * disable to add object in final list
    * </p>
    * <h4>NOTE:</h4>
    * <p>
    * Also, it respects the total count of entries that overlap your paged
    * list.
    * </p>
    */


  • @author tag u metod - používat @author tag u metod nemá žádný význam, protože to JavaDoc nástroj nepodporuje.

  • nepopisovat to, co za nás udělá JavaDoc automaticky - Je zbytečné kopírovat komentáře z metod rozhraní do implementace, je zbytečné uvádět implementující třídy rozhraní atd. JavaDoc nástroj má algoritmus na kopírování komentářů z nadřazených tříd a na rozdíl od verze 1.3 se vždy nekopíruje celý JavaDoc komentář (buď celý nebo nic), ale třeba i jen jeho části jako @param, @throws a @return. Někdo to sice nekopíruje, ale používá @inheritDoc a @see, ale to nejsou srovnatelné alternativy. Např. při použití @inheritDoc se kopíruje jen určitá část celého JavaDoc komentáře a tag @see má zcela odlišnou sémantiku. @inheritDoc je vhodné používat, pokud chci rozšířit určitou část dokumentace nadřazené třídy nebo metody.

  • zdokumentovat synchronized a native modifikátory - V JavaDoc API nenajdete informace o tom, zda je metoda synchronizovaná nebo ne (je to implementační záležitost). Proto je nutné tuto informaci uvést v JavaDoc.

  • zbytečně neopakovat význam tagů - Zde mám na mysli toto "@return Returns the statementBuilder." Proč psát 2x, že metoda vrací statementBuilder?

  • místo názvů tříd a metod používat odkazy @link - Hodně často se v komentářích odkazujeme na jiné třídy nebo metody a o dost větší přidaná hodnota bude, když si uživatel mezi jednotlivými komentáři může překlikávat, než aby to musel manuálně hledat. Proto je vhodné používat tagy @link resp. @linkplain.

  • používat @literal nebo @code pro psaní s <> - S příchodem Javy 5 a možností parametrizace může být problém zápisu těchto konstrukcí do JavaDoc komentářů. To stejné platí i o HTML a XML kódu. Než používat &lt; &gt; jako náhradu za <>, tak je lepší použít tagy @literal nebo @code. Tím se výrazně zvýší čitelnost JavaDoc i bez nutnosti zpracování JavaDoc nástrojem. Bohužel tyto tagy nelze použít na víceřádkové ukázky kódu, kde nám jde o přesné zarovnání kódu. Snad v příští verzi, pořád je tedy (bohužel) nutné používat tento způsob:

    * <p>Ukazka konfigurace ve web.xml:
    * <pre>
    &lt;listener&gt;
    &lt;listener-class&gt;com.o2bs.globals.web.common.envconfig.EnvAwareLog4jConfigListener&lt;/listener-class&gt;
    &lt;/listener&gt;
    * </pre>


  • psát přehledné komentáře i bez nutnosti použití JavaDoc nástroje - Hodně lidí hledí zejména na výslednou HTML podobu JavaDoc komentářů a ne na to, jak to vypadá přímo v Java kódu. Je pravda, že výsledná HTML podoba je asi nejdůležitější, ale dost často se prochází čistý Java kód bez IDE (a nebo i v IDE je rychlejší číst JavaDoc bez nutnosti si je zobrazovat v HTML podobě) a pak se i ocení, když to vypadá pěkně bez JavaDoc nástroje. Dle mého názoru se nemá cenu snažit o správně formátované HTML (tedy používání ukončovacích tagů), protože JavaDoc nástroj generuje HTML ve verzi 3.2 (i když hlavičku stránek dává pro HTML 4.0).


Psát správné (krátké, jasné s vypovídající hodnotou) JavaDoc komentáře není vůbec jednoduché a sám se občas nachytám, když se po nějakém čase vracím ke svému kódu. Pro ty, kdo chtějí JavaDoc dotáhnout k dokonalosti, doporučuji referenční příručku k JavaDoc nástroji a dokument s popisem konvence psaní komentářů.

20. května 2009

Spring security namespaces

Koncept "namespaců" resp. možnost vytváření vlastních konfiguračních XML tagů je ve Springu již od verze 2.0 a již je celkem hodně zajímavých tagů - ať už přímo ve Spring frameworku nebo v jiných Spring knihovnách nebo i v knihovnách třetích stran, např. DWR. Cíl je jasný - umožnit jednodušší (= rychlejší, přehlednější, jasnější, ...) konfiguraci Spring beanů.

Spring security přišel s podporou namespaců ve verzi 2.0. Sice moc namespaci nevyužívám (zvyk je železná košile), ale nedalo mi to, abych možnosti namespaců ve Spring security nevyzkoušel na jedné menší testovací aplikaci.

Začal jsem následující magickou ukázkou v dokumentaci:

<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>

<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>

A pak jsem to začal upravovat podle mojí "standardní" konfigurace Spring security. Většinu jsem dokázal pořešit pouze konfigurací na úrovní namespaců, ale ne vždy to bylo dostačující:
  • u LogoutFiltru jsem zvyklý používat vlastní LogoutHandler pro zalogování potřebných informací při odhlášení. Abych mohl použít vlastní handler, tak jsem si musel filter nakonfigurovat standardním způsobem s použitím <custom-filter position="LOGOUT_FILTER"/>

  • podobně u použití vlastního AccessDeniedHandleru. V rámci konfigurace <http> je již implicitní ExceptionTranslationFilter nastaven. Stejně jako u LogoutFiltru jsem musel provést konfiguraci filtru standardní cestou a uvést, že se jedná o vlastní filtr. Pozice vlastního filtru nesmí kolidovat z pozicí implicitního filtru, takže buď je potřeba dát implicitní filtr úplně pryč (neuvádět ho v <http>) a nebo umístit vlastní filtr před/za nějaký již existující, např. u ExceptionTranslationFilteru.

Takto jsem se dostal hodně daleko a konfigurace aplikace již vypadala skoro dle mých představ - uměla vše co jsem požadoval, místy byl zápis opravdu čitelnější a kratší. A i s použitím namespaců jsem mohl být flexibilní, snad všechny implicitní konfigurace jsem mohl nahradit svými, např. použít vlastní AccessDecisionManager

Jen jedna věc mi pořád vadila - při použití <http> se všechny uvedené filtry aplikují na všechny URL. Nemám tedy možnost říci, že na nějaké URL použiji nějaké filtry a na jiné URL zase jiné filtry. Použité filtry je např. vhodné odlišit pro dynamický a statický obsah. Z tohoto důvodu jsem opustil konfiguraci pomocí <http> a použil jsem standardní FilterChainProxy s <filter-chain-map>.

Na začátku jsem měl pár tagů a na konci jsem skončil skoro u "normálního" nastavení bez namespaců. I tak jsem byl velice mile překvapen, jak pěkně to mají navržené a jakou míru flexibility to má. Já Spring Security již celkem znám a i při použití namespaců vím, co se děje pod pokličkou, proto nevím, zda je tato zjednodušená forma konfigurace vhodná i pro začátečníky. Začátek s namespaci bude určitě rychlý, ale v každé aplikaci je potřeba něco nastavit jinak než standardně a pak bude problém.

Pro možnou inspiraci posílám moji výslednou konfiguraci:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"
>

<description>
Konfigurace Spring security.
</description>

<!--
Base filter chain of used filters. Filter's order is important,
more info here http://static.springframework.org/spring-security/site/reference/html/ns-config.html#ns-custom-filters.
-->
<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/img/**" filters="none" />
<security:filter-chain pattern="/css/**" filters="none" />
<security:filter-chain pattern="/js/**" filters="none" />
<security:filter-chain pattern="/accessdenied.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/error*.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/**"
filters="sessionContextIntegrationFilter, logoutFilter, userProfileStubFilter, authProcessingFilter,
userProfileAwareFilter, anonymousProcessingFilter, exceptionTranslationFilter,
filterInvocationInterceptor"
/>
</security:filter-chain-map>
</bean>


<!-- Musi byt, i kdyz neni mozne vyuzivat session. -->
<bean id="sessionContextIntegrationFilter"
class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation" value="true"/>
</bean>

<bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
<!-- URL redirected to after logout -->
<constructor-arg value="/"/>
<constructor-arg>
<list>
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingLogoutHandlerImpl"/>
<bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_spring_security_logout"/>
</bean>

<!-- "natvrdo" nastaveni prihlaseneho uzivatele (pouze pro vyvoj) -->
<bean id="userProfileStubFilter" class="com.o2bs.globals.web.springsecurity.utils.UserProfileStubFilter">
<constructor-arg value="123"/>
<constructor-arg value="bob"/>
<constructor-arg value="heslo"/>
<constructor-arg>
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</constructor-arg>
</bean>

<!-- Processes an authentication form. -->
<bean id="authProcessingFilter"
class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationBlockingProcessingFilter">
<security:custom-filter after="AUTHENTICATION_PROCESSING_FILTER" />
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login.do?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
<property name="mandatoryRoles">
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</property>
</bean>

<bean id="authBlockingManager"
class="com.o2bs.globals.web.springsecurity.auth.blocking.PeriodBlockingManagerMemoryImpl">
</bean>

<bean class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationFailureListener">
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
</bean>

<bean id="userProfileAwareFilter"
class="com.o2bs.globals.web.springsecurity.utils.UserProfileHolderAwareRequestFilter">
</bean>

<!-- I need to use IS_AUTHENTICATED_ANONYMOUSLY - anonymous user has to be handled -->
<bean id="anonymousProcessingFilter"
class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="anonymKey"/>
<property name="userAttribute" value="anonymousUser,ROLE_READ"/>
</bean>

<bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.do"/>
</bean>
</property>
<property name="accessDeniedHandler">
<!-- errorPage neni definovana, protoze se vyhazuje 403 a ta je namapovana ve web.xml -->
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAccessDeniedHandlerImpl"/>
</property>
</bean>

<!-- protect web URIs -->
<bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source>
<security:intercept-url pattern="/create*" access="ROLE_WRITE" />
<security:intercept-url pattern="/**" access="ROLE_READ" />
</security:filter-invocation-definition-source>
</property>
</bean>





<!--
AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were
received (ie a deny vote will be ignored, provided there was at least one grant vote).
In other words principal must have corresponding ROLE and particular level of authentication.
-->
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<!-- class will vote if any ConfigAttribute begins with PERM_. -->
<bean class="org.springframework.security.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<!-- allow attributes IS_AUTHENTICATED_FULLY or IS_..._REMEMBERED or IS_..._ANONYMOUSLY -->
<bean class="org.springframework.security.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>

<!-- There is implicit authenticationManager when namespaces are used. -->
<security:authentication-manager alias="authenticationManager" />

<security:authentication-provider>
<security:user-service>
<security:user name="jimi" password="heslo" authorities="ROLE_READ"/>
<security:user name="bob" password="heslo" authorities="ROLE_READ, ROLE_WRITE"/>
</security:user-service>
</security:authentication-provider>

<security:global-method-security access-decision-manager-ref="accessDecisionManager">
<security:protect-pointcut
expression="execution(* com.o2bs.globals.example.competence.service.*.save*(..))"
access="ROLE_WRITE"/>
</security:global-method-security>

<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAuthenticationListener"/>

</beans>

13. května 2009

Jaké jiné zabezpečení místo Spring security?

Při přípravě školení o Spring security jsem se zamýšlel nad tím, jaké jiné způsoby zabezpečení aplikace jsou možné, když bych vynechal Spring security. Já osobně jsem vždy používal Spring security, proto mě samotného tato otázka trochu zaskočila.

Našel jsem (= vymyslel, vyhledal, znal) následující způsoby:

  • self-made řešení - pod tím si představuji taková řešení, kde využiji základních možností JEE, tedy filtrů a servletů, a na základě toho si vytvořím své vlastní filtry, kontroly. K tomu si dodělám přihlašovací formulář, napojím to na databázi a mám základní řešení hotové.

  • JEE servery - zabezpečení aplikace nechat na serverech.

  • Seam a Drools - framework Seam integruje knihovnu Drools a dohromady to vytváří pěkné řešení pro kompletní zabezpečení aplikace.

Jednotlivé způsoby lze vhodně kombinovat, např. použít servery pro autentifikaci uživatelů a autorizaci řešit pomocí Spring security.

První řešení mi není sympatické z toho důvodu, že nerad vytvářím něco, co již někdo jiný udělal (a většinou mnohem lépe). Snažím se držet zásady, že čím méně sám naprogramuji, tím lépe :).
Druhé řešení nemám rád z důvodu závislosti na aplikačním serveru, tedy omezené možnosti přenositelnosti a hlavně při každé instalaci to musím řešit znovu a znovu. Také nabízené možnosti zabezpečení (zejména co se týče autorizace) nejsou takové jako u jiných řešení.

Znáte prosím nějaké další způsoby zabezpečení aplikací?

11. května 2009

GUTs = good unit tests

K problematice testování jsem četl výborný článek (1, 2) na JavaWorld, který všem vřele doporučuji - nejen kvůli obsahu, ale i kvůli množství odkazů na další články a zajímavé knihovny.

K obvyklým a častým "best-practices" (např. JUnit best practices) bych ještě přidal z mých zkušeností následující:

  • testovací kód by měl splňovat stejné kvalitativní nároky jako produkční kód. Místo jedné dlouhé testovací metody, která navíc ještě obsahuje duplicitní kód střídající se v jedn. testovacích metodách, je vhodné společný kód vyčlenit do separatních metod nebo i tříd, aplikovat pravidla refactoringu, když už kód "začne smrdět" (1, 2).
  • testy jsou ukázkou použití produkčního kódu, testy jsou součástí popisu API aplikace.
  • jednotlivé testy musí být na sobě nezávislé, pořadí spouštění testů může být libovolné
  • otestovat lze (skoro) všechno, jen je potřeba najít rovnováhu mezi náročností napsání testů a jejich přidanou hodnotou. Nesnažit se tedy za každou cenu dosáhnout 100% pokrytí produkčního kódu testy, ale spíše se držet pravidla 20/80, kdy otestováním 20% kódu otestujeme 80% funkcionality.
  • testy nesmí mít žádné vedlejší efekty, např. přidaný záznam v DB.
  • při psaní testů se nespoléhat na konkrétní prostředí (Locale, adresář na lokálním disku apod.), protože testy se mohou spouštět kdekoliv.

Co mě ale v uvedeném článku nejvíce zaujalo byla knihovna hamcrest. Při psaní testů jsem měl pořád problémy se psaním smysluplných komentářů do assertů a navíc mi ty všechny kontroly nepřisly úplně přehledné, když toho bylo více. Toto vše řeší hamcrest díky jednoduchému a výstižnému API - na ukázky a možnosti se podívejte do tutorialu nebo do uvedeného článku.