2013. április 28., vasárnap

Sonar - A metrikák értelmezése 3.

Ahogy a múltkori bejegyzésemben is említettem, nemcsak a csomagok kialakításánál, hanem az osztályok és metódusok szintjén is érdemes odafigyelni a tervezésre!

A célunk, hogy a projekt osztályai minél függetlenebbek legyenek egymástól (laza csatolás), az osztályok metódusai pedig minél jobban kötődjenek az adott osztályhoz (erős kohézió)! Ha az osztályok szorosan kapcsolódnak egymáshoz, akkor az osztályokon végzett módosítások továbbgyűrűzhetnek a többi osztály felé és más osztályokra is hatással lehetnek. Ha pedig valamely osztály két metódusa nem használ közös attribútumokat ill. metódusokat, akkor valószínűleg nincs is közös tulajdonságuk, így ezeket két különböző osztályba kellene kiszervezni.

Afferent Coupling, Efferent Coupling

Az Afferent Coupling értéke azoknak az osztályoknak a számát adja, amelyek a kiválasztott osztályt használják, így ha a kiválasztott osztályban módosítások történnek, ezekre az osztályokra lehet majd hatással. Az Efferent Coupling értéke pedig azt mutatja, hogy a kiválasztott osztály hány másik osztálytól függ, azaz hogy hány másik osztályt importált be (vagy hivatkozott be).

Az alábbi példát tekintve, a ReaderInitializationException osztály Efferent Coupling értéke 1, mivel a funkcionalitásához csak egy másik osztályt, mégpedig az InitializationException osztályt használja fel és importálja be, amit a source fül alatt le is ellenőrizhetünk. Az Afferent Coupling értéke pedig 6, mivel ennyi osztály használja a ReaderInitializationException osztályt.



RFC (Response For Class)

Az RFC értéke az osztályok felelősségét mutatja, aminek az értéke az osztály minden metódusával ill. külső metódushívással (kivéve getter/setter) eggyel növekszik. Az alacsony RFC értékre és az oszlop diagram balra tolódására kell törekedni, általánosságban pedig elmondható, hogy csak a több száz RFC értékkel bíró osztályokra érdemes ránézni és a javításukat megfontolni.

 
Példaként nézzük meg az alábbi forráskód részletet, ahol is a TestRFC osztály RFC értéke 6 lesz a kommenteknél megjelölt hívások miatt.
public class TestRFC {
 //Empty TestRFC constructor   -> +1
 private Person p=new Person(); //new Person()  -> +1
 public void testMethod1(){ //testMethod1() -> +1
  System.out.println("test1"); //System.out.println(…) -> +1
 }
 public void testMethod2(){ //testMethod2() -> +1
  System.out.println(p.toString()); //p.toString()  -> +1
 }
}

LCOM4 (Lack Of Cohesion Of Methods)

Az LCOM4, a Single Responsibility Principle tervezési minta betartásához nyújt segítséget, miszerint minden osztálynak csak 1 felelőssége legyen ami hozzásegít ahhoz, hogy megszűnjenek a hatalmas osztályok és helyettük sok kicsi, könnyen tesztelhető, gyorsan átlátható és módosítható osztály jöjjön létre! Megjegyezném, hogy a munkám során már nem egyszer találkoztam 10.000 kódsort meghaladó osztályokkal, amik ugye nem javadoc-al voltak tele... :)

Az LCOM4 metrika arra a felépítésre épít, hogy az egymással kapcsolatos metódusok, ugyanazokkal az osztály szintű (nem metódus hatókörű) változókkal dolgoznak vagy egymást hívják. Egy kohézív osztályban tehát csak összetartozó metódusok találhatók, amelyek kizárólag az osztály funkciójának a biztosításáért dolgoznak. A nem kohézív osztályok egynél több felelősséggel is rendelkeznek, így ezeket az egyes felelősségek szerint több kisebb osztályba kell kiszervezni.

Mivel az LCOM4 az osztályonkénti felelősségek számát jelöli, ezért csak akkor tekinthető elfogadhatónak, ha az értéke 1. A 0 értékkel rendelkező osztálynak nincsen konkrét felelőssége, az 1-nél több értékkel rendelkező osztályoknak pedig több felelősségük van.

Az alábbi osztálynál - a standard szabály szerint számítva - az LCOM4 értékének 3-nak kellene lennie, így a megadott osztályt 3 másik osztályra kellene szétbontani, melyek a következő metódusokat ill. attribútumokat tartalmaznák:
  1. video, playVideo(), stopVideo(), restartVideo()
  2. audio, playAudio()
  3. autoSleep()
public class AudioVideoManager   {

 private Video video;
 private Audio audio;

 public void playVideo() {
  video.play(); 
 }  

 public void stopVideo() {
  video.stop();
 }

 public void restartVideo() {
  stopVideo();
  playVideo();
 }

 public void playAudio() {       
  audio.play(); 
 }
 
 public void autoSleep() {  
  System.out.println("auto sleep");   
 } 
}
A valós életbeli példákon nézve a tapasztalat azt mutatja, hogy standard szabály túl sok ’false positive’ értéket jelezne, ezért a Sonar egy feltunningolt LCOM4 algoritmussal dolgozik, miszerint ha az LCOM4==1, lehetséges hogy további szétbontás is szükséges azonban az LCOM4>1 esetén biztosan kijelenthető, hogy szét kell bontani az osztályt. A fenti kódrészletnél az LCOM4 értéke a módosított szabály miatt így nem 3, hanem 1 lesz.


Ha az LCOM4 widget-en az LCOM4>1 linkre kattintunk, akkor az osztályok kiválasztása után az LCOM4 tab aktivizálódik, ahol megtekinthetjük, hogy ezt az osztályt milyen metódusok mentén kellene kisebb osztályokra szétbontani.


A Sonar metrikák elemzése ezzel a cikkel véget ért, legközelebb egy kicsit más témával jelentkezem.