Blog a programozásról, főleg a PHP-ről, és talán más dolgokról

fogyás

A projekt fenntartója: franiglesias. Hosted on GitHub Pages - Theme: mattgraham

írta Fran Iglesias

Ebben a cikkben bemutatunk egy gyakorlatot, amely felhasználható a kompaktabb osztályok írásának folyékonyabbá tételéhez, kifejezőbb és könnyen érthető módszerekkel.

Az objektum-kaliszténika gyakorlatok segíthetnek automatizálni azokat a gyakorlatokat, amelyek lehetővé teszik a legjobb tervezési elvek megközelítését. Bizonyos értelemben abból áll, hogy megtanulunk bizonyos hibás mintákat észlelni a kódban, és átalakítani úgy, hogy előrelépjünk a kód minőségének javítása érdekében.

Ezeknek a gyakorlatoknak az az ötlete, hogy mesterséges korlátozásokat vezessenek be a válaszok kényszerítésére, amelyek arra kényszerítenek bennünket, hogy a hagyományos megoldásokon túl gondolkodjunk.

Ebben a cikkben ezeket a korlátozásokat fogjuk alkalmazni:

  • A behúzás egyetlen szintje
  • Ne használjon mást
  • Tartsa az egységeket kicsiben

A korlátozások

A behúzás egyetlen szintje

A behúzás egy kódszervezési modell, amely segítségével könnyen azonosíthatjuk az utasításblokkokat, amelyek elágazásokat vagy utakat képeznek a szoftverfolyamatban, valamint a kapcsolódó utasítások blokkjait. Az olyan nyelvekben, mint a Python, a behúzásnak jelentése van, ezért elkerülhetetlen. A PHP-ben és sok más nyelvben azonban a behúzás hasznos konvenció. Írhatnánk programokat behúzás nélkül, és ugyanúgy működnének, csak nehezebben olvashatóak lennének.

A behúzás jellemző a vezérlő struktúrákra, például ha/akkor:

Ennek több szintje lehet:

A kihívás ezúttal a csökkentés lesz egy a behúzás szintje egy osztály minden módszerében.

Lényegében ez arra kényszerít minket, hogy fontolóra vegyük, hogy az egyes kódblokkok mit csinálnak, és vonják ki a saját módszerébe, elmagyarázva, hogy mit tesz a nevükben.

Ne használjon mást

Az if/then/else struktúra több okból is zavaró lehet. Az egyik éppen a felesleges használata, különösen, ha az akkori láb kilépést jelent a hurokból vagy a fő módszerből.

Azért vezettem be ezt a korlátozást, mert eléggé összefügg az előzővel.

Tartsa az egységeket kicsiben

Az elején idézett cikkben entitásokról beszélünk, de jobban belegondolva inkább az egységeket tettem, hogy az osztályokra és a módszerekre egyaránt hivatkozhassak. Végső soron minden egységről szól, függetlenül attól, hogy milyen felbontást kezelünk, legyenek a lehető legkisebbek és kezelhetőbbek.

Ez bizonyos ellentmondáshoz vezet. Például egy osztály kevesebb sort foglalhat el egyetlen nagy módszerrel, mintha több kisebbre osztanánk olyan elvek alapján, mint például a behúzási szintek csökkentése. Nyilvánvalóan kompromisszumokkal járó döntésekről van szó. Az egyensúly abban rejlik, hogy az osztály és módszerei olvashatóak és érthetőek legyenek.

Kiképzés

Valamikor ezelőtt klasszikus algoritmusokat és adatstruktúrákat írtam a TDD használatával, és bár ezek viszonylag kicsi osztályok, vannak olyan struktúráik, amelyeket tovább lehetne javítani a javasolt korlátozások alkalmazásával, ezért nézzünk meg néhány példát, és hogyan tudjuk ezeket fejleszteni.

Érdekes megjegyzésként elmondani, hogy a legtöbb refaktort az IDE (PHPStorm) által biztosított eszközökkel fogom elvégezni.

Kezdjük a BubbleSort-tal, a legegyszerűbb és leg intuitívabb rendezési algoritmussal, bár sok elemnél nem túl hatékony:

A teszt egyébként ez, és a phpspec paranccsal történik:

Ebben az esetben van három behúzási szint és a korlátozás az, hogy mindegyik módszernek legfeljebb csak egy lehet. Ehhez az IDE segítségével kibontjuk a beágyazottat a saját módszerére, ügyelve arra, hogy a tesztek továbbra is sikeresek legyenek:

A teszt sikeres, tehát nem törtünk el semmit. Láthatunk néhány részletet, amelyek javíthatók ebben a kivonatban:

  • A $ length paraméter felesleges, mert mivel könnyen megszerezhetjük a $ forrásból, ezért kihagyjuk.
  • Használhattunk volna foreach szerkezetet a helyett .
  • A tömb eleme átadható indexe helyett.

Ez egy érdekes pont: a módszer kinyerésének ténye néhány visszatükröződést okoz számunkra korábbi döntéseinkről és a kód esetleges fejlesztéseiről. Tehát a folytatás előtt néhányat alkalmazunk.

Jelenleg az elem és nem az index átadása nem tűnik életképesnek, de némi javulást elértünk, mivel most nem kell kifejezetten kiszámítanunk a $ source hosszát, ami eggyel kevesebb sort jelent, és kifejezettebbek vagyunk az ötlet, hogy bejárjuk a tömböt. Továbbra is fenntartjuk a behúzás egyetlen szintjét, amelynek szintén csak egy vonala van.

Most megvan a behúzás két szintje belül az összehasonlítóEveryElementWithCurrent módszerrel, tehát kezeljük őket ugyanúgy: kivonatolás egy módszerre.

Nos, van néhány jó dolog errefelé:

  • Minden szinten csak egy szintű behúzás van.
  • A metódus nevében hivatkozunk egy aktuális elemre, ezért jó lenne ezt kódban kifejezni az $ i változó nevének $ currentIndex vagy hasonlóra történő megváltoztatásával, így a hivatkozás explicit.
  • Ugyanezt a bánásmódot alkalmazhatnánk a for foreach-vé konvertálásához és a $ length változó mentéséhez .

Végezzünk néhányat a következőkből:

Ezzel minden módszerben ellapítottuk a behúzási szinteket. A negatív rész az, hogy az osztály sorainak száma nőtt, de ezt kompenzálja az a tény, hogy mindegyik módszer jobban megmagyarázza, mit csinál, és elmélyíthetjük a magyarázatot, amire szükségünk van.

Van néhány dolog, amelyet szintén érdemes megjegyezni:

  • A $ source tömböt metódusról metódusra továbbítjuk és visszaküldjük. Felmerül a kérdés, hogy hivatkozással át lehet-e adni, hogy elkerüljük a visszatéréseket és megtartsuk a változásokat, vagy akár az osztályba mentse tulajdonként és működtessük. Az utolsó opcióval kapcsolatban azt mondanám, hogy nem, mivel az osztályba beágyazott algoritmusnak nem kell állapota lennie, és a tömb elmentése ezt megadná neki. A tömb referenciaként történő továbbadását illetően ez egy olyan lehetőség, amely egy kicsit tömörebbé tenné a kódot, de lehet, hogy más módjaink is vannak, így egyelőre nem fogjuk alkalmazni.
  • Másrészt a swapElementsIfCurrentIsLower metódusban van egy háromsoros blokk, amelyet érdemes megérdemelni a saját módszerével, hogy szándéka kifejezett legyen.

Egyébként a $ i névváltoztatást kiterjesztjük minden felhasználására, hogy az mindig világosabb legyen:

Ennek a refaktornak köszönhetően nem kell lemennünk a cseremechanizmus belébe, hogy megértsük, mi történik az elemekkel.

Ezután megpróbálunk néhány olyan fejlesztést végrehajtani, amelyek segítenek a kód kissé tisztításában és a sorok számának csökkentésében, és meg fogjuk csinálni, kihasználva azt a tényt, hogy az algoritmus minden lépését a saját módszere képviseli.

A swapElements-re fogunk összpontosítani, és alkalmazunk egy kicsit radikális kezelést, eltekintve a $ source és az ideiglenes változó átadásától. Átadjuk az elemeket a módszerre hivatkozva, hogy kicseréljük őket, és a PHP által kínált kis szintaktikus cukor egy csipetjén keresztül hozzárendeljük őket:

Most elmondható, hogy mindegyik módszer rendelkezik a lehető legkisebb behúzási szintekkel, valamint a lehető legkisebb vonalakkal.

Ekkor adjuk át a $ source-t referenciaként, több hozamot is megtakarítva ezzel, kivéve a fő nyilvános módszert.

Mások törlése

Ezúttal egy bináris keresési fát fogunk áttekinteni, amely félig fix. Más szavakkal: van néhány első próbálkozás a kód jobb állapotba hozására, de még mindig sok hely volt a fejlesztésre.

Mint láthatjuk, vannak olyan zónák, amelyeknek két szintje van a behúzással, és jó néhány mással .

A teszt, amely megvéd minket ebben a folyamatban, a következő:

A bináris keresési fa szerkezetét az jellemzi, hogy minden csomópontnak két gyermeke van. Amikor egy csomópontot beillesztenek, összehasonlítjuk a gyökér csomópontjával. Ha még nem létezik, mert még nincsenek elemek a fához, akkor az új csomópont lesz a gyökér. Ha a gyökércsomópont létezik, akkor az új csomópont beillesztésre kerül az insertNew módszerrel. Ezt teszi a beszúrás módszer, amit elemeket adunk a fához.

Menjünk oda. A beszúrási módszerrel kezdjük, amely egy mást tartalmaz:

Ebben az esetben csak vissza kell térnünk az if ághoz:

Alternatív megoldásként megfordíthattuk volna a feltétel pozitívat, ami könnyebben olvasható:

Az insertNew metódus csomópontokat ad hozzá egy adott csomópont alá. Egy ilyen bináris fában minden csomópontnak lehet két gyermeke (és így tovább rekurzív módon), úgy hogy a bal gyermekcsomópont kisebb értékeket tartalmaz, mint a szülőcsomópont, a másik csomópont pedig nagyobb értékeket tartalmaz. Ha bármelyik gyermekcsomópont már létezik, akkor rekurzívan megpróbálja hozzáadni az új csomópontot, amíg egy szabad „ágat” nem találnak annak elhelyezésére.

Ha már az insertNew-ról beszélünk, eléri a behúzás mindkét szintjét, és van még két más, mit tehetünk ellene?

Először meg fogom fordítani a negatív feltételeket:

Az első dolog, amit megfigyelhetünk, az, hogy a feltételes összes láb nem vezet a kimenetre, és előtte vagy utána nincs feldolgozás. Más szavakkal, hozhatunk hozamot az egyes lábakra. Ez megkönnyíti számunkra a többi kiküszöbölését, mert szükségtelenné teszi őket.

A teszt azt mutatja, hogy ez a változás nem befolyásolja a funkcionalitást, kiküszöböltük a másik szintet és az egyik esetet, amikor két szintes behúzás történt.

A módszer ellaposításához ki kell bontanunk a két fő végrehajtási utat a saját módszereikbe, egyértelművé téve szándékukat:

Az insertNew futtatásának két módja most már egyértelmű, és a kódja gyakorlatilag megegyezik. Ez azért érdekes, mert rávilágít a Ne ismételd meg önmagad elvének egyik félreértelmezésére. A DRY elv a tudásra vonatkozik, nem a kódra, bár néha egybeesnek. Ebben az esetben ugyanaz a felépítésünk, de ez mást jelent: hogyan kezeljünk nagyobb értéket és hogyan kezeljünk egy kisebb értéket.

A findParent metódusnak számos problémája van, két szintes behúzással, beágyazott feltételekkel és öt mással, eltekintve néhány hibától, amelyeket átmenetileg javíthatunk.

Először egy általános tisztítás. Ha több visszatérésünk van, akkor meg kell jelölnünk a visszatérés típusát annak biztosítása érdekében, hogy mindegyik következetes legyen. Ebben az esetben a módszer BinarySearchNode vagy null értéket adhat vissza, ha nem található.

Van negatív feltételünk. Ebben az esetben kissé kényesebb megfordítani őket, mivel három águk van, és ez megváltoztathatja a viselkedést. De mivel vannak tesztjeink, nézzük meg, mi történik, ha megtesszük:

A helyzet egyrészt rosszabbá válik, mert a behúzási szintek nőttek, másrészt javult, mert a többiek egy része teljesen elengedhetetlenné vált, ezért csak eltávolítjuk őket.

Mivel minden ágnak megvan a hozama, úgy gondolom, hogy jó ötlet lesz eltávolítani a maradék 3 mást:

A tesztek folyamatosan haladnak, és a módszer kissé laposabb. Jelenleg jó ötletnek tűnik visszavonni a korábban előrehaladottakat, mivel ha megfordítok bizonyos feltételeket, csökkenthetem a behúzások szintjét módszerek kibontása nélkül:

Lehetséges, hogy ugyanarra a pontra jutottunk volna, ha nem fordítottuk volna meg a feltételeket, amikor először találkoztunk a módszerrel. A legkevesebb, az a fontos, hogy biztonságosan mozogjon a kódban.

Ennek a változtatásnak a végrehajtása azt mutatja, hogy a módszernek két lehetséges folyamata van, ezért explicitvé tehetjük azt, ha minden blokkot a saját módszerére helyezünk át:

Az új findParent ma már sokkal könnyebben érthető, és az ágai is. Még a negatív feltételesek is őrzáradékként működnek, ami sokkal nyilvánvalóbbá teszi funkciójukat (ne csinálj semmit, ha nincs mivel dolgozni). Mindkét kivont ág alapvetően azt mondja nekünk, hogy ha a keresett érték megegyezik a csomópont bal- vagy jobboldali gyermekével, akkor az a szülőcsomópont. És ha nem egyezik, akkor keresse tovább.

Az utolsó módszer, amelyet vasalni tudunk, a findNode, amelyet itt bemutatok a szükséges lépésekkel annak naprakészen tartásához. A másik eltávolításának egyszerűnek kell lennie:

A módszer megtalálja az értéknek megfelelő csomópontot. Ha az aktuális egyezik, akkor visszaadja, és ha nem, akkor a bal oldalon keres, ha kevesebb, és a jobb oldalon, ha nagyobb. A lapított módszer így néz ki:

A BinarySearchTree most sokkal kevésbé félelmetes: