CodeGym /Java blog /Véletlen /Kódolási szabályok: a rendszer létrehozásától az objektum...
John Squirrels
Szint
San Francisco

Kódolási szabályok: a rendszer létrehozásától az objektumokkal való munkaig

Megjelent a csoportban
Szép napot mindenkinek! Ma a jó kód írásáról szeretnénk beszélni. Természetesen nem mindenki akar azonnal rágódni az olyan könyveken, mint a Clean Code, mivel rengeteg információt tartalmaznak, de elsőre nem sok minden világos. És mire befejezi az olvasást, kiölheti minden kódolási vágyát. Mindezt figyelembe véve, ma egy kis útmutatóval (egy kis ajánlással) szeretnék jobb kódot írni. Ebben a cikkben tekintsük át a rendszer létrehozásával, valamint az interfészekkel, osztályokkal és objektumokkal kapcsolatos alapvető szabályokat és fogalmakat. A cikk elolvasása nem fog sok időt igénybe venni, és remélem, nem fog untatni. Felülről lefelé haladok, azaz az alkalmazás általános felépítésétől a szűkebb részleteiig. Kódolási szabályok: A rendszer létrehozásától az objektumokkal való munkaig - 1

Rendszerek

A rendszer általánosan kívánatos jellemzői a következők:
  • Minimális komplexitás. A túl bonyolult projekteket kerülni kell. A legfontosabb az egyszerűség és az áttekinthetőség (egyszerűbb = jobb).
  • Könnyű karbantartás. Egy alkalmazás létrehozásakor ne feledje, hogy azt karban kell tartani (még akkor is, ha személyesen nem Ön felelős a karbantartásáért). Ez azt jelenti, hogy a kódnak világosnak és egyértelműnek kell lennie.
  • Laza tengelykapcsoló. Ez azt jelenti, hogy minimalizáljuk a függőségek számát a program különböző részei között (maximalizálva az OOP-elveknek való megfelelést).
  • Újrahasználhatóság. Rendszerünket úgy tervezzük, hogy az alkatrészeket más alkalmazásokban is újra felhasználhassuk.
  • Hordozhatóság. Könnyűnek kell lennie egy rendszert más környezethez igazítani.
  • Egységes stílus. Rendszerünket egységes stílusban alakítjuk ki a különböző komponenseiben.
  • Bővíthetőség (skálázhatóság). Bővíthetjük a rendszert anélkül, hogy megsértenénk az alapstruktúráját (egy komponens hozzáadása vagy módosítása nem érintheti az összes többit).
Gyakorlatilag lehetetlen olyan alkalmazást építeni, amely ne igényelne módosításokat vagy új funkciókat. Folyamatosan új részeket kell hozzáadnunk ahhoz, hogy szellemszüleményenk lépést tudjon tartani a korral. Itt jön képbe a méretezhetőség. A skálázhatóság lényegében az alkalmazás kiterjesztését, új funkciók hozzáadását és több erőforrás felhasználásával (vagy más szóval nagyobb terheléssel) való munkavégzést jelenti. Más szóval, az új logika egyszerűbbé tétele érdekében ragaszkodunk néhány szabályhoz, például csökkentjük a rendszer csatolását a modularitás növelésével.Kódolási szabályok: A rendszer létrehozásától az objektumokkal való munkaig - 2

Képforrás

A rendszer tervezésének szakaszai

  1. Szoftver rendszer. Tervezze meg az alkalmazást átfogóan.
  2. Felosztás alrendszerekre/csomagokra. Határozzon meg logikailag elkülönülő részeket, és határozza meg a köztük lévő interakció szabályait.
  3. Az alrendszerek osztályokra bontása. Ossza fel a rendszer részeit meghatározott osztályokra és interfészekre, és határozza meg a köztük lévő interakciót.
  4. Az osztályok felosztása metódusokra. Hozzon létre egy teljes definíciót az osztályhoz szükséges metódusokról, a hozzárendelt felelősség alapján.
  5. Módszer tervezés. Készítse el az egyes módszerek funkcionalitásának részletes meghatározását.
Általában a közönséges fejlesztők kezelik ezt a tervezést, míg az alkalmazás építésze a fent leírt pontokat.

A rendszertervezés általános elvei és fogalmai

Lusta inicializálás. Ebben a programozási idiómában az alkalmazás nem vesztegeti az időt egy objektum létrehozására, amíg azt ténylegesen nem használják. Ez felgyorsítja az inicializálási folyamatot és csökkenti a szemétgyűjtő terhelését. Ennek ellenére nem szabad túl messzire vinni, mert ez sértheti a modularitás elvét. Talán érdemes az összes építési példányt áthelyezni egy bizonyos részre, például a fő módszerre vagy egy gyári osztályba. A jó kód egyik jellemzője az ismétlődő, általános kód hiánya. Az ilyen kódokat általában egy külön osztályba helyezik, hogy szükség esetén meg lehessen hívni.

AOP

Szeretném megjegyezni az aspektusorientált programozást is. Ez a programozási paradigma az átlátható logika bevezetéséről szól. Vagyis az ismétlődő kódot osztályokba (aspektusokba) helyezik, és bizonyos feltételek teljesülése esetén hívják meg. Például egy metódus meghatározott névvel való meghívásakor vagy egy adott típusú változó elérésekor. Néha a szempontok zavaróak lehetnek, mivel nem egyértelmű, hogy honnan hívják a kódot, de ez még mindig nagyon hasznos funkció. Főleg gyorsítótárazáskor vagy naplózáskor. Ezt a funkciót anélkül adjuk hozzá, hogy további logikát adnánk a normál osztályokhoz. Kent Beck négy szabálya az egyszerű architektúrához:
  1. Kifejezőképesség – Az osztály szándékát egyértelműen ki kell fejezni. Ezt a megfelelő elnevezéssel, a kis mérettel és az egyfelelősség elvének betartásával érik el (amivel az alábbiakban részletesebben foglalkozunk).
  2. Az osztályok és módszerek minimális száma — Ha az a vágya, hogy az órákat a lehető legkisebbre és szűkebbre összpontosítsa, túl messzire mehet (ami a sörétes sebészeti antimintát eredményezi). Ez az elv megköveteli, hogy a rendszer kompakt legyen, és ne menjünk túl messzire, külön osztályt kell létrehozni minden lehetséges művelethez.
  3. Nincs duplikáció – A duplikált kódot, amely zavart kelt, és a rendszer nem optimális tervezését jelzi, a rendszer kivonja és egy másik helyre helyezi át.
  4. Lefuttatja az összes tesztet – Az összes teszten átmenő rendszer kezelhető. Bármilyen változtatás a teszt kudarcát okozhatja, felfedve számunkra, hogy a módszer belső logikájában bekövetkezett változásunk a rendszer viselkedését is váratlan módon megváltoztatta.

SZILÁRD

A rendszer tervezésénél érdemes figyelembe venni a jól ismert SOLID elveket:

S (egyszeri felelősség), O (nyitott-zárt), L (Liskov-helyettesítés), I (interfész szegregáció), D (függőségi inverzió).

Nem foglalkozunk az egyes elvekkel. Ez kicsit meghaladná ennek a cikknek a kereteit, de bővebben itt olvashat .

Felület

A jól megtervezett osztály létrehozásának talán egyik legfontosabb lépése egy jól megtervezett felület létrehozása, amely jó absztrakciót reprezentál, elrejti az osztály megvalósítási részleteit, és ezzel egyidejűleg bemutatja az egymással egyértelműen konzisztens metódusok csoportját. Nézzük meg közelebbről az egyik SOLID-elvet – interfész szegregációt: az ügyfelek (osztályok) ne alkalmazzanak olyan szükségtelen módszereket, amelyeket nem fognak használni. Más szóval, ha egy olyan interfész létrehozásáról beszélünk, amely a legkevesebb metódussal az interfész egyetlen feladatának ellátására irányul (ami szerintem nagyon hasonlít az egyfelelősségi elvhez), akkor jobb, ha ehelyett készítünk néhány kisebbet. egy dagadt felületről. Szerencsére egy osztály több felületet is megvalósíthat. Ne felejtse el megfelelően elnevezni a felületeket: a névnek a lehető legpontosabban kell tükröznie a hozzárendelt feladatot. És természetesen minél rövidebb, annál kevesebb zavart okoz. A dokumentációs megjegyzések általában az interfész szintjén íródnak. Ezek a megjegyzések részleteket adnak meg arról, hogy az egyes módszereknek mit kell tenniük, milyen érvekre van szükség, és mit adnak vissza.

Osztály

Kódolási szabályok: A rendszer létrehozásától az objektumokkal való munkaig – 3

Képforrás

Vessünk egy pillantást az órák belső elrendezésére. Illetve néhány szempont és szabály, amit be kell tartani az óraírás során. Általános szabály, hogy egy osztálynak a változók listájával kell kezdődnie meghatározott sorrendben:
  1. nyilvános statikus állandók;
  2. privát statikus állandók;
  3. privát példány változók.
Ezután következnek a különféle konstruktorok, sorrendben a legkevesebb érvvel rendelkezőktől a legtöbbet érvekig. Őket a legnyilvánosabbtól a legprivátabbig követik a módszerek. Általánosságban elmondható, hogy a privát módszerek, amelyek elrejtik néhány korlátozni kívánt funkció megvalósítását, a legalsó részben vannak.

Osztályméret

Most az osztályok létszámáról szeretnék beszélni. Emlékezzünk vissza az egyik SZILÁRD alapelvre – az egyetlen felelősség elvét. Kimondja, hogy minden objektumnak csak egy célja van (felelősség), és az összes módszer logikája ennek megvalósítására irányul. Ez azt mondja nekünk, hogy kerüljük a nagy, felduzzadt osztályokat (amelyek valójában az Isten objektum anti-mintája), és ha sok különböző logikával rendelkező metódus van egy osztályba zsúfolva, el kell gondolkodnunk azon, hogy szétválasztjuk őket egy osztályra. pár logikai rész (osztály). Ez viszont növeli a kód olvashatóságát, hiszen nem tart sokáig megérteni az egyes metódusok célját, ha ismerjük egy adott osztály hozzávetőleges célját. Ezenkívül ügyeljen az osztálynévre is, amelynek tükröznie kell a benne található logikát. Például, ha van egy osztályunk 20+ szóval a nevében, gondolnunk kell a refaktorálásra. Egyetlen önmagát tisztelő osztálynak sem szabad ennyi belső változóval rendelkeznie. Valójában mindegyik metódus működik egy vagy néhány közülük, ami nagy kohéziót okoz az osztályon belül (ami pontosan olyan, amilyennek lennie kell, hiszen az osztálynak egységes egésznek kell lennie). Ennek eredményeként egy osztály kohéziójának növelése az osztálylétszám csökkenéséhez vezet, és természetesen nő az osztályok száma. Ez néhány ember számára bosszantó, mivel jobban át kell néznie az osztályfájlokat, hogy lássa, hogyan működik egy adott nagy feladat. Mindezeken felül minden osztály egy kis modul, amelynek minimálisan kapcsolódnia kell másokhoz. Ez az elkülönítés csökkenti a változtatások számát, amikor további logikát adunk egy osztályhoz. mindegyik metódus egy vagy néhány közülük működik, nagy kohéziót okozva az osztályon belül (ami pontosan így van, hiszen az osztálynak egységes egésznek kell lennie). Ennek eredményeként egy osztály kohéziójának növelése az osztálylétszám csökkenéséhez vezet, és természetesen nő az osztályok száma. Ez néhány ember számára bosszantó, mivel jobban át kell néznie az osztályfájlokat, hogy lássa, hogyan működik egy adott nagy feladat. Mindezeken felül minden osztály egy kis modul, amelynek minimálisan kapcsolódnia kell másokhoz. Ez az elkülönítés csökkenti a változtatások számát, amikor további logikát adunk egy osztályhoz. mindegyik metódus egy vagy néhány közülük működik, nagy kohéziót okozva az osztályon belül (ami pontosan így van, hiszen az osztálynak egységes egésznek kell lennie). Ennek eredményeként egy osztály kohéziójának növelése az osztálylétszám csökkenéséhez vezet, és természetesen nő az osztályok száma. Ez néhány ember számára bosszantó, mivel jobban át kell néznie az osztályfájlokat, hogy lássa, hogyan működik egy adott nagy feladat. Mindezeken felül minden osztály egy kis modul, amelynek minimálisan kapcsolódnia kell másokhoz. Ez az elkülönítés csökkenti a változtatások számát, amikor további logikát adunk egy osztályhoz. s kohéziója az osztálylétszám csökkenéséhez vezet, és természetesen nő az osztályok száma. Ez néhány ember számára bosszantó, mivel jobban át kell néznie az osztályfájlokat, hogy lássa, hogyan működik egy adott nagy feladat. Mindezeken felül minden osztály egy kis modul, amelynek minimálisan kapcsolódnia kell másokhoz. Ez az elkülönítés csökkenti a változtatások számát, amikor további logikát adunk egy osztályhoz. s kohéziója az osztálylétszám csökkenéséhez vezet, és természetesen nő az osztályok száma. Ez néhány ember számára bosszantó, mivel jobban át kell néznie az osztályfájlokat, hogy lássa, hogyan működik egy adott nagy feladat. Mindezeken felül minden osztály egy kis modul, amelynek minimálisan kapcsolódnia kell másokhoz. Ez az elkülönítés csökkenti a változtatások számát, amikor további logikát adunk egy osztályhoz.

Objektumok

Egységbezárás

Itt először egy OOP-elvről fogunk beszélni: a kapszulázásról. Az implementáció elrejtése nem jelenti a változók izolálására szolgáló metódus létrehozását (a hozzáférés meggondolatlan korlátozása egyedi metódusokon, gettereken és settereken keresztül, ami nem jó, mert a beágyazás lényege elveszik). A hozzáférés elrejtése absztrakciók képzésére irányul, vagyis az osztály olyan megosztott konkrét metódusokat ad, amelyekkel az adatainkkal dolgozunk. És a felhasználónak nem kell pontosan tudnia, hogyan dolgozunk ezekkel az adatokkal – működik, és ez elég.

Demeter törvénye

Tekinthetjük Demeter törvényét is: ez egy kis szabályrendszer, amely segíti a komplexitás kezelését osztály- és metódusszinten. Tegyük fel, hogy van egy Car objektumunk, és van egy move(Object arg1, Object arg2) metódusa. Demeter törvénye szerint ez a módszer a következő hívásokra korlátozódik:
  • magának az Autó objektumnak (más szóval a "this" objektumnak) metódusai ;
  • a move metóduson belül létrehozott objektumok metódusai ;
  • argumentumként átadott objektumok metódusai ( arg1 , arg2 );
  • belső autóobjektumok metódusai (ismét "ez").
Más szóval, Demeter törvénye valami olyasmi, mint amit a szülők mondanak egy gyereknek: "beszélhetsz a barátaiddal, de nem idegenekkel".

Adatstruktúra

Az adatstruktúra kapcsolódó elemek gyűjteménye. Ha egy objektumot adatszerkezetnek tekintünk, akkor van egy sor adatelem, amelyen a metódusok működnek. Ezeknek a módszereknek a létezését implicit módon feltételezzük. Vagyis az adatstruktúra egy olyan objektum, amelynek célja a tárolt adatok tárolása és kezelése (feldolgozása). Legfontosabb különbsége a szokványos objektumoktól az, hogy egy közönséges objektum olyan metódusok gyűjteménye, amelyek olyan adatelemeken működnek, amelyekről implicit módon feltételezik, hogy léteznek. Érted? Egy közönséges objektum fő szempontja a metódusok. A belső változók elősegítik a helyes működésüket. De egy adatstruktúrában a módszerek arra valók, hogy támogassák a tárolt adatelemekkel végzett munkát, amelyek itt a legfontosabbak. Az adatstruktúra egyik típusa az adatátviteli objektum (DTO). Ez egy nyilvános változókat tartalmazó osztály (vagy csak olvasási/írási módszerek) és adatátvitelre szolgál adatbázisokkal végzett munka, socketekből származó üzenetek elemzése stb. Szinte azonnal olyan entitásra konvertálódik, amelyen az alkalmazásunk működik. Az entitás pedig egyben adatstruktúra is, de célja, hogy részt vegyen az üzleti logikában az alkalmazás különböző szintjein. A DTO célja az adatok átvitele az alkalmazásba/alkalmazásból. Példa DTO-ra: szintén egy adatstruktúra, de célja az üzleti logikában való részvétel az alkalmazás különböző szintjein. A DTO célja az adatok átvitele az alkalmazásba/alkalmazásból. Példa DTO-ra: szintén egy adatstruktúra, de célja az üzleti logikában való részvétel az alkalmazás különböző szintjein. A DTO célja az adatok átvitele az alkalmazásba/alkalmazásból. Példa DTO-ra:

@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Minden elég világosnak tűnik, de itt megtudjuk a hibridek létezését. A hibridek olyan objektumok, amelyeknek vannak metódusai a fontos logikák kezelésére, belső elemek tárolására, valamint hozzáférési (get/set) metódusokat is tartalmaznak. Az ilyen objektumok rendetlenek, és megnehezítik az új módszerek hozzáadását. Kerülni kell őket, mert nem világos, mire valók – elemek tárolására vagy logika végrehajtására?

A változók létrehozásának elvei

Gondolkodjunk el egy kicsit a változókon. Pontosabban, gondoljuk át, milyen elvek érvényesülnek a létrehozásuk során:
  1. Ideális esetben egy változót közvetlenül a használat előtt kell deklarálnia és inicializálnia (ne hozzon létre egyet, és ne felejtse el).
  2. Amikor csak lehetséges, deklarálja a változókat véglegesnek, nehogy értékük megváltozzon inicializálás után.
  3. Ne feledkezzünk meg a számlálóváltozókról sem, amelyeket általában valamilyen for ciklusban használunk. Vagyis ne felejtse el nullázni őket. Ellenkező esetben minden logikánk megtörhet.
  4. Meg kell próbálnia inicializálni a változókat a konstruktorban.
  5. Ha választhat egy objektumot hivatkozással vagy anélkül ( new SomeObject() ), válassza az objektumot anélkül, mert az objektum felhasználása után a következő szemétgyűjtési ciklus során törlődik, és erőforrásai nem vesznek kárba.
  6. A változó élettartama (a változó létrehozása és az utolsó hivatkozás közötti távolság) legyen a lehető legrövidebb.
  7. A ciklusban használt változókat közvetlenül a ciklus előtt kell inicializálni, nem pedig a ciklust tartalmazó metódus elején.
  8. Mindig a legkorlátozottabb hatókörrel kezdje, és csak szükség esetén bővítse (meg kell próbálnia a változót a lehető leghelyesebbé tenni).
  9. Minden változót csak egy célra használjon.
  10. Kerülje a rejtett célú változókat, pl. két feladat között felosztott változót – ez azt jelenti, hogy a típusa nem alkalmas az egyik megoldására.

Mód

Kódolási szabályok: A rendszer létrehozásától az objektumokkal való munkaig – 4

a "Star Wars: Episode III - Revenge of the Sith" című filmből (2005)

Folytassuk közvetlenül a logikánk megvalósításával, azaz a metódusokkal.
  1. 1. szabály – tömörség. Ideális esetben egy módszer nem haladhatja meg a 20 sort. Ez azt jelenti, hogy ha egy nyilvános metódus jelentősen "duzzad", akkor el kell gondolkodni a logika szétbontásán és külön privát metódusokba való áthelyezésén.

  2. 2. szabály – az if , else , while és más utasítások nem tartalmazhatnak erősen beágyazott blokkokat: a sok egymásba ágyazás jelentősen csökkenti a kód olvashatóságát. Ideális esetben legfeljebb két beágyazott {} blokk lehet.

    És az is kívánatos, hogy ezekben a blokkokban a kód kompakt és egyszerű legyen.

  3. 3. szabály – Egy metódusnak csak egy műveletet kell végrehajtania. Vagyis ha egy metódus mindenféle összetett logikát végrehajt, akkor azt részmetódusokra bontjuk. Ennek eredményeként maga a módszer egy homlokzat lesz, amelynek célja az összes többi művelet megfelelő sorrendben történő meghívása.

    De mi van akkor, ha a művelet túl egyszerűnek tűnik ahhoz, hogy külön módszerbe helyezzük? Igaz, néha olyan érzés lehet, mintha ágyút lőnének verebekre, de a kis módszerek számos előnnyel járnak:

    • Jobb kódértés;
    • A módszerek a fejlesztés előrehaladtával általában bonyolultabbá válnak. Ha egy módszer kezdetben egyszerű, akkor egy kicsit könnyebb lesz bonyolítani a működését;
    • A megvalósítás részletei rejtettek;
    • Könnyebb kód újrafelhasználás;
    • Megbízhatóbb kód.

  4. A lelépési szabály – A kódot felülről lefelé kell olvasni: minél lejjebb olvasol, annál mélyebbre ásod a logikát. És fordítva, minél magasabbra mész, annál elvontabbak a módszerek. Például a switch utasítások meglehetősen nem tömörek és nemkívánatosak, de ha nem kerülheti el a switch használatát, akkor próbálja meg a lehető legalacsonyabbra helyezni, a legalacsonyabb szintű módszerekre.

  5. A módszer argumentumai – Mi az ideális szám? Ideális esetben egyiket sem :) De ez tényleg megtörténik? Ennek ellenére törekedjen arra, hogy a lehető legkevesebb érv legyen, mert minél kevesebb van, annál könnyebb egy módszert használni, és annál könnyebben tesztelhető. Ha kétségei vannak, próbálja meg előre látni az összes forgatókönyvet a módszer nagyszámú bemeneti paraméterrel történő használatához.

  6. Ezenkívül jó lenne elkülöníteni azokat a metódusokat, amelyek bemeneti paramétere egy logikai jelző, mivel ez önmagában azt jelenti, hogy a metódus egynél több műveletet hajt végre (ha igaz, akkor tegyen egyet; ha hamis, akkor tegyen egy másikat). Ahogy fentebb is írtam, ez nem jó, és lehetőleg kerülni kell.

  7. Ha egy metódusnak sok bemeneti paramétere van (a szélsőérték 7, de tényleg 2-3 után érdemes elkezdeni gondolkodni), akkor az argumentumok egy részét külön objektumba kell csoportosítani.

  8. Ha több hasonló (túlterhelt) módszer létezik, akkor a hasonló paramétereket ugyanabban a sorrendben kell átadni: ez javítja az olvashatóságot és a használhatóságot.

  9. Amikor paramétereket ad át egy metódusnak, biztosnak kell lennie abban, hogy mindegyiket felhasználja, különben miért van szüksége rájuk? Vágja ki a nem használt paramétereket az interfészből, és végezze el.

  10. A try/catch a természetben nem néz ki túl szépnek, ezért érdemes lenne áthelyezni egy külön köztes metódusba (a kivételek kezelésére szolgáló módszer):

    
    public void exceptionHandling(SomeObject obj) {
        try {  
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

Fentebb a duplikált kódról beszéltem, de hadd ismételjem meg még egyszer: Ha van néhány metódusunk ismétlődő kóddal, akkor azt át kell helyeznünk egy külön metódusba. Ezzel a metódus és az osztály is tömörebb lesz. Ne feledkezzünk meg a nevekre vonatkozó szabályokról: az osztályok, interfészek, metódusok és változók megfelelő elnevezésének részleteiről a cikk következő részében lesz szó. De ma csak ennyit tudok neked adni.
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION