pátek 10. července 2009

Závislosti polí ve formuláři

Ve svém vůbec prvním blogu bych se rád podíval na řešení závislostí polí ve formuláři. Nechci popisovat konkrétní technologii pro psaní webových aplikací nebo například tlustých klientů. Zajímá mě obecný problém, kdy máme aplikaci pracující s množstvým formulářů, které jsou „složité“ a jejich složitost je tvořena mimo jiné i závislostmi mezi poli, která obsahují. Závislostí polí ve formuláři myslím některou z následujících možností:
  • Viditelnost nebo přístupnost pole závisí na hodnotě jiného pole nebo polí.
  • Seznam přípustných hodnot pro nějaké pole závisí na hodnotě jiného pole nebo polí.
  • Změna hodnoty pole (nebo polí) způsobí změnu stavu (tzn. přístupnosti nebo hodnoty) jiného pole nebo polí.
  • Libovolnou jinou závislost.
V tomto blogu se nezaměřím na konkrétní technologií vytváření GUI aplikací, chci spíše popsat obecný postup, který lze implementovat při tvorbě webových aplikací, tlustých klientů nebo třeba i ajaxových webů. Příklady v blogu jsou naprogramované v Javě, ale diskutovaný problém i jeho řešení se na Javu omezovat nemusí.

Většina frameworků a aplikací má dnes spolehlivě vyřešené problémy jako binding formulářových dat na doménový model aplikace, validace jednotlivých polí, lokalizace a další problémy, které musí typické GUI řešit. Ještě jsem se ale nesetkal s aplikací, která by závislostí polí ve formuláři řešila způsobem, který by byl přehledný, do budoucna udržovatelný a hlavně, který by byl testovatelný (ve smyslu unit testů). Domnívám se, že závislosti polí jsou často v návrhu aplikací opomíjeny, a chci proto představit jednoduchý návod, jak závislosti řešit. Jistě nejde o žádnou převratnou inovaci, ale podobný postup jsem zatím nikde zdokumentovaný a hlavně použitý nenašel. Smysl tohoto blogu tedy vidím v tom, že některým čtenářům pomůže uvědomit si problém a jeho nepříjemným důsledkům se vyhnout.

Příklad

Budu používat příklad převzatý z bankovní aplikace, která řeší evidenci nemovitostí pro účely pořízení a schvalování žádosti o hypoteční úvěr. Aplikace eviduje tři typy nemovitostí – parcely, budovy a bytové jednotky. Pro svůj příklad se zaměřím na parcely. Atributy parcely a jejich závislosti jsou popsány následující tabulkou:

Popis parcelyČeské KÚ?Má číslo?Typ?OvěřitelnéČísloČíslo LVPomocná identifikaceZdroj PZE
Parcela katastru nemovitostíanoanoKNanopřístupné, povinnépřístupné, povinnénepřístupné, nullnepřístupné, null
Parcela zjednodušené evidenceanoanoZEanopřístupné, povinnépřístupné, povinnénepřístupné, nullpřístupné, povinné
Budoucí parcelaanonenepřístupné, nullnenepřístupné, nullnepřístupné, nullpřístupné, povinnénepřístupné, null
Zahraniční parcelanenepřístupné, nullnepřístupné, nullnenepřístupné, nullnepřístupné, nullpřístupné, povinnénepřístupné, null

Sloupce tabulky popisují atributy a vlastnosti parcely, řádky popisují čtyři možné typy parcel. První tři atributy můžeme chápat jako „diskriminant“ parcely – jejich hodnoty podmiňují přístupnost, povinnost a přednastavené hodnoty jejich dalších atributů. Závislosti polí nemusí být vždy specifikovány tabulkou, asi nejčastěji jsou podobné závislosti popsány sadou podmínek („if – then“). Bohužel není výjimkou, že podobná specifikace úplně chybí a předpokládá se nějaká implicitní znalost. V reálné aplikaci jsou definice parcely a závislostí jejich atributů mírně složitější. Snažil jsem se zadání zjednodušit, ale přitom ho ponechat dostatečně ilustrativní.

Jak by takové zadání řešila typická aplikace? Nejčastěji se setkávám s tím, že se podobné závislosti neřeší systematicky, že jsou řešeny množstvím podmínek na různých místech nebo vrstvách aplikace. Podmínky mohou být často rozhozeny mezi view a controllery (ve smyslu MVC návrhu). Například událost změny katastrálního území parcely (KÚ) může vyvolat akci na serveru (ve smyslu volání metody controlleru Springu MVC nebo volání akce Struts 2), která změní KÚ a pokud nová hodnota znamená:
  • České KÚ - vynuluje ještě hodnoty pomocné identifikace a zdoje PZE;
  • Zahraniční KÚ - nastaví typ parcely na null, stejně tak její číslo a číslo LV a vynuluje také hodnotu zdroje PZE.
Později se ještě nová hodnota KÚ bude muset zohlednit ve view, kde se podle ní rozhodne o přístupnosti / nepřístupnosti dalších polí formuláře. Pokud si teď představíme běžnou situaci, kdy má programátor rozhodnout, jestli je specifikace správně implementovaná, není to jednoduchý úkol. Jinak než uživatelským testováním není jednoduché zjistit:
  • Kde všude se závislosti polí vyhodnocují?
  • Dochází někde k duplikacím kódu pro vyhodnocování závislostí?
  • Jaký dopad to má na doménový model aplikace?
  • Kde všude se vyhodnocuje přístupnost polí formuláře?
  • Jak si s tím později poradí validace?
  • … a podobné otázky.
Pokud je formulářů a závislostí hodně, začne být velkým problémem, že závislosti nejsou zachycené explicitně, tzn. že neexistuje něco jako stav přístupnosti polí formuláře a jednotný mechanismus pro jeho vyhodnocování. Viděl jsem už několik projektů, u kterých mě mrzelo, že kód jinak rozumně a vhodně navržené aplikace byl znehodnocen . Netvrdím samozřejmě, že jsou to právě závislosti formulářových polí, které vždy degradují kvalitu zdrojového kódu, domnívám se ale, že jde o jeden z typických problémů.

Návrh řešení

Představme si, že parcelu máme reprezentovanou třídou Plot.
public class Plot {
private boolean locatedInCR;
private boolean numberAssigned;
private String plotType;
private String number;
private String ownershipNumber;
private String auxiliaryId;
private String simplifiedEvidenceSource;
...
}
Obecně pro každé pole formuláře můžeme uvažovat čtyři stavy:
  • Přístupné, nepovinné
  • Přístupné, povinné
  • Nepřístupné
  • Skryté
Mějme tyto stavy reprezentované třídou FieldState.
public class FieldState {
public static final FieldState ENABLED_OPTIONAL = new FieldState("ENABLED_OPTIONAL", true, true, false);
public static final FieldState ENABLED_MANDATORY = new FieldState("ENABLED_MANDATORY", true, true, true);
public static final FieldState ENABLED = ENABLED_OPTIONAL;
public static final FieldState DISABLED = new FieldState("DISABLED", true, false, false);
public static final FieldState HIDDEN = new FieldState("HIDDEN", false, false, false);

private String code;
private boolean visible;
private boolean enabled;
private boolean mandatory;

public boolean isVisible() { return visible; }
public boolean isEnabled() { return enabled; }
public boolean isMandatory() { return mandatory; }

@Override
public String toString() { return code; }

private FieldState(String code, boolean visible,
boolean enabled, boolean mandatory)
{
this.code = code;
this.visible = visible;
this.enabled = enabled;
this.mandatory = mandatory;
}
}
Jako „zkratka“ je ve třídě ještě reprezentovaná hodnota „přístupné“ (ENABLED), která vlastně přesně znamená „přístupné, nepovinné“.

Pokud budeme uvažovat formulář pro editaci dané parcely, můžeme stav jeho polí reprezentovat třídou PlotFieldState. Třída popisuje přístupnost jednotlivých polí.
public class PlotFieldState {
private FieldState numberAssigned = FieldState.ENABLED;
private FieldState plotType = FieldState.ENABLED;
private FieldState verifiable = FieldState.ENABLED;
private FieldState number = FieldState.ENABLED;
private FieldState ownershipNumber = FieldState.ENABLED;
private FieldState auxiliaryId = FieldState.ENABLED;
private FieldState simplifiedEvidenceSource = FieldState.ENABLED;
...
}
Předpokládejme, že existuje mechanismus, který pro každou kombinaci hodnot parcely (tzn. libovolnou instanci třídy Plot), umí vyhodnotit stav formuláře parcela (tzn. přístupnost a povinnost jeho polí). Mějme tento výpočet reprezentovaný rozhranním PlotDependencySolver.
public interface PlotDependencySolver {
void solveDependencies(Plot plot, PlotFieldState state);
}
Interface PlotDependencySolver obdrží jako parametry aktuální hodnoty parcely a stav odpovídajících polí formuláře. Provede vyhodnocení a výsledek - tzn. změněné hodnoty a změněný stav - reflektuje do těchto parametrů.

Explicitní reprezentaci stavu polí formuláře (reprezentovanou třídou PlotFieldState) můžeme použít pro následující činnosti:
  • Zobrazování (konstrukci view) formuláře.
    • Stačí pouze procházet pole formuláře, kde pro každé z nich mám k dispozici stav, který říká, jestli dané pole vykreslit, resp. jestli bude přístupné. Důležité je, že viditelnost a přístupnost polí nemusím řešit složitými podmínkami (např. v JSP, u mnoha elementů typicky v atributu disabled), ale jednoduchým dotazem na stav (PlotFieldState).
  • Validace hodnot z formuláře.
    • Opět stačí procházet hodnoty polí z formuláře, pro každé z nich ihned vím, jestli je povinné / nepovinné, tedy jestli ho mám validovat.
    • Pokud by bylo požadavkem validovat i kombinace hodnot polí (např. kombinace hodnot combo boxů – „auto Škoda, model Octavia“), mohu třídu PlotFieldState rozšířit i o reprezentaci přípustných hodnot pro pole formuláře. Snahou je přípravit si takovou stavovou informaci, která mi dovolí iterovat přes pole formuláře a validovat jedno po druhém.
  • Testování závislosti polí.
    • Mám-li stav formuláře explicitně reprezentovaný třídou a mechanismus pro zjišťování stavu, mohu tento mechanismus testovat (tzn. testovat závislost polí formuláře).
Hlavní myšlenka je tedy v tom mít explicitní reprezentaci polí formuláře a mechanismus, jak ho vyhodnotit. Výrazně nám to zjednoduší jinak potenciálně složité akce jako je zobrazování formuláře, validace hodnot a testování závislosti polí.

Následující ukázka kódu demonstruje možný postup při validaci parcely.
public class PlotValidationHelper {
public void validate(Plot plot, PlotFieldState state) {
if (state.getNumber().isMandatory() && !isNumber(plot.getNumber())) {
// report error
}
if (state.getOwnershipNumber().isMandatory() && !isNumber(plot.getOwnershipNumber())) {
// report error
}
if (state.getAuxiliaryId().isMandatory() && plot.getAuxiliaryId() == null) {
// report error
}
if (state.getSimplifiedEvidenceSource().isMandatory() && plot.getSimplifiedEvidenceSource() == null) {
// report error
}
}
...
}
Další ukázka kódu představuje jednoduchý JUnit test case pro testivání závislostí polí.
public class Test1 extends TestCase {
public void testLandRegister() {
Plot plot = new Plot();
plot.setLocatedInCR(true);
plot.setNumberAssigned(true);
plot.setPlotType("KN");

PlotFieldState state = new PlotFieldState();

getSolver().solveDependencies(plot, state);

assertEquals(FieldState.ENABLED, state.getVerifiable());
assertEquals(FieldState.ENABLED_MANDATORY, state.getNumber());
assertEquals(FieldState.ENABLED_MANDATORY, state.getOwnershipNumber());
assertEquals(FieldState.DISABLED, state.getAuxiliaryId());
assertNull(plot.getAuxiliaryId());
assertEquals(FieldState.DISABLED, state.getSimplifiedEvidenceSource());
assertNull(plot.getSimplifiedEvidenceSource());
}

}

Implementace

Zbývá už jen zamyslet se nad implementací mechanismu pro vyhodnocování nového stavu formuláře, tedy implementaci rozhranní PlotDependencySolver. Pro implementaci můžeme zvážit dvě možnosti - přímou implementaci prostředky Javy a implementaci pomocí rule engine.

Nebude žádným překvapením, že prostředky Javy můžeme metodu solveDependencies implementovat prostě jako sadu if – then statementů.
public class JavaPlotDependencySolver implements PlotDependencySolver {
@Override
public void solveDependencies(Plot plot, PlotFieldState state) {
if (plot.isLocatedInCR() && plot.isNumberAssigned() && "KN".equals(plot.getPlotType())) {
// Land register
state.setNumberAssigned(FieldState.ENABLED);
state.setPlotType(FieldState.ENABLED);
state.setVerifiable(FieldState.ENABLED);
state.setNumber(FieldState.ENABLED_MANDATORY);
state.setOwnershipNumber(FieldState.ENABLED_MANDATORY);
state.setAuxiliaryId(FieldState.DISABLED);
state.setSimplifiedEvidenceSource(FieldState.DISABLED);

plot.setAuxiliaryId(null);
plot.setSimplifiedEvidenceSource(null);
} else if (!plot.isLocatedInCR()) {
// Foreign plot
state.setNumberAssigned(FieldState.DISABLED);
state.setPlotType(FieldState.DISABLED);
state.setVerifiable(FieldState.DISABLED);
state.setNumber(FieldState.DISABLED);
state.setOwnershipNumber(FieldState.DISABLED);
state.setAuxiliaryId(FieldState.ENABLED_MANDATORY);
state.setSimplifiedEvidenceSource(FieldState.DISABLED);

plot.setNumberAssigned(false);
plot.setPlotType(null);
plot.setNumber(null);
plot.setOwnershipNumber(null);
plot.setSimplifiedEvidenceSource(null);
} else { ... }
}
}
Takový přímočará implementace má ale i některá omezení:
  • Musíme sestavit deterministický postup (algoritmus), kdy všechny podmínky a závislosti vyhodnotíme a reflektujeme do nového stavu.
  • Pokud si vyhodnocování závislostí představíme v nějakém "vyhodnocovacím stromě" (uvádím v uvozovkách a doufám, že intuitivní chápání pojmu tady postačí), může a bude se pravděpodobně stávat, že některé podstromy výpočtu se budou opakovat, protože musí reflektovat, že stav formuláře se během vyhodnocování mění (každá jednotlivá podmínka může do stavu něčím přispět). Mířím k tomu, že sestavit takový algoritmus nemusí být vždy jednoduché.
Přesto platí, že dostat vyhodnocování závislostí na jedno místo, reprezentované rozhranním PlotDependencySolver, vidím jako úspěch s jasným pozitivním dopadem na přehlednost a udržovatelnost kódu aplikace. U složitějších závislostí se dá přiklonit k rozšiřitelnějšímu řešení – mohu např. vytvořit „pluggable“ mechanismus, kde jednotlivé implementace PlotDependencySolver-u budou zohledňovat jednotlivé typy parcel (viz. zadání příkladu výše).

Z uvedených důvodů je elegantní a preferovanou možností zapojení rule engine (např. Drools), které nám dovolí většinu zmíněných výhrad jednoduše překonat. Protože vidím tuto možnost jako velmi vhodnou a zajímavou, budu jí věnovat další samostatný blog.

Shrnutí

V tomto blogu snažím klást důraz hlavně na myšlenku explicitní reprezentace stavu formuláře, ne na její implementaci. Důležité je dosáhnout toho, že závislosti polí budou vyhodnocovány na jednom místě a stav formuláře bude důsledně používaný. Pojďme si nakonec shrnout výhody a nevýhody řešení s explicitní reprezentací stavu polí formuláře. Postup přináší následující výhody:
  • Závislosti polí ve formuláři jsou definovány na jednom místě.
    • Abych závislosti zmapoval, pochopil nebo upravil, nemusím je hledat na různých místech / vrstvách aplikace.
    • Odpadávají duplicity vyhodnocování závislostí. Stejný kód pro vyhodnocení stavu formuláře mi poslouží pro vykreslení polí ve view jako i pro validaci jejich hodnot.
  • Přehledný, snadno čitelný kód pro vykreslování view a pro validace hodnot z formuláře.
    • Procházím pole jedno po druhém, vyhodnocuji jenom, jakým způsobem (ne)zobrazit a jak validovat.
  • Testovatelnost závislostí.
    • Testovatelnost závislostí vidím jako největší výhodu tohoto řešení.
Řešení má samozřejmě i nevýhody:
  • Režie navíc.
    • Samostatná reprezentace stavu polí formuláře jako i práce s explicitně definovanými stavy může být pro jednodušší aplikace zbytečná. Vedle doménových tříd reprezentujích strukturu informací tu mám ještě další, speciální třídy pro zachycení stavu polí ve formuláři.
  • Kombinace přístupu explicitního stavu polí s jiným řešením může smazat výhody explicitních stavů a vytvořit nepřehledný, těžko udržovatelný kód.
    • To je ale obecná poznámka aplikovatelná snad na jakékoliv řešení. Pokud někdo nedodrží domluvené návrhové vzory, přehlednost a udržovatelnost kódu tím samozřejmě trpí vždy.
Pokud jste blog dočetli až sem, děkuji za pozornost a trpělivost. Jak závislosti polí řešíte vy? Považuje explicitní reprezentaci stavu formuláře za dobrý nápad? Pokud vás problematika zaujala, doporučuji svůj další blog, který bude popisovat implementaci závislostí s pomocí Drools.

13 komentářů:

  1. Tuhle problematiku řeším docela často na klientské straně. To je zajímavé hlavně tím, že jde o čisté událostní programování, přičemž různé prohlížeče pracují s událostmi odlišně. Použití stavů je v takové situaci holá nezbytnost.

    Další zajímavý moment je na straně serveru, když některé části formuláře lze zobrazit až po odeslání některých hodnot - např. select se seznamem ulic až po vybrání země a města. Tuto záležitost je také vhodné řešit pomocí vnitřních stavů formuláře a to dělá opravdu málokdo.

    OdpovědětVymazat
  2. Ahoj, asi před rokem jsem řešil podobný problém. Hledal jsem framework pro validaci beanů a došel jsem k podobnému zjištění jako ty v článku. Nakonec jsme použili framework vlad(www.sapia-oss.org/projects/vlad), který je jednoduchý, lehce upravitelný a validace mám pěkně pohromadě. Jediná nevýhoda je, že se kód píše do XML. Celkově jsou s ním programátoři(hlavně díky jednoduchosti) spokojení.

    Osobně se mi nelíbí, že vymýšlíš a implementuješ vlastní řešení takhle profláknutýho tématu. Určitě hodně záleží na architektuře a požadavků aplikace(jak ses zmínils v článku), ale opravdu nejde použít existující řešení a případně doupravit to? Co třeba Domain Driven Development a využití anotací v domainových třídách?

    Co se týče samotného návrhu řešení, tak se mi to líbí;-)

    OdpovědětVymazat
  3. 2 Švarcík - Postup, který tady popisuji, nenahrazuje frameworky pro validaci ve Springu nebo ve Struts nebo například framework Vlad.

    Vedle validace jsem se zaměřil i na konstrukci / zobrazování formulářů, testování závislostí formulářových polí a reakci na stav formuláře (tzn. změnu stavu některých polí nebo změnu hodnot polí). Pokud uvažujeme pouze validace, právě závislosti polí neumím se zmíněnými frameworky řešit způsobem, který by mi vyhovoval (tzn. jinak než složitými podmínkami na několika místech). V kontextu validačních frameworků jde popsaný postup chápat jako další informaci, která vstupuje do algoritmu validace formuláře.

    Rozhodně mi nejde o nahrazení existujích frameworků, jenom o doplnění v úzce vymezené oblasti.

    OdpovědětVymazat
  4. Možná to někoho překvapí, ale souhlasím s Tomášem - je až překvapivé, že takhle jednoduchý problém nemá v řadě webových frameworků přímočaré řešení.

    Myslím si, že velmi elegantní odpovědí mohou být komponentové frameworky, které samy o sobě přirozeně pracují se stavy. Jenomže ty zase přinášejí o úroveň větší komplexitu než jednoduché request/response web frameworky.

    V naší firmě nakonec používáme vlastní komponentové řešení, ve kterém jsme tento problém řešili obdobně.

    OdpovědětVymazat
  5. Já bych jen doplnil z analytického hlediska, že příklad není možná úplně špatný, ale v některých ohledech ne zrovna šťastně volený.

    Například informace "české KÚ" je trochu zavádějící (v kontextu světa mimo banku), neexistuje v reálu nic jako "zahraniční KÚ". Jde o znásilnění standardního číselníku KÚ pro potřeby banky o položku "zahraničí" :-) Je toho více.

    Jinak - navržené řešení se mi celkem líbí a myslím, že ti dám ještě příležitost ho proklepnout ;-)

    OdpovědětVymazat
  6. Rastislav "Bedo" Siekel16. července 2009 v 7:54

    Riešenie je to síce pekné, ale obávam sa, že v praxi nepoužiteľné, pretože neráta so security. Jeden field môže byť pre niektoré role ENABLED, pre iné VISIBLE a pre ďalšie HIDDEN, takže nie je možné urobiť implementáciu PlotFieldState. :-( Možno by šlo urobiť PlotFieldState v závislosti na role, ale potom by JavaPlotDependencySolver musel nastaviť stav field-u pre všetky jeho role, čo je počet riadkov zdrojáku = počet polí * počet rolí. Ale nakoniec, pre niekoľko málo rolí by sa to aj dalo. Ale udržiavať to, keď pridám do aplikácie ďalšiu rolu - to by som nechcel :-)

    OdpovědětVymazat
  7. Bedo: To bys ovšem s vaničkou vylil i dítě. Jedna věc je použití vnitřních stavů formuláře - to je prostě nezbytnost a nemá smysl se hádat jestli to používat nebo ne. Druhá věc je zapojení security a dalších věcí (zdaleka nemusí jít jen o security). Samozřejmě u nějaké miniaplikace to jde pojmout tak naivně jak popisuješ, ale u reálných aplikací se spíš dá očekávat použití aspektu nebo něčeho podobného.

    Tady bych připojil další důležitý požadavek, který často není splněn - konečné rozhodnutí o finálním stavu fieldů musí padnout co nejpozději, v optimálním případě až v okamžiku výstupu, protože do toho zda field bude "read only" nebo "hidden" může kecat kde kdo.

    OdpovědětVymazat
  8. I když blog popisuje využití stavu formuláře pro řešení závislostí mezi jeho poli, dá se stav výhodně použít i při vyhodnocování přístupových práv. Souhlasím s Richardem, že do stavu formuláře se může promítnout mnoho různých aspektů. Nemusí to být jenom zde diskutované závislosti a přístupová práva, můžeme uvažovat např. "customizovatelná" nebo "personifikovaná" řešení typická pro portály, kde výslednou podobu formuláře může ovlivnit i sám uživatel.

    Pokud mám požadavek, že definice přístupových práv má mít vliv na podobu formulářů - a nemyslím jen vzhled, mám na mysli viditelnost polí nebo sekcí formuláře nebo dokonce datových záznamů, je řešení obvykle dost pracné.

    Jak by definice vazby přístupových práv na formuláře vůbec měla vypadat? V jednodušším případě postačí definice viditelnosti a povinnosti polí podle přístupových rolí, ale jenom tahle definice může být i výrazně složitější.

    Kde by se v tomto případě přístupová práva měla vyhodnocovat? Pokud by to bylo jenom ve view, měl bych sice funkční view, ale stále bych neměl bezpečnou aplikaci. Případný útočník by mohl view obejít a např. sám sestavovat http requesty se zakázanými atributy a klidně tak nechat server je aktualizovat. Narážím na to, že i vztah přístupová práva vs. formuláře je často třeba vyhodnocovat na několika místech. Proto vidím jako výhodné schovat i tento aspekt za stav formuláře.

    Ohledně implementace, kdybychom neuvažovali jenom závislosti mezi poli, můžeme místo rozhranní PlotDependencySolver mluvit třeba o rozhranní PlotStateSolver (nebo PlotStateFactory, o název mi tady nejde) a mít jednu implementaci pro závislosti, druhou pro přístupová práva a spojit je podobně jako "chain of responsibility".

    Jinak zatím děkuji za všechny příspěvky, rozvinulo se to v zajímavou diskuzi.

    OdpovědětVymazat
  9. Pokud o název nejde, tak já bych preferoval místo Plot spíš Land ;-)

    OdpovědětVymazat
  10. takto by sa mi to pacilo viac, ale je to asi len otazka stylu :)

    public enum FieldState {
    ENABLED_OPTIONAL(true, true, false),
    ENABLED_MANDATORY(true, true, true),
    ENABLED(true, true, false),
    DISABLED(true, false, false),
    HIDDEN(false, false, false);

    private boolean visible;
    private boolean enabled;
    private boolean mandatory;

    public boolean isVisible() { return visible; }
    public boolean isEnabled() { return enabled; }
    public boolean isMandatory() { return mandatory; }

    FieldState(boolean visible, boolean enabled, boolean mandatory) {
    this.visible = visible;
    this.enabled = enabled;
    this.mandatory = mandatory;
    }
    }

    OdpovědětVymazat
  11. Ano, dá se volit i enum. Ale protože jsem chtěl, aby platilo ENABLED == ENABLED_OPTIONAL, bylo by nutné ještě implementovat metodu valueOf. To by pro někoho mohlo být u enum-u matoucí, tak jsem volil starší přístup s "type safe enumeration".

    OdpovědětVymazat
  12. Dostal jsem se sem po precteni clanku o ciselnicich a prekvapilo me to natolik, ze musim reagovat i po takove dobe.

    Prominte, ale pripada mi, ze i pres vase zkusenosti a pracovni pozice s tim dost zapasite. Polozim nekolik otazek:
    1. Na kolik mist musi programator sahnout, aby korektne pridal dalsi atribut parcely?
    2. Jak slozity bude solver?
    3. Jak bude solver vypadat po par zmenach specifikace?

    a odpovedi:
    1. samotna promenna, stav ve formulari, validace, v solveru do vsech vetvi. Obecne to zavisi na poctu obsluznych funkci. Je pravdepodobne, ze nektere z tech mist se prehledne, zapomene upravit vetev v solveru nebo vubec nebude vedet, ze existuje nejaka validace.
    2. patrne je snaha vyclenit jakesi skupiny v tabulce, ktere zavisi na spolecne podmince. Pocet skupin se podari snizit rekneme na 10, v kazde vetvi je nutno upravit stav vsech atributu, nektere i vynulovat. Radove se dostavame na 200 radku, ve kterych se nevyzna za 5 minut ani autor.
    3. silene. Budou situace, kdy bude formular spatne povolovat nebo zakazovat polozky, protoze se sem tam zapomene radek. Budou kombinace, ktere nebudou osetrene, protoze je temer nemozne zjistit, jestli if vetve pokryvaji celou tabulku.

    Opet mi prichazi na mysl lepsi reseni pomoci enumu. Pro kazdy atribut bude polozka v enumu, ktera bude mit zhruba nasledujici metody:

    void init(Plot plot); // nuluje prislusnou polozku
    bool validate(Plot plot, pripadne dalsi kontext); // otestuje, zarve
    String getErrorMsg(); // text nebo identifikace hlasky kdyz dojde k chybe
    FieldState getState(Plot plot, pripadne dalsi kontext, napr security...); // dle pomerne jednoducheho testu vrati stav, typicky 3 radky kodu

    1. Kdyz se zavede novy atribut, implementuje se k nemu polozka do enumu a bude to fungovat. Staci sahnout na dve mista. Pokud budou metody u enumu defaultne abstraktni, i zacatecnik musi vse implementovat, cili na nic nezapomene.
    2. Funkce solver a validate (a pripadne dalsi) budou jednoduche for cykly, ktere projdou vsechny polozky. V solveru bude na jednom miste reseni takovych veci jako ze disabled polozka ma byt vynulovana.
    3. V pripade jakychkoli zmen je jednoduche najit postizene atributy a zmenit u nich podminky. Lze systematicky projit vsechny atributy a zkontrolovat, jestli maji spravnou funkci getState. Pri pridani dalsi obsluzne funkce a tedy i pripadne metody do enumu nezapomenete nic implementovat - kompilator vas donuti.

    PlotFieldState muze byt obycejne pole, ktere v solveru vytvorite a ve validaci zkontrolujete. Formular najde FieldState ordinalem z enumu.

    Vysledkem je kod, ktery je prehledny a udrzovatelny. Nejspise nebudete potrebovat funkci delsi nez 10 radku. Pri zmenach temer nelze na neco zapomenout - kod neni redundantni a nejsou v nem skryte zavislosti.
    Napadaji me dve slabiny
    - v getState se budou mnozit podobne testy, lze vyresit nejakym predpocitanim do kontextu a poslat ho tam.
    - delka enumu je umerna poctu atributu a poctu operaci (metod), cili roste kvadraticky, lze vyresit rozhazenim polozek enumu do trid.

    Omlouvam se za delsi prispevek, ale snad vas posune kupredu :-). Obecne si pri navrhu kladu prave podobne otazky a snazim se o vysledek, ktery bude prehledny, snadno menitelny a hlavne blbuvzdorny. Nasly by se dalsi kriteria, ale tato jsou dle mych zkusenosti snad nejdulezitejsi.

    OdpovědětVymazat
  13. @IA: Jedním z požadavků, který jsem si dal a zároveň výhodou, kterou zmiňuji, je odstranění duplikací při vyhodnocování závislostí. Proto dávám přednost vyhodnocení závislostí v metodě solveru proti rozdělení vyhodnocení na jednotlivé atributy. Pokud je doména hodně složitá, dá se deterministický postup nahradit deklarativními pravidly. Tomu se věnuje druhá část blogu - Závislosti polí ve formuláři a Drools. Pro zmenšení rizika zanesení chyby při změně domény důvěřuji dobrému pokrytí testy.

    Dále se dost lišíme v názoru na použití prostých java bean (PlotFieldState) oproti enumům.

    Vždy mě těší, pokud si někdo můj příspěvek přečte a přispěje do diskuze svým názorem. Zároveň si myslím, že každé řešení je špatné, dobré nebo lepší vždy v kontextu určitých požadavků a očekávání. Proto i teď zůstávám zastáncem svého řešení.

    OdpovědětVymazat