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]