Kódduplikáció (Code Duplication)
Az egyik legnagyobb ellenségünk a szoftverfejlesztés alatt a kódduplikáció, ami akkor fordul elő, amikor ugyanaz vagy nagyon hasonló kód részeket többször ismételve találunk egy adott szoftverprojektben vagy amikor egy célra többször elkészítünk egy megoldást.
Mindennek nem a fölöslegesen duplán eltöltött egyszeri fejlesztési idő a legnagyobb hátránya főleg hogy ez sok esetben egy másolás/beillesztés csupán. A duplikáns kód debuggolása és fenntartása fogja hosszú távon felemészteni az időnket és energiánkat.
Most nézzük át, pontosan milyen problémákat is okoz a kódduplikáció?
- Bővíthetőség: Ha ugyanazt vagy hasonló kódot többször más helyeken ismételjük, akkor minden egyes alkalommal, amikor változtatást kell végrehajtani, minden duplikált részt külön-külön módosítani kell. Ez időigényes és könnyű benne hibát véteni, sőt kifelejteni bizonyos duplikánsokat.
- Olvashatóság: A kódduplikáció nehezíti a kód olvashatóságát és megértését. Ha ugyanazt a kódot többször találjuk meg a projektben, akkor a kód fragmentáltá és nehezen követhetővé válhat. Ez megnehezíti a fejlesztők számára a kód megértését, a működés logikájának felismerését és a hibák gyors diagnosztizálását.
- Hibakeresés: Mikor egy feladatra több megoldásunk is van, akkor gyakran nem látjuk át melyikben keletkezett a hiba. Duplikáns kód esetén előfordulhat, hogy egy kódrészletben keressük a hibát és azért nem találjuk, mert tudtunkon kívül létezik egy másik verzió is az adott kódból és ez a bizonyos másik verzió, az amiben a hiba keletkezett.
- Inkonzisztens működés: Ha többször oldunk meg egy feladatot némileg eltérő módon és hol az egyiket használjuk hol pedig a másikat, az inkonzisztens viselkedést fog eredményezni. Egyszer így történik meg egy művelet máskor pedig kicsit másként.
A Kódduplikáció ellenszerei
Korábban már megismertünk egy módszereket a kódduplikáció elkerülésére:
Metódusok írása
Egy metódust úgy definiáltunk, hogy egy adagnyi kód, ami önmagában függetlenül végrehajtható, hívható. Mikor egy metódus “mögé rejtünk” valamennyi bonyolultabb működést, akkor azt csak hívnunk kell. Ha valamennyi változatosságot is szeretnénk a függvény végrehajtásában, arra valók a paraméterek.
Működés felbontása komponensekre
A Unity, ahogy a legtöbb egyéb Game Engine erősen támogatja a komponens alapú architektúrát.
Ha van egy objektumunk, aminek egyszerre több egymástól független viselkedése van, akkor ezeket a viselkedéseket külön komponensek formájában adjuk hozzá a GameObject-hez.
Ha animálni szeretnénk egy GameObjectet, akkor Animator komponenst adunk hozzá, ha meg szeretnénk valami módon jeleníteni, akkor egy Renderer-t, ha azt szeretnénk, hogy legyen hangja, akkor egy AudioSource-ot, ha azt szeretnénk, hogy a fizikai rendszer része legyen akkor egy Rigidbody-t és így tovább. Végül ha olyan egyedi működéssel szeretnénk felruhzáni egy objektumot, ami kifejezetten a mi játékunkra jellemző, akkor ezt a működést egy MonoBehaviour formájában adjuk hozzá a GameObject-hez.
Figyeljünk viszont arra, hogy ha saját működést adunk egy GameObject-hez, akkor ezen működés különböző független aspektusait külön szkriptekben íjuk meg! Például, ha a játékos karakterünk tud mozogni és sebzést kapni az ellenfelektől, az két egymástól teljesen független viselkedés. Javasolt külön szkriptben elkészíteni őket.
Ez különösen hasznos nagyobb projekt esetén, ahol sok különböző objektumot és viselkedést kell kezelni.
Komponens orientált fejlesztés előnyei és hátrányai
Mért hasznos szeparálnunk egy független működést külön komponensekre?
- Könnyű átláthatóság Új funkciók hozzáadásakor vagy meglévők módosításakor csak azzal a komponenssel kell foglalkoznunk, amelyet érinteni szeretnénk, anélkül hogy az egész kódbázist át kellene dolgoznunk.
- Újrafelhasználhatóság A komponens alapú architektúra lehetővé teszi a komponensek újrahasználását. Például, ha egy adott típusú objektumnak van egy olyan komponense, amely felelős az sebzés kezeléséért, akkor ezt a komponenst könnyedén újra felhasználhatjuk más hasonló viselkedéssel rendelkező objektumoknál is.
- Modularitás
Mikor le szeretnél cserélni egy viselkedést egy másikra részben eltérő megoldásra elkészítheted egymástól függetlenül mindkettőt és könnyedén cserélheted őket.
Ezt az előnyt később fejtjük ki bővebben: Interface-ek (Hamarosan)
A komponensek gyakran nem olyan függetlenek egymástól, mint ahogy szeretnénk.
Az előző példában lehetséges, hogy a sebezhető és a mozgató komponens igenis kapcsolatban áll. Például a játékos nem tud mozogni, ha lefogyott az élete.
Ebben az esetben referenciákat kell fenntartanunk a függő objektumok közt és függvényhívásokon vagy eseményeken keresztül kommunikálniuk kell ezen komponenseknek: Lásd:
Az előnyök hatása egy kicsit is komplikáltabb szoftver esetén is már masszívan túlsúlyba vannak ezen hátránnyal szemben.
Példák
Többek közt milyen működést szoktunk külön bontani?
- Logikai működés és UI
Erősen javasolt különválasztani a játékot befolyásoló belső logikát a megjelenített UI-tól. Lásd: Komponensek hierarchiája
- Játékos mozgása és irányítása
Gyakran javasolt két külön szkriptet létrehozni, amelyek közül az egyik felelős a játékos irányításáért és a másik a mozgásáért. Az irányitásért felelős szkript tartalmazhatja az input kezelését (pl. billentyűzet vagy egér események), míg a mozgásért felelős szkript frissítheti a játékos pozícióját a megfelelően beállított sebesség, gyorsulás stb. paraméterek alapján.
- Játékos mozgása és mögöttes AI
Érdemes a “testet” különválasztani az “agytól”. Külön szkript felelhet azért, hogy megmondja, mikor merre mikor menjünk és egy másik ami végrehajtja azt. Lásd:
Fontos kiemelni, hogy a fenti szétválasztások nem kötelező jellegűek és nem mindig segítik a kód karbantarthatóságát, átláthatóságát. Minden szoftver esetén a fejlesztőknek egyedileg kell dönteni arról, mely funkciók szétválasztását tartják logikusnak, ésszerűnek és hasznosnak.
Ilyen döntések meghozásában szinte kizárólag csak a tapasztalat tud útmutató lenni.