Hogyan fejlesszünk hatékonyabban szoftvert? – Töröljük a kódot!

Korábbi munkahelyemen abban a szerencsés helyzetben lehetett részem, hogy egyazon terméket fejleszthettem a kezdetektől 3 éven keresztül. A termék “vállalati” alkalmazás, ami nagymegrendelőknek (több tízezres felhasználószámnak) készült. Talán másoknak ez egy csapásnak tűnhet, hogy miért kell évekig ugyanazt a szoftvert túrni, én azonban ezen a projekten tanultam eddig a legtöbbet. Egy kisebb méretű (6-7 fős) szoftverfejlesztői csapattal dolgoztunk, a feladatom a bitfaragás mellett az ügyfélelvárások és az üzleti folyamatok megértése is volt, tehát üzleti elemzőként is funkcionáltam.

Pár év fejlesztés során a rendszerbe folyamatosan kerültek be az újabbnál újabb funkciók, amely egy iszonyúan komplex (vagy bonyolult?) rendszert eredményezett. Nagyon jól értettem az üzleti elvárásokat, azt is értettem, hogy ezek hátterében mi húzódik meg, és nagyon jól értettem a kódot, mert magam is aktívan fejlesztettem. Egy idő után az ügyfél által elvárt újabb funkciók komoly dilemmát jelentettek a szoftver továbbfejlesztésében. Ha lefejlesztjük az újabb funkciót, akkor az még bonyolultabbá teszi az eddigi modellt, amellyel az üzleti környezetet próbáltuk leképezni.

Legtöbbször ilyenkor egy fejlesztő cég próbálja lepattintja magáról a továbbfejlesztési (változáskezelési) igényeket, mert már nem képes kezelni a kódban fellépő komplexitást. Természetszerűleg mi is törekedtünk arra, hogy minél egyszerűbb megoldást vállaljunk be, de még így is sokszor nehézségekbe ütköztünk. Ekkor vetődött fel a gondolat:

Miért ne töröljünk régi kódokat, kódrészleteket, miért ne egyszerűsítsük őket, ha tudjuk, hogy nincs rájuk szükség?

Ha ezt nem tesszük, egyre csak lassítjuk a munkánkat, mert az új funkciók implementálása és a jelentkező hibák javítása egyre tovább tart.

De…

Sokszor próbálkoztunk azzal, hogy meglévő funkcionalitást kiirtsunk a kódból. Igen nehéz volt ez véghezvinni… Ha azonban sikerült, igen boldog voltam, mert sok részét gyakhattam ki a kódnak, amit nem sajnáltam, mert így sokkal egyszerűbb volt egy-egy új funkció kifejlesztése, és kisebb lett a kód.

Azonban sokszor a funkciómegszüntetés, -egyszerűsítés nehezen végig vihető:

  • Szállítói oldalon: szerződésben, korábbi megbeszéléséken, írásos formában bevállaltuk, hogy megcsináljuk a funkciót, ezért nem lehet kivenni a kódból, amit már lefejlesztettünk. Ettől kezdve a saját menedzsmentünk nem támogatja a meglévő funkciók törlését.
  • Terméket fejlesztünk, jól mutat a prospektusban az, hogy egy (haszontalan) funkciót nem csak a versenytársunk, de mi is implementáltunk (vagy éppen a versenytársunk nem tudja, de mi igen). Ez jó kis csali szokott lenni a potenciális ügyfelek előtt, akiknek az a célja, hogy minél többet tudjon a dobozos szoftver.
  • Mit fognak szólni hozzá a konkrét felhasználók, ha elveszítenek egy funkciót? Ha megkérdezzük őket, akkor nem akarják, hogy egy “ilyen hasznos” funkció kikerüljön a rendszerből, mert ki tudja, mikor lehet rá szükség… Jó az, ha megvan…
  • Se a megrendelő se a szálltító nem tudja, hogy a felhasználók pontosan hogyan is használják nap mint nap a rendszert, és milyen hatása lesz annak a napi folyamatokra, ha kiveszünk egy funkciót a rendszerből. Nem merik vállalni a kockázatot. (Nagy a felhasználószám, nagy a rendszer.)
  • Maguk a szoftverfejlesztők látják azt, hogy mennyire bonyolult a kód, és mennyire nehezen tudnak már egy idő után újabb funkciókat implementálni. Azonban a fejlesztők nem is mernek abban gondolkodni, hogy kódot töröljenek a célból, hogy egyszerűsítsék azt. Nem értik, pontosan mik is a kód hátterében meghúzódó üzleti elvárások, döntések, ami miatt egy-egy kód elkészült, ezért nem is tudják felmérni, hogy egy adott kódrészletre valóban szükség van-e vagy sem, vagy akár lehetne-e egyszerűsíteni rajta. Ezt a helyzetet általában még tovább nehezíti az, hogy a kód nem érthető, és csak nehezen fejthető belőle vissza az eredeti üzleti szándék, elvárás.
  • Automatizált tesztekkel nem támogatott Legacy code esetében nem is mernek hozzányúlni a meglévő kódhoz, nehogy elrontsanak vmi mást. (Szerencsére nálunk nem ez volt a helyzet.)

Arról áhítozunk sokszor, hogy a szoftverfejlesztésben létezzenek egzakt metrikák, amik mutatják, hogy jó-e vagy rossz, amit csinálunk. Ez itt is segítene, mert igazolhatná a menedzsmentnek és az ügyfélnek a funkciótörlés (-egyszerűsítés) jogosságát a költségmegtakarításon keresztül. De ilyenek sajnos nincsenek…

Nálunk nincsenek felesleges funkciók!

Vannak bizonyos felmérések, amik mutatják, hogy egy-egy rendszerben az egyes funkciók hány százalékát milyen gyakorisággal használják: “a rendszerek szolgáltatásainak 19%-ra ritkán, míg 45%-ra gyakorlatilag soha sincs szükségük.”

Egyesek talán úgy gondolhatják, hogy a saját projektjükre nem jellemző, hogy felesleges funkciók lennének benne, és arra is hivatkozhatnak, hogy régi a tanulmány, és az ő agilis projektjükben megvalósított funkciók kevéssé “feleslegesek”, mert végig prioritás szerint haladtak a megvalósítás közben. (Szemben a hagyományos, a “gonosz vízeséssel” fejlesztett szoftverekkel.) Én úgy gondolom, minden szoftverfejlesztési projektben, ahol van megrendelő, létezik a felesleges funkcionalitás jelensége:

  • Szerződünk (mi szállítók) bizonyos funkciókra, amikről utólag kiderül, hogy a valós felhasználás szempontjából feleslegesek. Ennek oka az, hogy a megadott pénzért a megrendelő minél több funkciót akar beletömködni a szoftverbe, hogy a projekt zárása után biztonságban érezhesse magát, és ne kelljen állandóan a szállítóhoz fordulnia pluszmegrendelésért, ha valamire nem gondolt volna.
  • Menet közben állandóan előfordul, hogy politikai okokból bevállalunk egy-egy funkciót. Ennek sokféle oka lehet, tipikus adok-kapok játék megrendelő és szállító között. (Jobb esetben már menet közben is tudjuk, hogy nem lesz a funkcióra szükség, és nem “csicsázzuk” túl.)
  • Ugyan az ügyfél természetszerűleg a pénzéért minél több funkciót akar, azonban lehet ezt “ésszerűen” is csinálni, prioritások mentén, amely elvileg csökkenti a felesleges funkcionalitást. Azonban még így is előfordulhat, hogy legjobb szándékunk ellenére egyszerűen rosszul priorizálunk (tipikusan agilis projekt, termékfejlesztés esetében). Rosszul mértük fel a piaci igényeket, menet közben változtak a piaci igények, rosszul értettük meg a megrendelőt, esetleg ő maga sem tudta pontosan meghatározni a prioritást.
  • A sikeres termékek hosszú évekig tartanak. Menet közben változhat az üzleti/jogszabályi környezet.

A lényeg, hogy felesleges funkcionalitást valósít meg a termékünk, ami növeli a termék komplexitását a kódban, ezáltal minden újabb funkció vagy hibajavítás megvalósítása drágább lesz (egyáltalán a hiba előfordulási valószínűsége is nagyobb). Arról nem is beszélve, hogy nemcsak kód szinten, de a funkcionalitás szintjén is komplikáltabb a szoftver, ami a felhasználók számára is bonyolulttá teszi a használatot, így több felhasználói tévedést, frusztrációt, hosszabb betanítási periódust eredményez.

Mi lenne, ha…?

Michael Feathers, a Working effectively with legacy code c. könyv szerzője felveti azt az ötletet, hogy mi lenne, ha éles környezetben mérhetnénk a kódlefedettséget: mi lenne ha valahogyan mérni tudnánk, hogy a kód egyes sorai milyen gyakran hívódnak az éles használat során? Ezáltal betekintést kapnánk abba, hogy milyen kockázattal járhat a kód egyes részeinek módosítása, és ez valójában egy indikátor is lehetne, hogy mely kódrészletek, funkcionalitások azok, amelyek a valóságban nem használtak. Ezáltal tudatosan irthatnánk a haszontalan funkciókat.

Feathers mostanában megjelent egy másik cikke, még sokkal izgalmasabb. Egy érdekes gondolatkísérletettel kacérkodik: mi lenne, ha minden kódsor, ami implementálásra került, három hónap elteltével eltűnne a kódbázisból? Ennek több hozománya is lenne. Egyrészt a fejlesztők a már meglévő funkciókat sokkal célravezetőbben, és valószínűleg jobban érthetőbben is implementálnák. Ennek oka az, hogy már ismerték a korábbi kódot, vannak tapasztalataik a rendszer korábbi implementációjáról és a már megvalósított funkcionalitás valódi üzleti felhasználásáról is. Az egyes újraírásokat egyre hatékonyabban végeznék el ezen információk birtokában, ami a kód felépítésében egy egyszerűbb rendszert eredményezne. (Tudják, hogy pontosan mire van szüksége a felhasználóknak, és megtanulják, hogyan lehet azt a legjobban, legegyszerűbben implementálni.) Amit viszont még fontosabbnak tart Feathers az az, hogy ez a fajta metodika komoly komprosszimukra kényszerítené az üzletet a funkcionalitás viszonylatában. Amiatt, hogy kevés funkciót tarthat meg (mert az implementált kód úgyis eltűnik 3 hónap után), komolyan végig kell gondolnia, hogy mik maradjanak meg, hogy azokra valóban szüksége van-e. Feathers azt gyanítja, hogy hosszú távon az ilyen módon fejlesztett szoftver költség szempontból sokkal kedvezőbb, mert a feleslegesen és/vagy rosszul implementált kód cipelése jelentős költséget képvisel, amellyel jelenleg senki nem számol.

A jövő a bátraké

Feathers szerint azon (szoftverfejlesztő) cégek nyernek a jövőben, akik felismerik, hogy hogyan lehet stratégialiag a termékeikből kódot törölni. A költséghatékonyság egyik módja már eleve az, ha nem fejlesztünk ki nem profitábilis funkciókat, a következő lépes azonban az, hogy hogyan irtsunk ki gyökerestől funkciókat a kódból. A kódcipelés költsége több, mint gondolnánk, és ez kompetitív előnyt jelenthet számunkra.

Share
This entry was posted in Módszertan and tagged , , , , . Bookmark the permalink. Follow any comments here with the RSS feed for this post. Trackbacks are closed, but you can post a comment.

Comments

  • Szia!

    Jó cikk! Érdekes, hogy mi is pont most gondolkodtunk ilyenben. Szokásomhoz híven kicsit technikailag szólnék hozzá.

    Az a furcsa, hogy mérni azt, hogy mely funkcionalitások kerülnek meghívásra, nem is olyan bonyolult. Gyakorlatilag hozzácsapsz egy AOP keretrendszer JAR-ját az alkalmazásodhoz, beállítod, hogy minden service hívást naplózzon. Vagy akár vastag kliensnél window.setVisible metódusra teszed, és máris megvan, hogy melyik ablakot hányszor nyitottak meg. Olyan library-t is láttam, amivel a vastag klienst lehet google analytics-hez csatolni, így lehet mérni a “funkciók látogatottságát”. A NetBeans és a Eclipse is biztosít ilyen api-t, sőt ők mérik is, publikálni is szokták, ez alapján fejlesztik tovább (mikor jogot adsz anonim adatok gyűjtésére, erről van szó, melyik menüpontra milyen gyakran klikkelsz).

    És mégis, szemben azzal, hogy milyen könnyen implementálható, nem láttam még projektet, ahol ezt mérnék. Valahogy eszébe sem jut az embereknek. Szerintem ez kis pszichológia is, nem akarnak szembesülni azzal, hogy mennyi funkciót fejlesztettek ki totál feleslegesen. Vagy a napi fejlesztés mellett nem marad idő. Minden esetre a régóta emlegetett statisztikákon kívül szívesen olvasnék magyar, friss alkalmazásokról szóló statisztikákat. Én pár projektnél be tudnám vállalni. :)

  • Marhefka István

    May 19th, 2011

    Viczi: Köszi a technikai infókat.

    Jó kiindulópont az AOP-os megközelítés a service-ek köré az egyes szolgáltatások használatának vizsgálatához. Kicsit azt sajnálom, hogy nem tudok olyan egyszerű megoldásról, amely a teljes kódbázis lefedettségét mérné, nem csak a szolgáltatások meghívásáét. Én még nem értem semmilyen módon éles alkalmazásban a funckiók használatát, ezért az is lehet, hogy ez a könnyen kivitelezhető módszer is nagy eredményeket hozhat.

    Tapasztalatom az még, hogy sok esetben fordul elő, hogy mindenféle csupa általános (és túlbonyolított) megoldásokat készítenek a fejlesztők, a valós élet pedig sokkal egyszerűbb. (Ezeket a megoldásokat szoktam szóban csodasz*rnak hívni.)

    Lehet, hogy ezt a fajta overengineeringet a kódlefedettség nem is tudja kimutatni. Gondolok, pl. olyan egyszerű dologra, hogy valamit listában tárolunk, de a valóságban mindig csak egy elemről van szó. Vagy másik tipikus példa, amikor rekurzív adatstruktúrákat és algoritmusokat készítenek a fejlesztők, valójában pedig egy erősen korlátozott (mondjuk kétszintű vagy max. háromszintű) hierarchiáról van szó.

    Mire gondoltál akkor, amikor azt írtad, hogy pár projektet be tudsz vállalni?

  • Szerintem első körben az AOP-os megközelítés jó lehet. Amit te írsz, arra jók a profiler-ek, viszont azokat élesben nem merném használni, a sebességcsökkenés miatt. De mi pl. használtuk úgy, hogy a szervezőknek a teszt rendszert kiadtuk tesztelésre, dokumentáció nélkül, és lemértük, hogy intuitíven a kódok hány százalékát fedik le. Maradt ki funkcionalitás bőven, tipikusan az adatértékektől függő elágazások, és kivételes esetek.

    A pár projekttel kapcsolatban azt gondoltam, hogy ez már régóta tervünk, hogy bevezetjük egy-két élesben futó projektnél. Utána név nélkül publikálni lehetne az eredményeket. Csak ez így magában nem sokat mond, kéne toborozni még pár céget, projektet, hogy reprezentatívabb legyen.

  • Érdekes dolog amiről írtál. Mi épp szerencsés helyzetben voltunk, mert az ügyfél saját baján tapasztalta bizonyos funkcióról, (amit majd személyesen elmondok), hogy túl bonyolult és nehézkes a használata. Sikerült meggyőzni őket, hogy ésszerűsítsük ezt funkciót, így a háttérben lévő kód is jelentősen leegyszerűsödött.

    A kód kidobása kétélű fegyver. A nagy sietségben lehet vele időt nyerni. Épp a napokban kell visszatennem egy egyszer már működő megoldást, ami menet közben kikerült a branchből. Szerintem a cipelés nem vitt volna el 4 napi melót, amennyit a visszapakolás igényel. De ez csak egy eset.

    Én a kód kidobása helyett, olyan alapinfrastruktúra kialakításában gondolkodnék, ahol ki/be lehet kapcsolni az egyes funkciókat. Talán a nem használt funkciók cipelése jobban meg tud térülni egy agilis projektben, mint a törlés. Mi úgy döntöttünk, hogy egyetlen konfigurálható rendszer fogja kiszolgálni a meglévő és leendő ügyfeleinket.

    Fontos szempontnak kell lennie a rendszer konfigurálhatóságnak már a fejlesztések elején is. Ugyan nagyon nehéz meghúzni a határt egy agilis projektben, hogy mikortól élvez prioritást a felületek / funkciók ügyfélfüggő paraméterezése. 1 ügyfél után, esetleg 2 ügyfél után, vagy esetleg 3…? :) Nem egyszerű a kérdés.

    Még egy fontos kérdés, az üzleti probléma mély megismerése után lehet igazán tömör és jó kódot írni. Az üzleti igények ismerete pedig meg tudja akadályozni az over engineering-et – nem készülünk soha be nem következő dolgokra. Találkoztam olyan kódokkal, amikről simán el lehetett dönteni, hogy azért lettek olyanok amilyenek, mert annak idején aki írta még maga se volt teljesen tisztában a problémával. Ilyenkor jelennek meg a fölösleges validációk, túl okos algoritmusok stb…

    Érdekes következtetéseket lehetne levonni, ha tudnánk monitorozni, hogy a vezérlés, hol, mennyi időt tölt el. Mi is gondolkodunk abban, hogy AOP-val logoljuk a rendszer működését. Csak egyelőre van fontosabb a sok fontos között.

  • Marhefka István

    Jun 8th, 2011

    Szerintem Te egy más dologról beszélsz. Terméket fejlesztetek, és kérdéses, hogy egy-egy funkciót megtartsatok-e, mert vmelyik ügyfélnek kellhet, vagy meg lehet-e tőle szabadulni. Sőt, ha jól értettem, külön-külön branchetek van az egyes ügyfelekre.

    Egyetértek abban Veled, hogy inkább egy olyan megoldás kellene, ami megfelelően paraméterezhető, és mellőzi a brancheket. Azonban ha nem megfelelően moduláris az alkalmazás, akkor egy ilyen megoldás kialakítása is költséges lehet, mert a kódban egymásnak ellentmondó üzleti igényeket kell megvalósítani (egyik ügyfélnek így, a másiknak úgy).

    “Az üzleti igények ismerete pedig meg tudja akadályozni az over engineering-et”. Ebben teljesen igazad van, csakhogy sok esetben nincs meg a megfelelő üzleti tudás, amikor a projekt elindul. Vagy lehet hogy megvan, de senki nem látja még, hogy a leendő rendszer hogyan fogja megváltoztatni a már működő üzleti folyamatokat. Ezért nagyon fontos, hogy fegyelmezetten az XP elveit kell követni (KISS, YAGNI). Ezt az emberek 90+%-a nem érti meg, és ha tud is róla, egyszerűen képtelen betartani. Nem tudja megállni, hogy feleslegesen ne absztraháljon.

    Az overengineering alatt én azt értem, hogy egy adott problémát egyszerűen vagy bonyolultan oldunk-e meg a kódban. Ehhez képest a funkciók törlésén alapuló egyszerűsítés ettől független dolog. (“Az elején rosszul gondoltuk azt, hogy egy funkciót meg kell-e valósítani. Ezt felülvizsgáljuk, és töröljük azt.”)

    Az utóbbi hónapokban többféle kóddal is találkoztam. Az egyik a tipikus gány, amihez az évek során már nagyon sokan nyúltak, a másik pedig az, amiben felesleges, ill. egyszerűen rossz absztrakciókat alakítottak ki a célból, hogy az alkalmazás megfelelően moduláris, “jóltervezett” legyen. Szerinted melyik kódot volt könnyebb refaktorálni? Azt, amelyik egyszerűen csak gány volt…

    De most már eltávolodtunk a post eredeti témájától, ami pedig az, hogy merjünk-e funkcionalitást törölni, ami a Ti esetetekben a bonyolult üzleti probléma és a több ügyfél miatt nem egyszerű kérdés, de folyamatosan érdemes fejben tartani.

  • Egyet értek Veled. Főleg a saját szemszögemből tudom megközelíteni a dolgot, ezért jöttem ezzel példával.
    Abban megegyezhetünk, ha lehet törölni, akkor nem kell rajta agyalni, törölni kell.

    Jelenleg még sok branch van, de azon is dolgozunk, hogy csak 1 legyen, mert már 2 ügyfél supportja is gáz, ha több branch van. Ha esetleg még több ügyfél lenne saját branchekkel, nem győznénk a hibákat javítani és testre szabni a különböző branchekben lévő kódot.

  • Elnézést, hogy egy több éves bejegyzéshez szólok hozzá, nem tudom, hogy valaki olvasni fogja-e még. De megütötte a figyelmemet a funkciók kihasználásának a mérése. Sajnos nem értek sokat a témához, mert külső ügyfél által rendelt szoftver fejlesztésében még nem vettem részt. Viszont elsőre én úgy gondolnám, hogy a megrendelő funkcióhasználata a megrendelőn kívül senkire nem tartozik, még a szoftver fejlesztőire sem. Lehet, hogy ezzel tévedek, de nem lehet, hogy sok esetben jogi okai vannak annak, hogy nem lehetséges a funkciók kihasználásának mértékét megállapítani?

  • Marhefka István

    Nov 27th, 2013

    Teljesen igaza van. Ha szerződést kötöttünk arra, hogy a szoftver valamit tudjon, akkor azt jogilag nem törölhetjük. Azonban közös érdekünk, hogy egy sokéves szoftverben folyamatosan egyszerűsítsünk, ezért közös megegyezéssel a nem használt funkciók törlése járható út lehet.

    Más részről gyakran előfordul az is, hogy a kód túltervezett, olyan elméleti eseteket kezel le, amelyek a valóságban elő sem fordulnak, és erről a specifikáció sem rendelkezett. Ezeket a kódokat megkérdezés nélkül is lehet egyszerűsíteni, törölni.

Leave a Comment