Thursday, 17 July 2014

Code Smells - Change Preventers

Ova klasa code smell-ova se odnosi na one koji otežavaju izmene u kodu i koji afektiraju više delova koda i sistema. Stoga, kada je potrebno uraditi izmenu koda, koja se tiče jedne semantičke celine, potrebno je izmeniti kod na više mesta.
Ovi code smell-ovi obično znače i da postoji jaka uvezanost (tight coupling) delova sistema, što znači da je prilično teško, ako ne i nemoguće, zameniti deo sistema pošto je svaki deo jako uvezan sa ostalim delovima sistema.
Takođe, ovi code smell-ovi često ukazuju da postoji slaba/nejasna raspodela odgovornosti (poor separation of concerns), kako među komponentama, tako i među klasama. U ovom slučaju, narušen je jedan od osnovnih principa objektno orijentisanog dizajna, a to je Single Responsibility Principle (SRP). Ovaj princip se odnosi na stepen kohezije klase i kaže da svaka klasa treba da ima jednu i samo jednu odgovornost i da ta odgovornost treba da bude u potpunosti enkapsulirana u toj klasi, pri čemu se odgovornost definiše kao razlog za izmenu.

Konkretni code smell-ovi koji potpadaju pod change preventers grupu su:
  • Divergent change
  • Shotgun surgery
  • Parallel inheritance hierarchy
  • Inconsistent abstraction level i
  • Conditional complexity

Divergent change

Ovaj code smell se odnosi na situaciju kada se klasa, najčešće, menja na dva ili više različita načina ili zbog dva ili više razloga, što predstavlja narušavanje SRP-a.

Shotgun surgery


Shotgun surgery podrazumeva situaciju koja se manifestuje tako da prilikom rešavanja nekog bug-a (ili dodavanja nove f-nalnosti), je potrebno izmeniti kod na dosta mesta, gde je lako zaboraviti neku izmenu. Ova situacija ukazuje na grešku u dizajnu, jer jedna izmena afektuje više klasa. Idealno, trebalo bi da postoji jedan-na-jedan veza između izmene i klase.

Parallel inheritance hierarchy

Ovo je specijalni slučaj shotgun surgery code smell-a, gde dodavanja klase u jednu hijerarhiju zahteva dodavanje klase u paralelnu hijerarhiju(e).


Obično se ovaj problem zaobilazi koristeći Move Method refaktorisanje, gde se metode iz klasa referencirane paralelne hijerarhije, prebacuju u korespodentne klase u inicijalnoj hijerarhiji.


Ovo, sa druge strane, može narušiti SRP, pošto se odgovornosti i logika iz jedne klase prebacuju u drugu klasu, u kojoj ne bi trebalo da stoje.
Druga solucija, je da se sve metode referencirane hijerarhije sažmu u jednu klasu. Ovo je prihvatljivo ako broj klasa u referenciranoj hijerarhiji nije prevelik.


Inconsistent abstraction level

Interface klase treba da pruža konzistentan nivo apstrakcije. Ovaj nivo konzistencije opada sa dodavanjem novih, dodatnih metoda klasi. Inconsistent abstraction level code smell se dešava na nivou klasa, gde public metode moraju imati kohezivan set metoda (refactoring: move method), a takođe se dešava i na nivou metode, gde implementacija metode mora biti na nivou apstrakcije jednom ispod onog koji ime metode implicira (refactoring: extract method).

Cyclomatic complexity

Cyclomatic complexity (ili conditional complexity) predstavlja softversku metriku koja ukazuje na meru kompleksnosti programa. Metrika je razvijena od strane Thomas J. McCabe, Sr i vrednost, na koju ukazuje, se često naziva McCabe index. McCabe index date metode predstavlja broj linearno nezavisnih putanja izvršavanja u okviru metode. Što je veći broj linearno nezavisnih putanja, to znači da je metoda kompleksnija, teža za razumevanje, održavanje i samim tim predstavlja metodu kandidata za refaktorisanja.
Prihvatljiv index je 1-5. Index 5-10 predstavlja metodu koja se može uzeti u razmatranje za refaktorisanje, dok index preko 10 ukazuje na metodu koja bi svakako trebalo da se uzme u razmatranje.
Takođe, potrebno je napomenuti da McCabe index za neke metode može biti visok zbog postojanja switch case naredbe unutar metode, koja drastično povećava broj linearno nezavisnih putanja izvršavanja. Ove metode, ako samo zbog switch case naredbe imaju visok McCabe index, nije neophodno uzeti u razmatranje, osim ako postoji mogućnost Replace conditional logic with Strategy refaktorisanja.

Koristan Visual Studio plugin, koji sadrži ovu softversku metriku, je CodeMaid i može se koristiti za potrebe merenja složenosti metoda neke klase (McCabe index).

Refaktorinzi koji se obično koriste kod cyclomatic complexity code smell-a su:


Dva mala saveta koja se mogu koristiti za poboljšanje čitljivosti koda su:

  • Extract boolean in if statement - radi čitljivosti, kompleksnu boolean logiku u if naredbi bi trebalo izdvojiti u posebnu metodu
  • Avoid negative conditionals (ItemIsValid vs ItemIsInvalid) - takođe zbog čitljivosti, naziv boolean metode koja se koristi u if naredbi treba biti afirmativan, umesto negativan

Friday, 30 May 2014

Code Smells - Object Orientation Abusers

Object Orientation Abusers je još jedna klasa code smell-a. Neki od code smell-ova koji potpadaju ovde su:
  • Switch Statements
  • Temporary Field
  • Refused Interface
  • Inappropriate Static

Switch Statements

Standardan code smell u dizajnu je česta upotreba switch case-a. Ako se switch-case naredba upotrebljava više puta za jednu promenljivu (flag), onda se ona može zameniti polimorfizmom, gde bi data promenljiva (obično enumeracija) mogla biti konvertovana u zasebnu strukturu klasa, gde bi vrednosti te enumeracije predstavljale zasebne klase. Sve metode koje su do tada bile definisane za datu klasu i koje su uzimale u obzir enum promenljivu, potrebno je prebaciti u zasebne klase i telo svake od njih predstavlja telo case dela za datu enumeraciju u datoj (staroj) metodi.
Postoje situacije koje dovode do naglog povećanja klasa kod zamene switch-case-a, polimorfizmom, a ne postoji puto switch-case-eva u telu date klase, pa bi trebalo u ovom slučaju razmisliti da li je potrebno sprovesti ovo refaktorisanje.

Temporary Field

Ako se neki atribut klase koristi samo da bi prenosio neko stanje izmedju metoda, to je code smell (npr. u okviru jedne klase, postoje dve metode, koje se moraju pozvati u odredjenom redosledu, da bi atribut(i) te klase bili validni)
Ovaj code smell se refaktoriše u:
Primer:
class Employee
{
  private decimal _earningsForBonus;
  // other fields and methods
  private decimal CalculateBonus()
  {
    return _earningsForBonus * BonusPercentage();
  }
  private void CalculateEarningsForBonus()
  {
    _earningsForBonus = YearToDateEarnings() + OvertimeEarnings() * 2;
  }
}
se može refaktorisati u:
class BonusCalculator
{
  private decimal _earningsForBonus;
  private decimal _bonusPercentage;
  public BonusCalculator(Employee employee)
  {
    CalculateEarningsForBonus(employee.YearToDateEarnings(),
    employee.OvertimeEarnings());
    _bonusPercentage = employee.BonusPercentage();
  }
  public decimal CalculateBonus()
  {
    return _earningsForBonus * _bonusPercentage;
  }
  private void CalculateEarningsForBonus(decimal ytdEarnings, decimal ytdOvertimeEarnings)
  {
    _earningsForBonus = ytdEarnings + ytdOvertimeEarnings * 2;
  }
}

Refused Inheritance

Ako klasa koja nasledjuje parent klasu i override-uje većinu public metoda parent klase, onda je to code smell i ta izvedena klasa verovatno ne treba da bude u tom delu hijerarhije. U tom slučaju, veza između izvedene i parent klase, najverovatnije treba da bude kompozicija (izvedena klasa -> parent klasa), umesto nasleđivanja. Takođe, moguće je da te public metode parent klase, koje override-uje izvedena, treba prebaciti u izvedenu.

Inappropriate Static

Statičke metode se ne mogu naslediti i dovode to tight couplinga sa klijentskim kodom, pa ih treba pažljivo koristiti. Njihovo korišćenje treba rezervisati za:
  • ponašanje koje nikad neće biti potrebno redefinisati i
  • za stateless operacije
Primeri su:
  • Proste matematičke operacije
  • Globalne konstante

Tuesday, 27 May 2014

Code Smells - Obfuscated code

Obfuscated kod predstavlja kod koji je namerno napisan da bi bio nečitljiv. Definiciju obfuscated koda (koji bi se mogao prevesti kao nerazumljiv) možete videti ovde. Postoji opravdan razlog zbog kojeg bi se kod namerno mogao pisati tako da bude nečitljiv, ali sa aspekta refaktoringa, ovo predstavlja code smell koji predstavlja kandidata za refaktorisanje. Zašto? Zato što od svih aktivnosti vezanih za životni ciklus softvera (dizajn, implementacija, testiranje, održavanje itd.), 70% vremena ode na održavanje koda. Održavanje može predstavljati rešavanje bug-a, a u nekim slučajevima i dodavanje sitnih funkcionalnosti. Iz ovog razloga je bitno da kod bude što čitljiviji, jer će se najviše vremena potrošiti na čitanje i razumevanje datog koda. Ako uzmemo u obzir činjenicu da se na jednom projektu, u toku njegovog života, jako često menjaju ljudi koji održavaju kod, potrebno je da kod bude što čitljiviji i razumljiviji.

Regioni

Jedan od obfuscatora i code smell-a su regioni (C#) i to iz sledećih razloga:
  • Ako se koriste u okviru jedne metode, onda im je svrha da sakriju detalje predugačke metode koja je sama po sebi code smell
  • Predstavlja tepih ispod kojeg se krije smelly code, jer ako je kod klase toliko dugačak da postoji potreba da se zbog preglednosti podeli u regije, onda je to code smell i ta klasa bi trebalo da se podeli u više klasa  

Komentari

Komentari su jedan od najčešćih code smell-ova i jako često se zloupotrebljavaju.
Komentari:
  • Treba da objasne zašto je kod napisan tako kako je napisan, a ne šta i kako je urađeno. Ako se objašnjava kako je nešto urađeno, to znači da je taj komentar napisan zato što je, zbog nedovoljno jasnog i čitljivog koda, postojala potreba da se taj kod dodatno objasni, što predstavlja code smell.
  • Mogu biti zastareli i generalno im se ne može verovati, jer pri svakom nailasku na komentar postoji potreba da se proveri da li on ažurno opisuje deo koda, za koji je postojala potreba da se napiše
  • Kod treba pisati tako da su komentari redudantni, što znači da ne bi trebalo uopšte da postoje, već bi kod trebalo da bude samoobjašnjiv. Da ponovim, validan razlog za korišćenje komentara je kada je potrebno da objasni ZAŠTO je neki kod napisan tako kako je napisan

Method names

Kada se postavlja pitanje koliko dugačko treba biti ime metode, generalno pravilo je da treba preferirati duža imena metoda u odnosu na kraća. Dužim imenom metode, dovoljno jasno opisujemo šta data metoda radi. Kod davanja imena metodama (a i promenljivama), potrebno je staviti se u ulogu klijentskom programera i kako bi on očekivao da se zove metoda, koja radi to što radi. Ovaj princip se zove principle of least surprise

Imena metoda bi trebalo da budu dovoljno deskriptivna da iskomuniciraju klijentskom programeru, kada bi trebalo da je koristi (pozove).

Imena metoda ne bi trebalo da otkrivaju previše implementacionih detalja u svom imenu. Mogli bi pomisliti da se ovaj savet, u neku ruku, kosi sa prvim savetom za davanje imena metodama, ali prvi savet kaže da bi ime metode trebalo biti dovoljno deskriptivno da se može shvatiti šta metoda tačno radi, dok se ovaj savet tiče otkrivanje načina implementacije funkcionalnosti metode (kako metoda radi), koji ne bi trebalo otkrivati i koji bi trebalo abstrahovati od klijentskog koda.

Takođe, potrebno je da imena metoda opisuju bočne efekte. Primer:


public User GetUser(string userName)
{
 User user = GetUserFromDatabase(userName);
 if (user == null)
 {
   user = new User();
 }
 return user;
}

Dakle, ova metoda vraća user-a ako postoji u bazi, a ako ne postoji kreira novog. Ovo je neophodno iskomunicirati klijentu tako da bi, definitivno, prikladnije bilo nazvati metodu GetOrCreateUser.
Takođe, što se tiče imena, potrebno je biti konzistentan u davanju imena. Dakle, ako se koristi određeni prefiks ili sufiks u imenu jedne metode iz nekog razloga i ako se kreira neka druga metoda koja ima iste osobine kao prva metoda koja ima dati prefiks ili sufiks, potrebno je ostati konzistentan i kod imena druge metode, koristiti isti prefiks ili sufiks. Svako odstupanje u konzistentnosti zbunjuje ili dovodi čitaoca u sumnju vezano za to zašto se druga metoda zove drugačije, ako radi na isti način kao i prva metoda.

Tell don't ask design principle


Razlika između proceduralnog i objektno orijentisanog programiranja je, između ostalog i ta da je kod OOP ponašanje (metode) enkapsulirano zajedno sa podacima (atributi klase), dok isto ne važi za proceduralno programiranje. Iz ovog razloga se, kod proceduralnog programiranja, prvo dobavljaju podaci, a zatim nad njima vrši procesiranje, dok je, kod OOP, dozvoljeno procesiranje nad datim podacima enkapsulirano u istu klasu u kojoj se nalaze dati podaci. I pored ovoga postoji mogućnost da se definiše i eksterna logika za obradu podataka neke klase, ali ovo je nešto što treba izbegavati jer za ove potrebe postoji sama klasa. Na ovaj način (enkapsulacijom ponašanja zajedno sa podacima) je moguće na jednom mestu definisati dozvoljene obrade nad podacima (public interfejs klase), a takođe ovo povećava i reusability date logike, obzirom da je ona centralizovana. Ovo je ilustrovano na sledećoj slici i ovaj princip se naziva Tell don't ask principle, tj. potrebno je reći datoj klasi šta treba da odradi nad podacima, a ne dovlačiti njene podatke i nad njima vršiti neku obradu.

Sunday, 18 May 2014

Refactoring

Definicija refaktoringa

Ovo je definicija po Martin Fowler-u:
A change made to internal structure of software to make it:
  • easier to understand and
  • cheaper to modify
without changing its observable behaviour

Zašto treba refaktorisati

  • poboljšava dizajn
  • poboljšava čitljivost koda
  • otkriva bug-ove
  • pomaže da se ubrza razvoj
Refaktoring predstavlja neku vrstu održavanja higijene koda. Ako se kod zapusti, urušava se njegov dizajn i interna struktura. Ovo dovodi do otežanog održavanja koda, pravljenja bug-ova, dupliciranja koda, usporenog razvoja itd.

Kada treba i kada ne treba refaktorisati

Treba refaktorisati kada se prati TDD metodologija razvoja softvera (red-green-refactor). Takođe, ako se vodimo tzv. Pain Driven Development-u, treba refaktorisati ako je teško praviti izmene, rešiti bug, održavati kod ili dodavati novu funkcionalnost.
Ne treba refaktorisati ako je sistem toliko zapušten da je izvodljivije napisati aplikaciju ispočetka nego upustiti se u refaktorisanje. Takođe, ne treba (još uvek) refaktorisati ako trenutni kod ne radi. Pre refaktorisanja, potrebno je obezbediti da trenutni kod radi (isti princip kao kod TDD i red-green-refactor), pošto ne postoji mogućnost da se verifikuje da refaktorisanje nije unelo loše izmene i da se program ponaša isto kao i pre refaktorisanja.

Principi i proces refaktorisanja

Principi kojima treba težiti u toku refaktorisanja su sledeći:
  • Keep it simple (avoid trying to be clever, avoid to much complexity)
  • Keep it DRY (avoid duplication)
  • Make it expressive (self-documenting code)
  • Reduce overall code (eliminate code whereever possible, but don't violate readibility)
  • Separate concerns (Single Responsibility Principle)
  • Appropriate level of abstraction
Takođe i ovde treba pratiti Kent Beck-ova pravila jednostavnog dizajna (poređana po prioritetu)
  1. Run all tests
  2. Contain no duplicate code
  3. Express all the ideas the author wants to express
  4. Minimize classes and methods
Alati za refaktorisanje

  • Visual Studio 
  • ReSharper
  • CodeRush
  • Visual Assist X
  • JustCode

Characterization testovi

Da bi mogli da odradimo refaktoring, koji po definiciji predstavlja izmenu interne strukture koda, koja ne menja ponašanje programa, moramo imati načina da potvrdimo da kod, izmenjen refatkoringom, nije izmenio ponašanje programa.
Ovo se obično radi unit-testovima, koje treba pre refaktoringa pustiti kako bi date rezultate uporedili sa rezultatima nakon refaktoringa. Ovi rezultati se moraju poklapati kako bi bili sigurni da izmena koda, uvedena refaktoringom, nije promenila ponašanje samog programa. Ovo je tzv. red-green-refactor pristup, koji se koristi u TDD, gde se prvo napiše unit test koji ne prolazi (red), zatim se ispravi kod tako da dati unit test prolazi (green), a potom se kreće sa refaktorisanjem koda (refactor).

Ako je u pitanju legacy code koji nema napisane unit testove, onda je problem kako dokazati da refaktorisanje koda neće uticati na promenu ponašanja programa. Ovde stupaju characterization test-ovi, koji se, dakle, koriste kod refaktoringa legacy code-a koji nije pokriven unit test-ovima.
Ovi testovi se pišu na sledeći način:
  • Pokrene se program sa određenim input-om
  • Sačuva se rezultat, tj. output
  • Napiše unit test koji za dati input proverava da li se dobija dati output
  • Ovo se ponovi za više input-a
Kada se ovo odradi (i svi testovi prolaze), onda se pristupa refaktorisanju koda. Nakon ovoga se dati, characterization testovi ponovo puštaju i ako prolaze, onda dato refaktorisanje nije izmenilo ponašanje programa. U slučaju da ne prolaze, to znači da je kroz refaktorisanje najverovatnije uveden i bug koji treba ispraviti, kako bi characterization testovi ponovo prolazili i kako bi mogli nastaviti sa refaktorisanjem.
Inače, termin je prvi skovao Michael Feathers u svojoj knjizi Working Effectively with Legacy Code. Evo i definicije sa Wikipedia-e
In computer programming, a characterization test is a means to describe (characterize) the actual behavior of an existing piece of software, and therefore protect existing behavior of legacy code against unintended changes via automated testing. This term was coined by Michael Feathers. [1]