T-76.115 SEPA päiväkirja Refaktorointi Jani Heikkinen Kim Nylund 15. maaliskuuta 2005 1
Sisältö 1 Esittely 3 2 Menetelmä projektikäytössä 3 3 Kokemukset ja muutokset suunnitelmaan 4 3.1 Suunnitteluvaihe........................... 4 3.2 Iteraatio 1............................... 4 3.3 Iteraatio 2............................... 4 3.4 Iteraatio 3............................... 5 4 Yhteenveto 6 2
1 Esittely Fowler [2]: Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. Its heart is a series of small behavior preserving transformations. Each transformation (called a refactoring ) does little, but a sequence of transformations can produce a significant restructuring. Since each refactoring is small, it s less likely to go wrong. The system is also kept fully working after each small refactoring, reducing the chances that a system can get seriously broken during the restructuring. 2 Menetelmä projektikäytössä Kuten testaus, refaktorointi ei ole aktiviteeti, jota tehdään ainoastaan tietyn vaiheen lopuksi. Refaktorointia tehdään jatkuvasti pienissä pyrähdyksissä. Ryhmä käyttää refaktorointimenetelmiä, kun järjestelmään lisätätään uusia toimintoja ja kun järjestelmässä korjataan virheitä [1]. Näiden vaiheiden aikana voidaan tunnistaa erilaisia pahan lähdekoodin hajuja (bad code smells, BCS) [1], joita ovat mm.: 1. kahdentunut lähdekoodi 2. pitkät metodit 3. isot luokat 4. pitkät parametrilistat 5. hajanaiset muutokset (vs. yhden muutoksen seurauksena muutetaan vain yhtä oliota). 6. haulikkoleikkaus (edellisen vastakohta, eri paikkoihin tehdyt muutokset yhdistetään yhteen luokkaan) 7. ominaisuuskateus (metodi käyttää muun kuin oman luokan resursseja) 8. dataklimpit (samat dataryhmät esiintyvät monissa eri paikoissa) 9. alkukantainen pakkomielle (data-arvoja ei haluta korvata luokalla) 10. switch-lauseet 11. rinnakkaiset perintähierarkiat 12. laiskat luokat 13. spekulatiivinen yleistys 14. väliaikaiset kentät 15. viestiketjut 16. välimies 17. tarpeeton tuttavuus 18. vaihtoehtoiset luokat eri rajapinnoilla 19. keskeneräiset kirjastoluokat 20. dataluokat Myös lähdekoodin katselmoinnissa voidaan hyödyntää refaktorointia (pienien muutoksien kautta voidaan nähdä syvemmälle ongelmaan), mutta projektiryhmä ei katsonut lähdekoodin katselmointia tarpeelliseksi. Koodia refaktoroidessa saattaa syntyä uusia virheitä. Tästä syystä ennen refaktorointia laaditaan testit jäsenneltävän toiminnon osalta. Testit ajetaan välittömästi refaktoroinnin jälkeen varmistuaksemme, ettei toiminto ole vioittunut. 3
Suurimmissa refaktorointikokonaisuuksissa refaktorointi jaetaan vaiheisiin, joiden välillä ohjelmisto ja suunnitelmat ovat stabiilissa tilassa. Muutettaessa korkeamman tason abstraktioita, on tärkeää informoida tästä muita ryhmän jäseniä, jotta uutta ohjelmistokoodia ei tuoteta vanhan abstraktion perusteella. 3 Kokemukset ja muutokset suunnitelmaan 3.1 Suunnitteluvaihe Suunnitteluvaiheessa ei ollut vielä koodia refaktoroitavana. Luokkarakenteesta vastaavat henkilöt suorittavat design patterns -harjoituksen ja valitsevat ohjelmiston rakenteeksi tunnetun ja hyväksi havaitun suunnittelumallin. Käyttämällä valmista suunnittelumallia vältytään valmiiksi mietittyjen asioiden uudelleen keksimiseltä sekä vältytään vanhoilta, jo entuudesta tunnettuilta, virheiltä. Oletimme, että tällä on selvä vaikutus siihen, kuinka paljon refaktorointia joudutaan tekemään myöhemmin projektin aikana. 3.2 Iteraatio 1 Ensimmäisen iteraation aikana ei juuri tehty refaktorointia. Kun koodi luotiin, se muuttui jatkuvasti, eikä refaktorointia näin ollen pystytty tekemään. Refaktoroitavan lähdekoodin on toimittava suurimmalta osin, jotta sitä kannattaisi refaktoroida. Muutoin kannattaa kirjoittaa lähdekoodi alusta alkaen uudelleen. Ryhmä tutustui refaktorointikäytäntöön. Testit luotiin iteraation loppuvaiheessa, ja testien valmistuttua ryhmä keskittyi korjaamaan koodissa ilmenneitä vikoja. Samalla pyrittiin hyödyntämään refaktorointikäytäntöjä. Luodut testit tukevat myöhemmässä vaiheessa lähdekoodin refaktorointia. Iteraatiovaiheen loputtua SEPA-pari kävi läpi yksikkötestauksen ja järjestelmätestauksen aikana ilmenneet viat, sillä ne antavat vihjeitä siitä, mitkä kohdat koodissa on muutettu jälkeenpäin. On todennäköistä, että koodin hajoaminen alkaa näistä kohdista. Tämän lisäksi SEPA-pari piti listaa niistä asioista, jotka ovat epäloogisesti nimetty tai rakenteeltaan turhaan hankalasti toteutettu. 3.3 Iteraatio 2 Toisen iteraatiovaiheen alussa, SEPA-pari kävi läpi koodin ulkoasua ja rakennetta. Suurimmat refaktorointia tarvitsevat kokonaisuudet jaettiin osiin, jotka voitiin toteuttaa ryhmän sen hetkisen aikataulun mukaan. Osia, joiden toiminnasta ryhmä ei saanut tarkkaa kuvaa iteraation alussa (kuten järjestelmässä käytetyt reflektointiin liittyvät tekniikat), päätettiin jättää refaktoroimatta. Refaktorointi vaikutti luokkasuunnitteluun, mutta suurempia arkkitehtuurillisia muutoksia ei tässä iteraatiossa refaktoroinnista syntynyt. Esimerkiksi Userluokka ja sen perivä Student-luokka refaktoroitiin, jolloin luokkasuunnittelu parani huomattavasti. Muutokset tehtiin, jotta järjestelmään saatiin lisättyä ominaisuuksia, mm. DataManager-luokkaan. Havaitut BCS kohdat: 1, 5 ja 8. Tällä refaktoroinnilla oli suora vaikutus järjestelmän lähdekoodin määrään ja näkyi suhteellisena notkahduksena lähdekoodin rivimäärän kuvaajassa (19. tammikuuta) (Kuva 2, sivu 6). 4
Kuva 1: Hirvio projektin lähdekoodin rivimäärä Käyttöliittymäluokat ovat yksinkertaisia, eikä niissä havaittu tarvetta refaktoroinnille. Autentikointiluokassa havaittiin BCS:t 2 ja 10, Logger-luokassa havaittiin BCS 4. Nämä BCS:t ovat vielä käsittelemättä. 3.4 Iteraatio 3 Kolmannen iteraation alkaessa ryhmä oli toteuttanut vaaditun toiminnallisuuden. Kolmannen iteraation aikana koodi muuttui lähinnä käyttöliittymän osalta, joten tämän vaiheen aikana ohjelman lähdekoodia pystyttiin helposti refaktoroimaan ilman, että joku olisi lisännyt lähdekoodia, joka perustui vanhaan versioon. I2-vaiheessa havaitut BCS:t autentikointiluokassa refaktoroitiin. Tässä yhteydessä löydettiin lisäksi BCS:t 5 (hajanaiset muutokset) ja 7 (ominaisuuskateus). Refaktoroinnin tulos oli, että luokkasuunnittelu muuttui autentikoinnin osalta ratkaisevasti. Autentikointiluokka ei enää saanut tehdä yksityista yhteyttä järjestelmän tietokantaan, mikä edisti arkkitehtuurissa käytettyä MVC-mallia. Samalla SQL-komennot saatiin arkkitehtuurin mukaisesti malliosaan (model). Lähdekoodin luettavuus parani, koska nyt ei enää käytetty tietokantahaun palauttamia taulukkoja (käyttäjä)tiedon tallennuspaikkana, vaan esimerkiksi User-luokkaa. Refaktorointi näkyy kuvassa 2 (s. 6) 28. helmikuuta kohdalla. Datamanager, joka sisältää kaikki tietokantahaut, oli kasvanut toisen iteraatiovaiheen aikana nopeasti. Jo normaalin koodauksen aikana havaittiin BCS:t 1 (kahdentunut lähdekoodi) ja 2 (pitkät metodit). Muistiinpanot haettiin monella eri haulla kannasta, ja jokaisen metodin lopussa luotiin Note-objekti tietokannan antamasta datasta. Lähdekoodi ei ollut kahdentunut, vaan viisikertaistunut ja se refaktoroitiin extract method-mallin mukaan, toistuvasta lähdekoodista tehtiin oma metodi. Muutos lyhensi metodeja ja ennen kaikkea paransi lähdekoodin ylläpidettävyyttä. Refaktoroinnissa havaitsimme myös virheen; kaikki viisi kohtaa eivät olleetkaan identtiset. Tämä virhe olisi ollut vaikea havaita ilman 5
refaktorointia. Muutos vähensi lähdekoodin määrää hieman yli 40 rivin verran. Tämä muutos oli luokan sisäinen muutos, eikä se vaikuttanut järjestelmän arkkitehtuuriin. Myöhemmässä vaiheessa havaittiin aivan sama tilanne kuin edellisessä kappaleessa, mutta nyt Student-objektin kohdalla. Kuvassa 2 (s. 6) tämä näkyy 11. maaliskuuta kohdalla. Lähdekoodin määrä väheni noin 70 rivillä. Kuva 2: Hirvio projektin lähdekoodin rivimäärä FD vaiheen lopussa MVC suunnittelumallin model osan toteuttava DataManager-luokka on kasvanut projektin kuluessa hyvin laajaksi ja sisältää hyvin monta eri BCS:tä (mm. 1, 2, 3, 5, 8). Näiden refaktorointi aiheuttaisi muutoksia luokkasuunnitteluun, mikä päätettiin jättää tekemättä projektin loppuvaiheessa. Myös suunnittelumallin controller osan toteuttavassa HirvioApplication luokassa havaittiin monia BSC:tä (mm. 1 ja 3). 4 Yhteenveto Refaktorointi antaa hyvät eväät jo toimivan, mutta heikosti suunnitellun tai muutoin rapistuneen lähdekoodin parantamiseen. Selvä prosessi, jossa ensin etsitään lähdekoodin heikkoudet ja tämän jälkeen etsitään parhaat tavat korjata ne, on selkeä ja helposti omaksuttavissa. Jos ohjelmistoprojekti voi aloittaa aivan tyhjältä pöydältä, kuten tällä kurssilla, refaktoroinnista ei välttämättä saada kaikkea irti. Kuitenkin suurin osa projekteista perustuu jo olemassa olevalle lähdekoodille, jota monet eri kehittäjät ovat tehneet ajan mittaan. Tälläisessä ympäristössä on varmasti hyötyä refaktorointiosaamisesta, joten tämän SEPA-aiheen tuoma kokemus on varmasti hyödyllinen tulevaisuudessakin. Tämän kurssin harjoitustyössä, refaktorointia tarvittiin lähinnä niissä kohdissa, jotka kasvoivat nopeasti. Refaktoroinnin olisi näiden kohtien osalta voinut korvata tarkalla metoditason suunnittelulla, mutta tämä suunnittelu olisi tehnyt nopeasti kasvavan koodin kehityksestä jäykkää. 6
Ehkä kaikkein tärkein asia, minkä refaktorointi mahdollistaa, on oppia jo olemassa olevan lähdekoodin toiminta läpikotaisin. Jos jokin ohjelman kohta on epäselvä, se voidaan refaktoroida selvemmin luettavaan muotoon. Tältä osalta refaktorointi soveltui hyvin projektiimme, koska projektin laajuuden vuoksi kaikkia järjestelmän osia ei voinut olla suunnittelemassa alunperin ja näin osa toiminnoista saattoi olla vieraita. Refaktorointi hyödytti tätä kautta sekä SEPA ryhmää, että koko projektia. Viitteet [1] M Fowler, Refactoring, Improving The Design of Existing Code, Addison- Wesley, 1999. [2] Refactoring home page (http://www.refactoring.com/) 7