Fejlesztés, metodikák, ortogonális kódok

D-nee tett fel hétvégén egy érdekes linket a deliciousre: Jeremy D. Miller írt egy egész részletes szösszenetet Orthogonal Code címmel, és egész jól összefoglal bizonyos programtervezési elveket.

Mondjuk ha csak annyi lenne a véleményem róla, hogy érdemes elolvasni, akkor egyszerűen fognám magam, és én is feltenném a deliciousre, hogy az a kevés ember, aki véletlenül odatéved, megtalálja. De a helyzet az, hogy egyfelől érdekesnek tartom, másrészt nem értek vele (mindenben) egyet.

Ami feltétlenül tetszett az írásban, és ami miatt minden, programozásban érintett embernek ajánlom elolvasásra, hogy konkrét példán bemutatja a különböző programfejlesztési elveket. Ráadásul mosóporreklám stílusban, azaz ilyen volt és ilyen lett összehasonlítással.

Előrebocsátanám még (egyszer), mielőtt elkezdek belemenni egyes részletkérdésekbe, hogy a cikkel alapvetően egyetértek, de néhány dolog érdemes továbbgondolásra. Az egyik, hogy ezeket a tervezési elveket szabálynak állítja be, szerintem viszont legfeljebb ökölszabálynak lehet tekinteni. Azaz alapvetően érdemes követni őket, kivéve, ha nem jók az adott helyzetben. 😀

Miért lehetnek rosszak ezek az elvek? Például ha minden egyes funkciót külön osztályba teszünk, az osztályok, interfészek száma könnyen kezelhetetlen méretűre duzzadhat. Szóval amit megnyerünk a réven (áttekinthetőbbek és szerkeszthetőbbek a lokális kódok), elveszíthetjük a vámon (struktrurálisan nehezen áttekinthető a rendszer, nagy a betanulási idő).

Emellett a sok indirekció és absztrakciós szint teljesítményproblémákhoz vezethet. Például egy hívásnak nem elhanyagolható költségei vannak (stackelés, stb.). Vagy éppen hivatkozhatnék arra a nem túl régi tapasztalatomra, hogy egy algoritmusnál a legtöbb időt az vitte el, hogy a memóriából ismételten lekért adatokat. Ezt profilerrel derítettem ki, és lokális változóba áttöltve az adatokat és újra felhasználva kb. felére csökkentettem a futási időt. Vicces dolog az a cache miss (legalábbis szerintem ez történhetett).

És a legkomolyabb, kimutatható probléma (szerintem): a “tell, don’t ask” elvet jelenleg piszkosul nem szokás adatmodellre alkalmazni. Legalábbis Java környékén az aktuális state of the art marhára nem teszi – kivéve, ha rosszul látom, hogy mi a legfejlettebb. Aminek akár még oka is lehet.

A “tell, don’t ask” elv mit is jelent? Mondd meg az objektumnak, hogy mit csináljon (állapotfüggően), ne lekérd az állapotát, és ez alapján te döntsél helyette.

Érdekes módon viszont az adatmodellek manapság egyre inkább mennek a nagyon buta, csak getter/setterekből álló modellek felé. Miért is? Én úgy tippelem azért, mert

  1. Ez a rész jól generálható. Az EMF alaptechnológia tipikusan arról szól, hogy valahogy összeklikkelgetsz egy EMF modellt, beállítod a genmodellt, és kapsz egy gyönyörűséges Java osztályhalmazt. Ami persze nem tud semmit azon túl, hogy getter, setter és factory hívásokkal felépíthető és bejárható.
  2. J2EE technológiánál az entitásokat menti le a rendszer egy az egyben adatbázisba, amely entitásoknak tipikusan szintén getter/setter metódusai vannak. Esetleg még számított mezők is beköszönnek.

Mindkét esetben az a szokás, hogy a modellbefolyásoló logikát külön Manager jellegű osztályokba tesszük, akik a tényleges igényeknek megfelelően építik (rombolják 🙂 ) a modellt.

És végül a legfontosabb hozzáfűznivalóm az íráshoz: igen, hosszú távon megéri ezeket az elveket követve tervezni, kivéve, ha amiatt, mert nem készülünk el határidőre, rövid/közepes távon befejeződik a projekt. És ennek a kezelése bizony emberi kérdés. Emiatt zárszóként Jeff Atwoodot idézném:

The guys and gals who show up every day eager to hone their craft, who are passionate about building stuff that matters to them, and perhaps in some small way, to the rest of the world — those are the people and projects that will ultimately succeed.

PS.: Mostanában Aadaam is foglalkozik azzal, hogyan érdemes nagyobb rendszereket összerakni. Ráadásul azt mutatja meg, hogyan lehet PHP-ban nem gányolni. Szép teljesítmény az is.

Törött ablakok és hitelek

Az utóbbi időben volt szerencsém elolvasni néhány érdekes írást programozástechnikáról. Ez nem arról szól, hogy a Java/C#/C++/Lisp nyelven jó programokat írni (noha az is hasznos olvasnivaló lehet), hanem inkább afféle ötleteket mutat, amik a programozási munka menedzsmentjéhez tartoznak.

Igen, erről nem véletlen, hogy egyeseknek a [[A programozás technológiája|PT]] jut eszébe, de szerintem annál jóval gyakorlatiasabb dologról van szó. 🙂 Persze ezt az én erőteljesen elfogult véleményem mondatja csak velem, nyilván másnak ugyanolyan használhatatlan dologról van szó.

Az utóbbi időben volt szerencsém elolvasni néhány érdekes írást programozástechnikáról. Ez nem arról szól, hogy a Java/C#/C++/Lisp nyelven jó programokat írni (noha az is hasznos olvasnivaló lehet), hanem inkább afféle ötleteket mutat, amik a programozási munka menedzsmentjéhez tartoznak.

Igen, erről nem véletlen, hogy egyeseknek a PT jut eszébe, de szerintem annál jóval gyakorlatiasabb dologról van szó. 🙂 Persze ezt az én erőteljesen elfogult véleményem mondatja csak velem, nyilván másnak ugyanolyan használhatatlan dologról van szó.

Nem tudom, mennyire ismert a „törött ablakok elmélete” (Broken Window Theory), ezért megpróbálom röviden összefoglalni. A fő gondolata, hogy ha egy civilizált környéken betörik egy ablak (lehet autóé, házé), és senki nem foglalkozik vele – itt most nem arra gondolok, hogy aki betörte, annak bűntudata lesz tőle, de azt beleértem, hogy a javítás idejére bedeszkázzák, – akkor ez közepesen hosszú távon (akár néhány hónap alatt) az egész környéken a környék leromlásához, illetve a(z akár súlyosabb) bűnesetek számának növekedéséhez vezethet.

Ez a hatás nagyjából úgy keletkezhet, hogy az emberek azt látják, hogy más nem foglalkozik a problémákkal, ezért nem érzi úgy, hogy neki is tennie kell valamit. Ha valaki kételkedik az állítás igazságában, gondoljon bele, hogy hol szedne fel nagyobb eséllyel véletlenül elejtett papírzsebkendőt: a Nyugatinál a metróaluljáróban vagy egy öt csillagos hotelben a francia Riviérán.

Hogy ez hol jön a programozáshoz? Az elv ott is alkalmazható: ha nem foglalkozol a kis hibákkal, akkor aki később kapcsolódik, azt fogja látni, hogy az elődje sem gondoskodott róla, hogy elkerülje ezeket, ő sem fog vele foglalkozni (annyira). Aki dolgozott már ilyen kóddal, az tudja, miről beszélek. 🙂

Mik ezek a kis hibák? Warningok, hiányzó dokumentáció, nem kifinomult hibakezelés, gányolás… Lehetne még sorolni. És ezeket többnyire csak akkor lehet megfelelően kezelni, ha mindenki az első pillanattól fogva odafigyel ezekre, különben annyira elszaporodnak, hogy a kezelésük szinte reménytelen (próbált már valaki 1000-es nagyságrendben warningokat javítgatni?).

Jó, mi a teendő olyankor, amikor közeleg a határidőt, és még rengeteg mindent implementálni kell? Nos, ekkor jön a Brute Force Development: kódolunk, gányolunk, és reménykedünk, hogy működni fog. Gondolhatnánk, hogy ez rossz, de a gyakorlatban elkerülhetetlen. Ez nagyon gyakran „törött ablakokhoz” vezethet (nem, most kivételesen nem arról beszélek, hogy a legálisan beszerzett Windows-unk crackeltté válik 🙂 ).

Természetesen ez rossz, de mivel elkerülhetetlen, ezért kénytelenek vagyunk kezelni. Erre a kezelésre ad egy módszert a technológiai tartozás (Technical Debth) fogalma.

Ez a valós életbeli adósságok fogalmához kapcsolódik: felveszünk hitelt, hogy valami lehetőséget időben kihasználjunk (nem kell 20 évet várnunk egy lakásra, és közben albérletben élni, hanem most beköltözünk), de ez nincs ingyen (20 évig fizetjük a részletet). Akkor ne vegyünk fel hiteleket? De, ha van valami helyzet, amit így értelmesen kihasználhatunk, hosszabb távon pénzt spórolhatunk meg, akkor fel lehet venni, de ésszel. Nagyon oda kell figyelni a hitelek visszafizetésére (lásd még: válság 🙂 ).

A programozásban ehhez hasonlóan, ideiglenesen nem a legjobb, legügyesebb, legszebb megoldást választjuk, hanem rosszabb megoldást választunk, tákolunk, gányolunk. És mondjuk magunknak dokumentáljuk, hogy mik ezek a részek, és ezzel foglalkozni kell. Mi lehet ennek az értelme? Határidő, esetleg azt mondani, hogy ha működik, akkor kiadjuk, és utána belül foltozzuk a következő verzió fejlesztésének első lépéseként.

De természetesen itt is figyelni kell, ugyanis ha túl sok efféle dolgot hagyunk benne a rendszerben, akkor később ennek az lehet a következménye, hogy ahhoz, hogy új funkciót adjunk hozzá, nagyságrendekkel több munkát kell befektetni, mint az ideális lenne.

Saját tapasztalataim alapján is igazolni tudom ezt az elvet: az elmúlt héten a saját kódomat próbáltam nagyságrendekkel javítani, hogy új funkciókat adjak hozzá. Ok, vettem a fáradtságot, hogy +20% munkával javítsam azt, amit a TDK előtt BFD-vel befejeztem (visszafizettem a technológiai tartozás egy részét), annak érdekében, hogy a rendszer jobban használható legyen.

Remélem, hasznos/érdekes, amit most felvetettem, ha másnak van véleménye, kiegészítése, nagyon szívesen veszem bármilyen formában, más tapasztalataiból tanulni igenis jó dolog. 🙂

TDK – egy nagy projekt utóélete

Az elmúlt időszak egyik fontos eseménye volt az, hogy részt vettem a kari TDK konferencián. Ez azzal járt, hogy az elmúlt néhány hónapban minden ismerősömet azzal kergettem az őrületbe, hogy legtöbbet erről beszéltem velük…

Most viszont az egész véget ért, ideje számot vetni, értékelni, ami elkészült, és a tanulságokat levonni.

Az elmúlt időszak egyik fontos eseménye volt az, hogy részt vettem a kari TDK konferencián. Ez azzal járt, hogy az elmúlt néhány hónapban minden ismerősömet azzal kergettem az őrületbe, hogy legtöbbet erről beszéltem velük…

Most viszont az egész véget ért, ideje számot vetni, értékelni, ami elkészült, és a tanulságokat levonni.

Számomra ez nagy projekt volt: 4 hónap kódolás, 1 hónap dolgozatírás, és mellé még ez-az kapcsolódott (pl. prezentáció készítése). A méretre jellemző még, hogy 5000 sor Java kódot írtam (ez nagyobb, mint bármilyen projekt, aminek a fejlesztésébe belefolytam, és a többi projektet ráadásul nem is egyedül kódoltam), valamint [intlink id=”609″ type=”post”]61 oldal dokumentációt[/intlink] (szintén rekorder méret), ráadásul angolul.

Sokféle új dolgot próbáltam ki menet közben (vagy használtam nagyobb méretben, mint korábban): Eclipse alapú fejlesztés, kényszerkielégítés (CSP/CLP) vagy éppen nagy dokumentumok szerkesztése LaTeXben. És persze Java rulez:D

Ami kicsit fájdalmasabb rész, az a tanulságok levonása: sajnos sikerült a szükségesnél kicsit nagyobbat markolni, aminek az lett a vége, hogy a befejezés kicsit rohanós lett. Ráadásul bejött a szokásos problémák egyike, mármint sikerült belefutni egyes implementációs/elvi szintű problémákba, amik megkerülésére vannak ötletek, de azért még időt igényel.

Ráadásul a negyedik-ötödik hónapra a kezdeti lelkesedés is elfogyott, így a morál csökkent. Valószínűleg könnyebb lenne fenntartani a lelkesedést, ha lenne még egy ekkora állat, aki hajlandó a projekttel foglalkozni – különösebb ellentételezés nélkül – azzal sajnos nem tudok szolgálni, munka viszont van bőven… Persze álmodozni lehet.

Ami még hasonlóan fontos kérdés, hogy hasznos volt-e a befektetett energia. Na, ez az, ami jó kérdés: rövid távon kifejezetten nehéz aprópénzre váltani, amit összeszedtem (pláne, hogy a cucc még nem is 100%-os), de hosszabb távon remélhetőleg hasznosabb lesz: felhasználható diplomamunkába, esetleg cikkekhez, doktoranduszi kutatási téma alapja lehet, stb.

De legalább az a veszély nem fenyeget, hogy a projekt hirtelen véget ér. A hiányokat lehet pótolni, illetve néhány újdonságot is lehet csinálni. De addig is irány a régi grind…

Ja, és megpróbálok majd gyakrabban írni az oldalra. 🙂 Tudom, ilyet már mondtam, de próbálkozni lehet. Mások újévkor fogadnak meg mindig olyan dolgot, amit nem tartanak meg, én ezért nem ígérek semmit senkinek – elég lesz magammal elszámolnom.

Update: amit elfelejtettem, az az, hogy szeretnék köszönetet mondani mindenkinek, aki bármilyen aprósággal hozzájárult a munkához, gondolok itt azokra is, akik nem szerepelnek a dokumentáció köszönetnyilvánításában. Tényleg mindenkinek nagyon hálás vagyok.

2D adatszerkezet modellezése Javában

…avagy hogyan lehet pozitív tulajdonság a gyengeség és a lustaság. 🙂

Arról a gyakran előforduló modellről van szó, amikor sorok és oszlopok metszeteiben rácselemek csücsülnek, melyek automatikusan létrejönnek a nekik megfelelő sor-oszlop párra való hivatkozáskor, valamint automatikusan megszűnnek soruk vagy oszlopuk törlése esetén.
A megvalósítás kézenfekvőnek tűnik: a sorokban/oszlopokban egy map-ben tároljuk az oszlopokkal/sorokkal indexelve a rácselemeket.

Valahogy így:
[cc_java]
public class Node {
}

public class Column {
}

public class Row {

protected final Map nodes = new HashMap();

public Map getNodes() {
return nodes;
}

}
[/cc_java]

Mi a gond ezzel?

  • Ha törlünk egy oszlopot, az összes sor map-jében még ott marad a rá mutató referencia, mint kulcs. Ez memory leakhez vezet: a szemétgyűjtő nem tudja eltakarítani a “zombi” oszlopokat és a hozzájuk tartozó, ugyancsak zombi rácselemeket. Ez ráadásul az adatstruktúra perzisztálásánál is problémát jelent.
  • Hogyan s mikor hozzuk létre a rácselemeket? Mindig, amikor létrehozunk egy oszlopot? Ezt jobban lehetne automatizálni, ráadásul ha nem minden rácspontba akarunk elemet tenni, fölöslegesen hozzuk létre ezeket.

Több megoldás is lehetséges, gondolhatnánk elsőre:

  • Gondolkodhatnánk egy címkézett gráfban, és a beépített kollekciók helyett alkalmazhatnánk valamiféle gráfkezelő könyvtárat, pl. a JGraphT-t, mely gondoskodik csúcs törlésekor a hozzá tartozó élekről.
  • Kibővíthetnénk a lista adatszerkezetet eseménykezelőkkel, melyek elem hozzáadásakor és törlésekor végrehajtanak egy megadott kódot.

Azonban a probléma megoldható a Java Collections Framework keretein belül, karöltve természetes kiegészítőjével, a Collections Generic API-val (a Commons Collections generikus változata). Nehogy már egy ilyen általános jelenséget ne tudjunk a megannyi hasznos absztrakciót tartalmazó Java nyelv elemeivel modellezni! A kulcsfogalom a gyenge referencia, mellyel befolyásolhatjuk a garbage collector működését, ezzel rábízva az elárvult elemek törlését.

Javában az alapértelmezett referencia erős (strong), de ezenkívül még létezik a gyenge (weak), lágy (soft) és fantom (phantom) referencia. A weak reference lényege, hogy amennyiben egy objektumra csak ilyenek mutatnak, a szemétgyűjtő teljes lelki nyugalommal eltakarítja. Nekünk pont ez a viselkedés jön kapóra: a map-ben gyenge referenciákat fogunk csak tárolni az oszlopokra/sorokra. (A másik két típus most nem érdekes számunkra, pedig azok is hasznosnak bizonyulhatnak más esetekben.)

A Collections API tartalmazza a WeakHashMap osztályt, mely a HashMap-hez hasonló, azonban kulcsai gyenge referenciák. A WeakHashMap ezeket átlátszóan kezeli, önmagától létrehozva és dereferálva nekünk őket. Így programunkban az automatikus törléshez csak két dolgot kell módosítanunk: HashMap helyett WeakHashMap-et használunk, és a kívánt pillanatban meghívjuk a garbage collectort.

A létrehozás pedig lusta map segítségével történik: a rácselemek példányosítása akkor és csak akkor történik, amikor először hivatkozunk az őket azonosító sor-oszlop párosra. Ehhez is kész eszközt kapunk a kezükbe a Collections Generic Libraryben: a LazyMap-et és az InstantiateFactory-t, melyek a Decorator és a Factory pattern alkalmazásával érik el céljukat.

Imhol a forráskód módosított része. Figyelemreméltó, hogy mennyi minden változott…
[cc_java]
public class Row {

protected final Map nodes = LazyMap.decorate(new WeakHashMap(), new InstantiateFactory(Node.class)); // !

}
[/cc_java]

És egy kis tesztosztály, a kiértékelés a kedves olvasó feladata:
[cc_java]
public class GridTest {

public static void main(String[] args) {
Grid grid = new Grid();
for (int i = 0; i < 2; i++) {
grid.getRows().add(new Row());
grid.getColumns().add(new Column());
}
for (Row row : grid.getRows()) {
for (Column column : grid.getColumns()) {
System.out.println(row.getNodes().get(column));
}
}
grid.getColumns().remove(1);
System.gc(); // !
for (Row row : grid.getRows()) {
for (Column column : row.getNodes().keySet()) {
System.out.println(row.getNodes().get(column));
}
}

}

}
[/cc_java]

Hát ilyen power a Collections Framework, amint már Stampie is [intlink id=”587″ type=”post”]rámutatott[/intlink], ami pedig esetleg hiányzik belőle, azt a Collections Generic pótolja. Mi a tanulság? Használjuk ki a nyelvi lehetőségeket, amelyek rendelkezésünkre állnak!