1. Objektumorientált
mérések a gyakorlatban
Orcsik Antal
Arkon Zrt.
1
Orcsik Antal vagyok, lassan 8 éve dolgozom az Arkonnál, mint PHP fejlesztő.
2000 óta foglalkozom weboldalak fejlesztésével.
5 éve vagyok a köpönyeg.hu fejlesztője.
2011. július óta futtatunk méréseket a köpönyeg kódján, rögtön elkezdett érdekelni, hogy
pontosan mi is ez, és miért jó.
2. • hatékonyan működjön
• legyen átlátható
• könnyen újra lehessen hasznosítani
• gyorsan lehessen bele fejleszteni
• (minél inkább dokumentálva legyen)
Milyen a jó szoftver (kód)?
2
Alapvetően a piac követeli meg, hogy a szoftvert alkotó kód:
• hatékonyan működjön
Persze attól még, hogy jól működik a szoftver nem biztos, hogy jó a kód is!
• legyen átlátható
• könnyen újra lehessen hasznosítani
• gyorsan lehessen bele fejleszteni
• (minél inkább dokumentálva legyen)
Ezeknek a szempontoknak a fontossága pedig sokszorosára növekszik, ha a webről
beszélünk, ahol a folyamatos karbantartás és fejlesztés a mindennapi munka része. Az oldalak
aktív, nagy létszámú közönséget szolgálnak ki és óhatatlanul előfordulnak hibák, amiket
villámgyorsan kell javítani. Ha lehet, már kezdetektől fogva figyelni kell erre, egy megörökölt
rendszer esetében pedig talán még fontosabb.
3. • Tesszük a dolgunkat és ha valami
nagyon nagy szívás, ott gond van
• Nekieshetünk átnézni az egészet,
antipatternekre vadászva
• Mérjük meg…
Honnan tudhatjuk, hogy gond van
a kódunkkal?
3
Vegyük azt az esetet, hogy már van egy rendszerünk, mivel konkrét tapasztalaim ezena téren
vannak.
Feltételezhetjük, hogy az egész kódunk azért nem egy rakás szemét, mert az már messziről
látszik. Szeretnénk tehát kideríteni, van-e olyan része amin érmes lenne a korábban említett
szempontok miatt javítani.
1. Tesszük a dolgunkat és ha valahol nagyon nagy szívás van, ott gond is van
remekül fog kinézni, mikor egy gyorsjavítás kapcsán közöljük, hogy itt lenne pár nap extra
munka, mert csúnya a kód, nem is beszélve a lehetséges függőségekről, meg arról, hogy
hány dolgot fogunk közben eltörni. Jobb lenne talán ezt megelőzni...
2. Nekieshetünk átnézni az egészet, antipatternekre vadászva
ebből már születhetnek jó ötletek, átgondolt refaktorálási storyk, de meglehetősen
időigényes dolog, és kisebb dolgok, vagy nagyobb összefüggések elkerülhetik a
figyelmünket. Biztos van valami jobb megoldás...
3. Mérjük meg…
4. • Chidamber & Kemerer (1994)
A metrics Suite for Object Oriented Design
• Li & Henry (1993)
Maintenance Metrics for the Object Oriented
Paradigm
• Thomas J. McCabe (1976)
A Complexity Measure
Mit, hogyan, mivel?
4
Na persze, de mégis mit, és főleg hogyan?
Nem kell nekünk feltalálni a spanyol viaszt, az objektumorientált rendszerek mérhetőségével
kapcsolatban íródott már néhány remek publikáció a '90-es években (amikre egyébként
minden későbbi hivatkozik).
• [Chid94] Chidamber & Kemerer: A metrics Suite for Object Oriented Design
• [LiHe93] Li & Henry: Maintenance Metrics for the Object Oriented Paradigm
Amik elsőként fogalmazták meg mit lehet és kell is mérni, illetve, hogy az eredményeket
hogyan értékeljük. Meg kell viszont említenünk még egy nagyon fontos művet
• [McCa76] Thomas J. McCabe: A Complexity Measure
Thomas J. McCabe '76-ban megalkotta a Ciklomatikus Komplexitás fogalmát, azóta is ez az
alapja mindennek. Erre még később részetesebben is kitérek.
5. • Manuel Pichler (@manuelp)
http://www.manuel-pichler.de
• phpmd (PHP Mess Detector)
http://phpmd.org
• pdepend (PHP Depend)
http://pdepend.org
Mit, hogyan, mivel?
5
Ha már tudjuk mit kell mérni, akkor már csak eszközök kellenek. Ezeket sem kell nekünk
megírni, ezt megtette már helyettünk
Manuel Pichler (@manuelp) - http://www.manuel-pichler.de
Maguk az eszközök pedig:
• phpmd (PHP Mess Detector) - 2009
• pdepend (PHP Depend) - 2011
A pdepend Java és C programozók számára ismerős lehet, ezekhez a nyelvekhez is létezik ez
az eszköz.
Először a Mess Detectorról lesz szó.
6. • Unused Code Rules
• Unused Formal Parameter
• Unused Private Method
• Unused Private Field
• Unused Local Variable
PHP Mess Detector Rules 1.
function example($a, $b) {
return $a * 2;
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
6
A phpmd arra való, hogy feltérképezze a kódunkat és szóljon, ha valami szerinte nincs
rendben. A több tucat csomagokba rendezett szabály van, melyeket a kódunkra
alkalmazhatunk. A teljes lista elérhető a pmd online dokumentációjában (http://phpmd.org/
rules/index.html), lássunk a fontosabb csoportokat és néhány szabályt, amit én is használok:
• Unused Code Rules, nem használt kódrészletek
◦ Unused Formal Parameter
◦ Unused Private Method
◦ Unused Private Field
◦ Unused Local Variable
Ezek ugyan nem tűnnek komoly dolgoknak, de sokszor segítettek egy-egy hiba
felfedezésében. Volt egy eset, mikor elírtam egy változó nevét egy metódusban, a pmd pedig
szólt, hogy nem használom a metódus egyik paraméterét. Ezek sárga színnel jelennek meg a
listában, és csak figyelmeztetések, de kijavításuk általában egyszerű így ezt érdemes minél
hamarabb megtenni!
Vannak szükségtelennek tűnő figyelmeztetések is, pl. mikor egy interface megkövetel egy
paramétert a metódusban, de mi azt nem használjuk. Itt trükközhetünk is, de pont az ilyen
esetekre létezik a metódus vagy osztály szintű szabály elnyomás, amit a fejléc commentbe kell
tenni.
7. • Naming Rules
• Controversial Rules
• Design Rules
• Code Size Rules
• Exessive Method Length
• Cyclomatic Complexity
PHP Mess Detector Rules 2.
7
• Naming Rules
• Controversial Rules
Ezek a szabályok a metódusok és változók elnevezéseivel és használatával kapcsolatos
következetességre figyelnek, túl rövid vagy hoszzú változónevek, CamelCase, getX(), isX().
• Design Rules
Ezek arra figyelnek, hogy ne legyen a kódban exit, eval vagy goto, illetve bizonyos
öröklődéssel kapcsolatos jellemzőket, amikről majd később még lesz szó.
• Code Size Rules, pedig a kód méretével kapcsolatos szabályokat tartalmazza, pl.:
◦ Exessive Method Length, a különösen hosszú metódusokat nézi
◦ Cyclomatic Complexity, ami a metódusok Ciklomatikus Komplexitását figyeli, térjünk is
ki egy kicsit erre
8. • E: élek (5)
• N: pontok (5)
• P: kilépési pontok (1)
Cyclomatic Complexity 1.
CYCLO = E - N + 2P = 2
8
Korábban már említettem, hogy bizonyos Thomas McCabe nevéhez fűződik ez az érték. Ezzel
lehet a legjobban jellemezni egy kódrészlet, metódus vagy osztály bonyolultságát. Nem nehéz
kiszámolni sem, képzeljünk el a folyamat ábrát. Ez ugye egy gráf, élekkel és pontokkal. Ebből
a Ciklomatikus Komplexitást úgy kapjuk meg, ha az élek számából kivonjuk a pontok számát
és hozzáadjuk a kilépési pontok számának kétszeresét.
• E: élek
• N: pontok
• P: kilépési pontok
CYCLO = E - N + 2P
9. Cyclomatic Complexity 2.
function example($a) {
if ($a) {
for ($i=1;$i<$a;$i++) {
if ($i%2 && $a>10) {
echo $i;
}
}
}
}
• minden metódus alaból 1
• minden elágazás és kör +1
9
Na pesze senki ne ijedjen meg, lehet ezt egyszerűbben is:
• minden metódus komplexitása alapból 1
• amihez annyiszor adunk még egyet, ahány elágazás és kör van a kódban
Egy metódus komplexitása annak bonyolultságát határozza meg. Egy túlságosan komplex
metódus valószínűleg többet csinál, mint amit kéne neki, érdemes lehet szétszedni több
részre. Na de, mi számít túlságosan komplexnek? Mondjuk a 10.
Mikor 2011-ben bevezettük a phpmd-t, én is elkezdtem figyelni erre a dologra, és ma már úgy
kódolok, hogy fejben számolom a komplexitást. Ha egy metódus eléri a 10-et, akkor újra
átgondolom amit csinálok, és sok esetben sikerül egy nagyobb részletet kiszervezni úgy, hogy
azt máshol is fel tudom használni. Ezen felül leszoktat a terjengős elágazás erdőkről, és
megtanít rá, hogy kétszer is meggondoljuk mit és mikor vizsgálunk.
10. Cyclomatic Complexity 3.
if ($cond) {
$result = 1;
} else {
$result = 0;
}
$result = 0;
if ($cond) {
$result = 1;
}
switch ($cond) {
case “a”: $x = 1; break;
case “b”: $x = 2; break;
default: $x = 3;
}
$switch = array(
“a” => 1,
“b” => 2,
);
$x = 3;
if (isset($switch[$cond])) {
$x = $switch[$cond];
}
10
Mutatok még két nagyon egyszerű példát, komplexitás csökkentésre.
11. Tapasztalat
11
A szabályok finomhangolása után a köpönyeg nagyjából 400 pmd hibát tartalmazott, amit az
elmúlt egy évben refaktorálással körülbelül 100-ra csökkentettem, és a cél természetesen,
hogy végül egy se maradjon, bár vannak tipikusan kihívást jelentő komplexitás és metódus
hossz hibák.
Egyik-másik parser vagy ábra rajzoló a 20-30 komplexitást is eléri, amit csak igen kemény
munkával lehet rendbetenni.
12. PHP Depend
Áttekintő piramis
Absztrakció
Instabilitás
grafikon
12
Térjünk át akkor a következő eszközre.
A korábban említett publikációk számos értéket határoznak meg, a PHP Depend, a
dokumentéciója szerint (http://pdepend.org/documentation/software-metrics/index.html), közel
40 olyan metrika kiszámítására képes, amikből a kódunk minőségére következtethetünk,
viszont most csak néhányat emelnék ki.
A program két összefoglaló ábrát készít a kódunkról
• az Áttekintő piramist és
• az Absztrakció Instabilitás grafikont
Kezdjük az előzővel.
13. Piramis - Code Size
• CYCLO: Cyclomatic Complexity
• LOC: Lines of Code
• NOM: Number of Methods
• NOC: Number of Classes
• NOP: Number of Packages (Namespaces)
13
A piramis három sarka három mérési kategóriát jelöl, bennük pedig a mért adatok és az azok
hányadosaiból számolt értékes metrikák találhatóak:
• Code Size (a kód méretei) A komplexitás mellet nagyon fontos még a kód mérete, amit
PHP esetén négy értékkel határozunk meg:
◦ CYCLO: Cyclomatic Complexity, ismét itt
◦ LOC: Lines of Code, azaz a sorok száma
◦ NOM: Number of Methods, azaz a metódusok száma
◦ NOC: Number of Classes, azaz az osztályok száma
◦ NOP: Number of Packages (Namespaces), azaz a csomagok, vagy 5.3 óta a
névterek száma
Ezeknek viszont a hányadosai hordozzák a fontos információt.
A CYCLO/LOC egy sor átlagos komplexitása a LOC/NOM pedig egy metódus átlagos hossza.
Az előző 0.2 alatt van, az utóbbi pedig 13 alatt, akkor vagyunk a zöldben, de ez azt is jelenti,
hogy átlagosan 2,6 komplexitása lehet egy metódusnak, ha a zöldben szeretnénk maradni. Ez
jóval kevesebb, mint a 10, amit a phpmd-nél mondtam, de látszik, hogy arra kell törekedni,
hogy minél egyszerűbb és érthetőbb legyen egy metódus.
Nekem a LOC/NOM okozta a legtöbb gondot, mert a köpönyeg elég sok procedurális kódot
tartalmazott az MVC átírás előtt. Konkrétan 26-ról kellett szépen lassan ezt a számot lefelé
terelgetni.
Ököl szabály, hogy a kisebb jobb. Szóval a legjobb a kódunk akkor lesz, ha minél több
névteret hozunk létre, bennük lehetőleg kevés osztályt, néhány egyszerű és rövid metódussal.
Ezzel így valami nem stimmel, de majd később visszatérünk erre.
14. Piramis - Coupling
• CALLS: metódus hívások
• FANOUT: típus kényszer,
a rendszer más részéből
14
• Coupling (kapcsolódás) a program egyes részei közötti kapcsolatot is mérhetjük
◦ CALLS: ez különálló metódushívások száma
◦ FANOUT: ez pedig a metódusok paraméterlistájában lévő típus kényszereket számolja,
ha azok az alkalmazás egy másik ágához kapcsolódnak
A kapcsolódás már keményebb dió, itt azt kell megértenünk, hogy ha a CALLS/NOM, azaz a
kapcsolódás intenzitása, nagy az azt jelenti, hogy az egész szoftverünk durván néhány
metódusból áll, amik folyton egymást hívogatják. Ez arra utalhat, hogy nagyon kusza a
szoftverünk részegységei közötti kapcsolat, ami nem csak a hibák felderítésében okozhat
gondokat, de egy apró baki is képes lehet rendszer szintű gondokat okozni.
15. Piramis - Inheritance
• ANDC: Average Number of
Derived Classes (szélesség,
intenzitás)
• AHH: Average Hierarchy
Height (mélység)
15
Itt nincsenek hányadosok, ezt a két átlagot önmagában kell vizsgálnunk. Itt is érvényes az
ökölszabály, hogy a zöld a jobb, de árnyaltabb a kép.
• Inheritance (öröklődés) az öröklődést is leírhatjuk két értékkel
◦ ANDC: Average Number of Derived Classes, azaz a származtatott osztályok átlagos
száma. Ez az osztályok számának súlyozott átlaga, ahol minden osztály súlya a
közvetlen leszármazottainak száma.
Ez a szám az örökési rendszerünk szélességét mutatja meg, hogy mennyire vesszük
igénybe az öröklést, és annál kisebb, minél több a gyermektelen osztály.
◦ AHH: Average Hierarchy Height, azaz az öröklési hierachia átlagos magassága. Ez a
gyökér osztályok számának súlyozott átlaga, ahol minden osztály súlya a belőle kiinduló
öröklési fa legnagyobb mélysége.
Ez a szám pedig a arról tájékoztat, milyen mennyire mélyek az öröklések, és annál
kisebb, minél kevesebb a mély öröklési fa.
Az ANDC esetében pl. csúnyán a narancsba kerülünk, ha egy db absztrakt objektumból
örököltetünk mindent. De hát, miért ne tennénk, láttunk már ilyet, hogy minden egy Base object
leszármazottja, és ennek megvannak a jó oldalai is. Ennek ellenére ez a mérési rendszer azt
javasolja, az osztályaink inkább legyenek különállóak, mintsem egy terebélyes hierachia
részei, a pmd még figyelmeztetni is tud, ha ilyesmire utaló jeleket talál!
16. PHP Depend - Low, Avg, High!
16
Ez is most nagyon furcsa, hiszen van nagyon jó és szép rendszer, ami intenzíven alkalmazza
az öröklést, akkor ez mégis miért rossz? Nem az.
Nézzük meg a színkódot:
szürke: alacsony
zöld: átlagos
narancs: magas
A piramis öröklés sarkára különsen igaz, de a többire is, hogy a narancs nem “rossz”, hanem
“magas”. Ez az ábra pedig nem azt mutatja meg, hogy a kódunk milyen rossz vagy jó, hanem
azt, hogy “milyen”. Áttekintést ad arról, hogy a kódunkban milyen intenzitással használunk
öröklést, óriási osztályink, vagy csak kicsik, hosszú rövidek-e a metódusaink vagy hosszúak
és bonyolultak?
Nagyon jó, ha a piramisunk zöld, de nem cél! Ne felejtsük el, hogy az ideák kergetésében
nagyon könnyű szemelől téveszteni a célt.
17. Absztrakció és Instabilitás 1.
• ac: absztrakt osztályok
• cc: konkrét osztályok
A = ac / (ac + cc)
• Ce: efferens kapcsolatok
• Ca: afferens kapcsolatok
I = Ce / (Ce + Ca)
17
Ezen a látványos ábrán az egyes csomagokat (névtereket) láthatjuk az absztrakciójuk,
instabilitásuk és méretük függvényében.
Az absztrakció egyszerű, a csomagban található absztakt osztályok aránya az összeshez.
Minél több van belőlük, annál inkább jobbra helyezkedik el a kis golyó az ábrán.
A = ac / (ac + cc)
Az instabilitás már bonyolultabb. Vannak az Afferens kapcsolatok, azok a csomagok, amik
valahogy függenek a vizsgált csomag osztályaitól, illetve az Efferens kapcsolatok, azok a
csomagok, amiktől valahogy függenek a vizsgált csomag osztályai.
• Ca: Afferent Couplings, amik tőlem függenek
• Ce: Efferent Couplings, amiktől én függők
Az instabilitást úgy számolhatjuk ki, hogy az osztály saját függőségeinek számát elosztjuk az
összes függőséggel, vagyis egy csomag instabil, ha több indetől függ, mint amennyien tőle
függenek.
I = Ce / (Ce + Ca)
18. Absztrakció és Instabilitás 2.
?
18
Egy csomag akkor van az ideális zónában, ha az instabilitás és az absztrakció összege 1. Teht
egy olyam csomag, ami főleg másoktól függ, tipikusan ilyenek a kontrollerek, azokban ne
legyen túlsúlyban az absztrakt osztályok száma a konkrétakkal szemben. A másik oldalon
viszont, ha egy csomag főként absztrakt csomagokból áll, akkor inkább tőle függjenek más
csomagok, azaz legyen minél inkább stabil.
Jellemző, hogy olyan absztrakt csomag, amitől semmi nem függ nem vagyon van, hisz ki
gyártana ilyen csomagot?
Az már gyakrabban fordul elő, hogy egy konkrét csomag nem függ semmitől, sőt talán még
tőle függenek. Ezeket célszerű megvizsgálni és valahogy átszervezni.
19. Összefoglalás
• Előnyök
• sokkal gyorsabb, mint bármi más
• aprólékos (pmd)
• átfogó kép (pdepend)
• Hátrányok
• néha szigorú és ellentmondásos
19
A bemutatott két eszköz kiválóan alkalmas arra, hogy viszonylag gyorsan adjon információt a
kód állapotával kapcsolatban. Nálunk minden buildnek része, hogy ezek a mérések lefutnak,
hogy legalább élesítés előtt mindenképp lássuk, mit is csináltunk. Ha figyeljük a számokat
könnyen észrevehetjük, ha rossz irányba haladunk.
Előnyök
Hátrányok
Mindenképp javaslom, hogy futtassátok le ezeket a saját rendszereiteken is, ha még nem
tettétek és fontoljátok meg, hogy a talált problémákat kijavítsátok.
A személyes tapasztalatom, hogy az elmúlt évben úgy fejlesztettem, hogy ezeket szem előtt
tartottam és a köpönyeg ma sokkal jobb állapotban van, mint előtte, és fejleszteni is könnyebb.