Refaktoring – zato što znam da pišete loš kod
Autor: Ivan Đurđevac |
Datum:
  • # PHP Science

Svi mi koji provodimo vreme gledajući u kod kako bismo razvili korisne aplikacije, znamo koliko je važno da one budu funkcionalne i da jednostavno rade onako kako to korisnici žele i očekuju. Ali, isto tako znamo i ono što menadžere i naše korisnike nimalo ne zanima, znamo koliko je važno da kod koji pišemo bude kvalitetan.

Dešava li vam se da počnete da razvijate aplikaciju puni entuzijazma i sjajnih ideja kako ćete rešiti sve probleme i zahteve koju su ispred vas kao programera postavljeni? Nakon nekoliko meseci klijent je promenio neke zahteve, aplikacija je sve složenija i neki delovi koda više nisu tako čitljivi i jasni kakvi su bili na samom početku. Vreme prolazi a vaša aplikacija ima sve više delova u koje niko ne želi da zalazi, a onaj ko se usudi mora biti naoružan strpljenjem i jakim živcima.

Da bi vaš kod ponovo bio čitljiv, lak za razumevanje i održavanje potrebno je da ga refaktorišete. Teorija kaže da je refaktoring izmena izvornog koda pri čemu sam izlaz aplikacije ostaje isti, što znači da ćete ispraviti uočene nedostatke u arhitekturi aplikacije i učiniti je nadogradivom, a vaš kod čitljivim i jednostavnim. Jako je važno da se ova tehnika primenjuje često, gotovo svakodnevno. Refaktoring treba da bude sastavni i neizostavni deo razvoja.

Standardi u PHP zajednici

Vaš tim bi trebalo da definiše određena pravila koja ćete svi poštovati. Prihvatite jedan standard i držite se njega. U PHP jeziku postoji više standarda u pisanju koda, što sam izbor čini težim.

Kako bi PHP zajednica prevazišla poteškoće koje nastaju usled korišćenja različitih standarda, osnovana je grupa koje će se baviti uvođenjem preporučenih standarda u PHP zajednici - Framework Interoperability Group (FIG). Sastavljena je od vodećih ljudi iz zajednice. Usvojena su tri dokumenta koja definišu preporučene standarde (PHP Standards Recommendations): PSR-0, PSR-1, PSR-2 i PSR-3.

PSR-0 je skup propisa koji definišu učitavanje objekata. Poštovanjem ovih propisa postiže se da jedan autoloader instancira objekte iz klasnih biblioteka i različitih okruženja. Problem učitavanja objekata najbolje razumeju oni koji su pokušali da u okviru Codeigniter aplikacije koriste komponentu Symfony Event Dispatcher i istovremeno neku od Zend klasa.

PSR-1 je osnovni standard pisanja koda, koji ima za cilj da postigne visoku operabilnost između deljenog koda. Nadovezuje se na PSR-0 standard.

PSR-2 je proširena verzija PSR-1 standarda sa ciljem da se smanje razlike između koda pisanog od strane različitih autora. Ovi dokumenti će u bliskoj budućnosti postati de facto standard. Nove verzije FuelPHP i Laravel frejmvorka biće napisane u skladu sa PSR-1 standardom iako se njihov kod u trenutno aktuelnim verzijama drastično odstupa od ovog standarda. Zend i Symfony već podržavaju usvojene preporuke PSR-2 standarda u potpunosti.

Ove tri grupe preporuka definišu temelje za pisanje čitljivog PHP koda. Poslednje usvojeni standard PSR-3 je otišao korak dalje i definisao interfejs za logovanje. Biblioteku koja implementira PSR-3 interfejs za logovanje jednostano možemo zameniti sa bilo kojom bibliotekom koja je implementirala isti interfejs. Monolog biblioteka se već prilagodila ovim preporukama i implementirala interfejs Psr\Log\LoggerInterface. Definisanje preporuka koje definišu interfejse pomiriće razlike u PHP zajednici.

Kod je vlasništvo tima, a ne pojedinca

Ustanovite i poštujte pravilo da je kod zajedničko vlasništvo svih članova tima. To što je jedan programer pisao metodu i funkcionalnost ne znači da tu niko ne sme da zalazi, ili da je on zadužen za određenu funkcionalnost. Preporučljivo je da se programeri prepliću, tako da jedan nadograđuje funkcionalnosti koje je završio prvi programer. Na ovaj način postići ćete da svaki programer bolje poznaje svaki deo aplikacije. U slučaju da jedan od vas reši da otputuje u Costa Ricu, ostali neće imati problema da nastave da rade na aplikaciji, jer neće postojati ni jedan deo aplikacije za koji je bio zadužen samo jedan član tima.

Klasa ima jednu odgovornost, metoda samo jednu funkcionalnost

Svaka klasa koju napišemo, mora imati jednu i samo jednu odgovornost. Ako imate više od jednog razloga da promenite klasu, vaša klasa ima i više od jedne odgovornosti. Ovo pravilo je poznato kao Single Responsibility Principle, i predstavlja prvo slovo S u SOLID principima. Ovaj princip je primenjiv kako na klase, tako i na metode, pa i module aplikacije.

Ukoliko imate klasu koja će se baviti platama radnika u vašoj firmi, pomislićete da je sve u redu jer ona ima samo jednu odgovornost - plate radnika. Zamislimo da ova klasa ima dve metode, jednu koja će praviti proračun plate za određenog radnika i drugu koja će štampati izveštaj koji radnici dobijaju na kraju svakog meseca. Ovakva klasa može biti izmenjena jer je došlo do promene dizajna platnog isečka i može biti promenjena jer od sledećeg meseca radnici koji budu koristili bolovanje duže od 20 dana, za vreme bolovanja primaće samo 25% plate umesto dosadašnjih 50%. Jasno je da ova klasa zapravo ima više od jedne odgovornosti i više od jednog razloga da bude izmenjena. Ispravno bi bilo da imamo klasu koja će se baviti proračunima radnika, i drugu klasu koja bi bila zadužena za štampanje platnog isečka.

Na ovom jednostavnom primeru možete videti da vam se može desiti da u toku pisanja klase budete ubeđeni kako vaša klasa ima dodeljenu samo jednu odgovornost, a da tek kada je budete sledeći put menjali shvatite da ih ipak ima više. Single Responsibility Principle važi i za metode i klase.

Ako ste metodu komentarima podelili na više segmenata kao u sledećem primeru, to je jasan znak da bi svaki segment trebalo da se refaktoriše, gde bi rezultat bila zasebna metoda koja će imati samo jednu funkcionalnost.

public function sellProduct($purchaser, $numberOfItems)
{
    // check if purchaser has enough money
    if ($purchaser->money < $product->price * $numberOfItems)
    {
        throw Exception('You do not have enough money');
    }
    // check if we have enough items on stock
    if ($product->stock < $numberOfItems) 
    { 
        throw Exception('Sorry, we do not have enough items'); 
    }
    // Calculate taxes 
    $taxLocal = $tproduct->price * $taxLocalCoefficient;
    $taxGlobal = $product->price * $taxGloballCoefficient;
    $taxes->save($product, $taxLocal, $taxGlobal);
    // Add to cart
    $this->addToCart(product, $numberOfItems);
}

Dužina jeste bitna

Važno je da vaše klase i metode ostanu male i fleksibilne bez obzira na to što se aplikacija razvija dugi niz godina i što je pretrpela brojne izmene. Moraću da budem veoma iskren i da vam kažem - dužina je bitna.

Postoje mišljenja da klasa ne bi trebalo da ima više od 100 linija koda, metoda 20 a namespace do 15 klasa.

Što klasa ima više linija koda članovima tima je teže da u svakom trenutku znaju sve potpise metoda u klasi. To znači da im je teže da pronađu metodu koja ima je potrebna i troši se više vremena na razvoj.

Ako vaša metoda ima više od 20 linija koda, razmislite o refaktoringu i podeli na nekoliko pomoćnih metoda. Ako je broj linija višestruko veći, onda je stanje alarmantno i refaktoring bi trebalo da uradite ovog momenta. Da, potrebno je da zastanete sa čitanjem ovog članka i da se vratite nakon što "stanjite" problematičnu metodu.

PHP je od verzije 5.3 bogatiji za namespace opsege. Namespace ima ulogu da logički podeli klase i spreči koliziju sa istim imenima klasa. Njihova najvažnija uloga jeste da na okupu drži određene klase, a da nama koji ih koristimo pomogne da u svakom trenutku znamo gde se nalazi koja klasa. Ako previše klasa objedinimo u jedan namespace, nećemo moći da zapamtimo koja klasa pripada kom namespace opsegu i funkcionalnost koja bi mogla da nam pomogne okrenućemo protiv sebe.

Komentarisanje koda je veština koja se uči

U vašem kodu postoje određena ključna mesta koja mogu zbuniti onoga ko tumači kod. Na ovim mestima je potrebno ostaviti komentar koji će jasno i nedvosmisleno objasniti šta ste želeli da postignete određenim kodom. Ne treba ići u krajnost i komentarisati svaku liniju koda. Posebno ne treba komentarisati linije koda koju su sasvim jasne i bez komentara. Ako je bloku koda potreban komentar, onda odvojite vreme da taj komentar bude kvalitetan i da onome ko ga čita pomogne da razume šta je njegova svrha.

Pisanje kvalitetnih komentara je veština koja se uči. Kada vaš kolega naiđe na vaš komentar i traži dodatno pojašnjenje, važno je da uradite sledeće:

  • Odustanite od namere da mu usmeno objasnite komentar
  • Prepravite komentar onako kako biste ga usmeno objasnili kolegi
  • Vratite ispravljeni komentar kolegi kako biste videli da li će mu sada pojasniti šta se dešava u kodu
  • Ponavljajte proces sve dok mu komentar ne razjasni šta se dešava u kodu

Na ovaj način postići ćete sledeće:

  1. Naučićete kako da pišete komentare koji su razumljivi i koji postižu svoj cilj, a to je da programerima koji ga čitaju pomognu da razumeju ono što nisu uspeli iz samog koda.
  2. Ispravićete komentar tako da će on sada osim što će pomoći kolegi koji trenutno radi na njemu, biti od koristi i svima ostalima koji se sa njim susretnu.
  3. Pomoći ćete samom sebi da protumačite kod koji ste pisali ranije.

DocBlock

DocBlock je posebna vrsta komentara koja će nam pružiti važne informacije o elementu koji komentarišete. Sa DocBlock-om komentarišite svaku klasu i metodu u aplikaciji, bez izuzetka. Ovaj standard u komentarisanju podržavaju sva razvojna okruženja za PHP jezik: Eclipse, Zend Studio, NetBeans, PHPStorm, Komodo, PHPEdit i drugi.

/**
* This is the short description for a DocBlock.
*
* This is the long description for a DocBlock. This text may contain
* multiple lines and even some _markdown_.
*
* * Markdown style lists function too
* * Just try this out once
*
* The section after the long description contains the tags; which provide
* structured meta-data concerning the given element.
*
* @author Mike van Riel
*
* @since 1.0
*
* @param int $example This is an example function/method parameter description.
* @param string $example2 This is a second example.
*/

Sa phpDocumentator-om veoma lako ćete generisati API dokumentaciju za vašu aplikaciju. Ako ste koristili DocBlock komentare, bili detaljni i pedantni, vaša dokumentacija biće savršena. Ovakva dokumentacija pomoći će vam da ponovo pregledate vaš kod iz druge perspektive i možda uočite neke nedostatke kao što su dve različite metode koje zapravo imaju istu namenu.

Magični brojevi su zli!

[caption id="attachment_280" align="alignleft" width="300"]Magični brojevi Magični brojevi[/caption]

Magičnim brojevima nije mesto u vašem kodu. Brojevi u kodu nisu magični zato što mogu da ispunjavaju želje, već zato što su programeri koji čitaju programski kod sa magičnim brojevima ubeđeni da je samo magija mogla da ih umetne u programski kod, i da ne postoji nikakvo logično objašnjenje zašto se oni tu nalaze. Pogledajmo sledeći primer.

if ($member->getPermissionLevel() < 5)
{
     // Do some stuff
}

Da li možete da mi kažete kada će biti ispunjen uslov u ovom jednostavnom primeru? Ono što treba da uradimo jeste da magične brojeve zamenimo sa konstantom pa će kod da bude neuporedivo jasniji.

if ($member->getPermissionLevel() < ROLE_EDITOR) 
{ 
   // Do some stuff 
}

Sada je jasno da će se kod izvršiti samo ako član ima ulogu urednika ili višu. Važno je da kod bude što čitljiviji, jer na taj način štedimo vreme i živce programerima koji će čitati kod koji smo mi pisali. Osim čitljivosti, u ovom primeru ćemo ispoštovati i DRY princip, koji je jedan od osnovnih principa programiranja. DRY princip kaže da se informacija može i treba nalaziti samo na jednom mestu.

define('ROLE_EDITOR', 5);

Kada prikazujemo veliki broj podataka na stranici, koristićemo paginaciju da ne bi učitavali sve podatke na istoj stranici.

$blog_posts = $model->getData(10); // Blog post limit to 10

Na prvi pogled ne možemo biti sigurni šta predstavlja broj deset.

$blog_posts = $->getData(Config::get("posts_per_page"));

Sada je jasnije da je programer želeo da prikaže deset postova po stranici. Prednosti ovako refaktorisanog koda su:

  • U slučaju izmene ne menja se klijentski kod, već samo konfiguracija
  • Konfiguracija je centralizovana, pa ćemo kada treba da nešto podešavamo u aplikaciji, znati gde treba da gledamo - u konfiguracionim fajlovima
  • Kod je jasniji i štedimo vreme potrebno za njegovo tumačenje.

Dajte prednost objektima

Ako vaša metoda treba da vam vrati više od jednog rezultata, može vam se učiniti kao sjajna ideja da rezultate smestite u jedan niz i da metoda vrati taj niz kao rezultat. Jedini izuzetak kada metoda može i treba da vrati niz jeste kada ona radi sa nizom podataka.

function getImageInfo()
{
    return array['width' => 45, 'height' => 22, 'title' => 'This is image'];
}

Šta je loše u tome što je metoda vratila niz ?

  • Korisnik metode getImageInfo mora da zna strukturu niza
  • struktura niza ne može da se menja jer je u tesnoj vezi sa klijentskim kodom koji koristi ovu metodu

Ono što metoda treba da vrati kao rezultat jeste objekat tipa ImageInfo, jer smo u prethodnom slučaju simulirali objekat uz pomoć niza.

function getImageInfo()
{
    return new ImageInfo(45, 22, "This is image");
}

Sada kada je metoda vratila objekat a ne niz prednosti su:

  • Klijentski kod treba da poznaje samo public metode da bi došao do podataka
  • Podaci su sada enkapsulirani u objektu ImageInfo i objekat se lakše menja i nadograđuje
  • Ako želimo da još više oslabimo vezu između klijentskog koda i samog objekta, objekat može da implementira interfejs, što će nam omogućiti da objekat po potrebi zamenimo drugim.

Daj mi lepo ime, baš po mojoj meri (i funkcionalnosti)

Metode bi trebalo da budu deskriptivne i da rade isključivo ono što njihovo ime govori. Ako ne možete da nađete u trenutku pravo ime za metodu tražite savet od kolege, jer u timu bi trebalo da izgradite zajednički odnos prema imenovanju metoda i klasa. U početku ćete trošiti više vremena na pravilno imenovanje metoda, ali će na kraju proces preći u automatizam.

Mislite da ste dovoljno pametni i dobri developeri i da svakoj vašoj metodi i klasi dajete idealna i intuitivna imena koja u potpunosti jasno i nedvosmisleno naglašavaju funkcionalnost metode? Proverite to tako što ćete pitati kolegu šta misli da radi metoda koju ste upravo napisali. Još je važnije da, ako vam kolega kaže da je imao problema da reši bag ili da se snađe u kodu zato što je mislio da metoda radi nešto sasvim drugo, shvatite da je vreme da toj metodi date ispravniji naziv. Tada zajedno sa vašim timom raspravite o novom nazivu za metodu. Vreme potrošeno za razgovor o boljem imenovanju metode nikako nije potrošeno vreme, jer će umanjiti probleme u budućnosti, poboljšati vaš kod i vaš tim učiniti jedinstvenijim.

Nemojte davati privremena imena metodama samo zato što u tom trenutku ne možete da nađete bolji naziv. Male su šanse da ćete se vratiti i menjati ime metode kao i sve njene pozive. Zato je u samom startu potrebno odrediti dovoljno dobar naziv koji odgovara metodi.

Bez refaktoringa nema kvalitetnog razvoja

Ono što menadžeri ne znaju jeste da je vreme uloženo u refaktoring isplativo, jer će vaš kolega mnogo više provesti u tumačenju i debagovanju mračnih delova koda, nego što biste vi proveli da taj kod učinite lepim i čitljivim. Menadžer vam nikada neće reći da odvojite vreme i da refaktorišete kod. Naprotiv, reći će kako nema vremena i kako morate da nastavite sa razvojem novih funkcionalnosti, a da trenutne probleme na aplikaciji zakrpite. Ipak vi ste programer i vaša je dužnost da refaktorišete kod, kada god je to potrebno. Ako je prošlo nedelju dana i vi niste refaktorisali ni vaš kod, nemojte ni pomisliti kako ste počeli da pišete savršen kod. Istina je da što ste bolji programer više ćete refaktorisati kod.

Osim u svakodnevnom radu, nedostatke u vašem kodu najbolje ćete uočiti na Code Review sesijama. Code Review jeste vreme rezervisano za developere i njihov kod. Tada će ceo tim analizirati ključne delove koda, predlagati rešenja i zadržaće se na svakom detalju. Ovo je idealna prilika da se uoče propusti i mesta za refaktoring, ali i da se junior programeri obrazuju. Bilo bi dobro da se ovakve sesije održavaju što češće, bar dva puta mesečno. Jako je važno da na ovim sesijama nema mesta za Menadžere - developers only.

Ove smernice će vam pomoći da postanete bolji i kvalitetniji programer kojeg će svako želeti u svom timu. Refaktorišite vaš kod svakodnevno, jer je on neizostavni deo kvalitetnog razvoja softvera.

Pogledajte kod koji ste pisali pre pola godine. Sjajna je vest ako ga smatrate lošim jer to znači da ste napredovali, u suprotnom imate dovoljno razloga da se zapitate da li napredujete kao developer. Refaktoring će od vašeg koda napraviti još bolji a od vas još bolje programere.