CodeGym /Java blogg /Slumpmässig /Kodningsregler: Från att skapa ett system till att arbeta...
John Squirrels
Nivå
San Francisco

Kodningsregler: Från att skapa ett system till att arbeta med objekt

Publicerad i gruppen
God dag, allihop! Idag vill vi prata med dig om att skriva bra kod. Naturligtvis vill inte alla tugga på böcker som Clean Code direkt, eftersom de innehåller rikliga mängder information men inte mycket är klart till en början. Och när du har läst klart kan du döda all din önskan att koda. Med tanke på allt detta vill jag idag ge dig en liten guide (en liten uppsättning rekommendationer) för att skriva bättre kod. I den här artikeln ska vi gå igenom de grundläggande reglerna och begreppen relaterade till att skapa ett system och arbeta med gränssnitt, klasser och objekt. Att läsa den här artikeln kommer inte att ta mycket tid och jag hoppas att det inte kommer att tråka ut dig. Jag ska arbeta mig från toppen till botten, dvs från den allmänna strukturen i en applikation till dess smalare detaljer. Kodningsregler: Från att skapa ett system till att arbeta med objekt - 1

System

Följande är generellt sett önskvärda egenskaper hos ett system:
  • Minimal komplexitet. Alltför komplicerade projekt måste undvikas. Det viktigaste är enkelhet och tydlighet (enklare = bättre).
  • Enkelt underhåll. När du skapar en applikation måste du komma ihåg att den måste underhållas (även om du personligen inte är ansvarig för att underhålla den). Det betyder att koden måste vara tydlig och tydlig.
  • Lös koppling. Detta innebär att vi minimerar antalet beroenden mellan olika delar av programmet (maximerar vår efterlevnad av OOP-principerna).
  • Återanvändbarhet. Vi designar vårt system med möjlighet att återanvända komponenter i andra applikationer.
  • Bärbarhet. Det ska vara lätt att anpassa ett system till en annan miljö.
  • Enhetlig stil. Vi designar vårt system med en enhetlig stil i dess olika komponenter.
  • Utökningsbarhet (skalbarhet). Vi kan förbättra systemet utan att bryta mot dess grundläggande struktur (att lägga till eller ändra en komponent bör inte påverka alla andra).
Det är praktiskt taget omöjligt att bygga en applikation som inte kräver modifieringar eller ny funktionalitet. Vi kommer hela tiden att behöva lägga till nya delar för att hjälpa vår idé att hänga med i tiden. Det är här skalbarhet kommer in i bilden. Skalbarhet är i huvudsak att utöka applikationen, lägga till ny funktionalitet och arbeta med fler resurser (eller med andra ord, med en större belastning). Med andra ord, för att göra det lättare att lägga till ny logik håller vi oss till några regler, som att minska systemets koppling genom att öka modulariteten.Kodningsregler: Från att skapa ett system till att arbeta med objekt - 2

Bildkälla

Stadier för att designa ett system

  1. Mjukvarusystem. Designa applikationen övergripande.
  2. Indelning i delsystem/paket. Definiera logiskt distinkta delar och definiera reglerna för interaktion mellan dem.
  3. Indelning av delsystem i klasser. Dela upp delar av systemet i specifika klasser och gränssnitt och definiera interaktionen mellan dem.
  4. Indelning av klasser i metoder. Skapa en fullständig definition av de nödvändiga metoderna för en klass, baserat på dess tilldelade ansvar.
  5. Metoddesign. Skapa en detaljerad definition av funktionaliteten hos enskilda metoder.
Vanligtvis sköter vanliga utvecklare denna design, medan applikationens arkitekt sköter punkterna som beskrivs ovan.

Allmänna principer och koncept för systemdesign

Lat initiering. I detta programmeringsspråk slösar inte applikationen tid på att skapa ett objekt förrän det faktiskt används. Detta påskyndar initieringsprocessen och minskar belastningen på sopsamlaren. Som sagt, du ska inte ta det här för långt, eftersom det kan bryta mot principen om modularitet. Kanske är det värt att flytta alla fall av konstruktion till någon specifik del, till exempel huvudmetoden eller till en fabriksklass . En egenskap hos bra kod är frånvaron av upprepad kod. Som regel placeras sådan kod i en separat klass så att den kan anropas vid behov.

AOP

Jag skulle också vilja notera aspektorienterad programmering. Detta programmeringsparadigm handlar om att introducera transparent logik. Det vill säga, repetitiv kod läggs in i klasser (aspekter) och anropas när vissa villkor är uppfyllda. Till exempel när du anropar en metod med ett specifikt namn eller får åtkomst till en variabel av en specifik typ. Ibland kan aspekter vara förvirrande, eftersom det inte är omedelbart klart varifrån koden anropas, men detta är fortfarande mycket användbar funktionalitet. Speciellt vid cachning eller loggning. Vi lägger till denna funktionalitet utan att lägga till ytterligare logik till vanliga klasser. Kent Becks fyra regler för en enkel arkitektur:
  1. Uttrycksförmåga — Avsikten med en klass bör uttryckas tydligt. Detta uppnås genom korrekt namngivning, liten storlek och efterlevnad av principen om ett enda ansvar (som vi kommer att överväga mer i detalj nedan).
  2. Minsta antal klasser och metoder — I din önskan att göra klasserna så små och snävt fokuserade som möjligt kan du gå för långt (vilket resulterar i hagelgevärskirurgins antimönster). Denna princip kräver att systemet ska hållas kompakt och inte gå för långt, skapa en separat klass för varje möjlig åtgärd.
  3. Ingen duplicering — Duplicerad kod, som skapar förvirring och är en indikation på suboptimal systemdesign, extraheras och flyttas till en separat plats.
  4. Kör alla tester — Ett system som klarar alla tester är hanterbart. Varje förändring kan leda till att ett test misslyckas, vilket avslöjar för oss att vår förändring av en metods interna logik också förändrade systemets beteende på oväntade sätt.

FAST

När du designar ett system är de välkända SOLID-principerna värda att överväga:

S (enkelt ansvar), O (öppet-stängt), L (Liskov substitution), I (gränssnittssegregation), D (beroendeinversion).

Vi kommer inte att uppehålla oss vid varje enskild princip. Det skulle vara lite utanför ramen för denna artikel, men du kan läsa mer här .

Gränssnitt

Ett av de viktigaste stegen för att skapa en väldesignad klass är kanske att skapa ett väldesignat gränssnitt som representerar en bra abstraktion, dölja implementeringsdetaljerna för klassen och samtidigt presentera en grupp metoder som tydligt överensstämmer med varandra. Låt oss ta en närmare titt på en av SOLID-principerna — gränssnittssegregering: klienter (klasser) ska inte implementera onödiga metoder som de inte kommer att använda. Med andra ord, om vi pratar om att skapa ett gränssnitt med minst antal metoder som syftar till att utföra gränssnittets enda jobb (som jag tycker är väldigt lik principen om ett ansvar), är det bättre att skapa ett par mindre istället. av ett uppsvällt gränssnitt. Lyckligtvis kan en klass implementera mer än ett gränssnitt. Kom ihåg att namnge dina gränssnitt korrekt: namnet ska återspegla den tilldelade uppgiften så exakt som möjligt. Och, naturligtvis, ju kortare det är, desto mindre förvirring kommer det att orsaka. Dokumentationskommentarer skrivs vanligtvis på gränssnittsnivå. Dessa kommentarer ger detaljer om vad varje metod ska göra, vilka argument den kräver och vad den kommer att returnera.

Klass

Kodningsregler: Från att skapa ett system till att arbeta med objekt - 3

Bildkälla

Låt oss ta en titt på hur klasserna ordnas internt. Eller snarare några perspektiv och regler som bör följas när man skriver klasser. Som regel bör en klass börja med en lista med variabler i en specifik ordning:
  1. offentliga statiska konstanter;
  2. privata statiska konstanter;
  3. privata instansvariabler.
Därefter kommer de olika konstruktörerna, i ordning från de med minst argument till de med flest. De följs av metoder från de mest offentliga till de mest privata. Generellt sett ligger privata metoder som döljer implementeringen av någon funktionalitet vi vill begränsa längst ner.

Klasstorlek

Nu skulle jag vilja prata om storleken på klasserna. Låt oss påminna om en av SOLID-principerna — principen om ett enda ansvar. Den säger att varje objekt bara har ett syfte (ansvar), och logiken i alla dess metoder syftar till att uppnå det. Detta säger till oss att undvika stora, uppsvällda klasser (som faktiskt är gudsobjektets anti-mönster), och om vi har många metoder med alla möjliga olika logik inklämda i en klass, måste vi tänka på att dela upp den i en klass. ett par logiska delar (klasser). Detta kommer i sin tur att öka läsbarheten för koden, eftersom det inte tar lång tid att förstå syftet med varje metod om vi vet det ungefärliga syftet med en given klass. Håll också ett öga på klassnamnet, som bör återspegla logiken den innehåller. Till exempel, om vi har en klass med 20+ ord i namnet, vi måste tänka på omfaktorisering. Varje klass med självrespekt borde inte ha så många interna variabler. Faktum är att varje metod fungerar med en eller ett fåtal av dem, vilket orsakar mycket sammanhållning inom klassen (vilket är precis som det ska vara, eftersom klassen ska vara en enhetlig helhet). Som ett resultat leder en ökning av en klasss sammanhållning till att klassstorleken minskar, och naturligtvis ökar antalet klasser. Detta är irriterande för vissa människor, eftersom du behöver gå igenom klassfiler mer för att se hur en specifik stor uppgift fungerar. Utöver det hela är varje klass en liten modul som ska vara minimalt relaterad till andra. Denna isolering minskar antalet ändringar vi behöver göra när vi lägger till ytterligare logik i en klass. varje metod fungerar med en eller några av dem, vilket orsakar mycket sammanhållning inom klassen (vilket är precis som det ska vara, eftersom klassen ska vara en enhetlig helhet). Som ett resultat leder en ökning av en klasss sammanhållning till att klassstorleken minskar, och naturligtvis ökar antalet klasser. Detta är irriterande för vissa människor, eftersom du behöver gå igenom klassfiler mer för att se hur en specifik stor uppgift fungerar. Utöver det hela är varje klass en liten modul som ska vara minimalt relaterad till andra. Denna isolering minskar antalet ändringar vi behöver göra när vi lägger till ytterligare logik i en klass. varje metod fungerar med en eller några av dem, vilket orsakar mycket sammanhållning inom klassen (vilket är precis som det ska vara, eftersom klassen ska vara en enhetlig helhet). Som ett resultat leder en ökning av en klasss sammanhållning till att klassstorleken minskar, och naturligtvis ökar antalet klasser. Detta är irriterande för vissa människor, eftersom du behöver gå igenom klassfiler mer för att se hur en specifik stor uppgift fungerar. Utöver det hela är varje klass en liten modul som ska vara minimalt relaterad till andra. Denna isolering minskar antalet ändringar vi behöver göra när vi lägger till ytterligare logik i en klass. s sammanhållning leder till en minskning av klassstorleken, och naturligtvis ökar antalet klasser. Detta är irriterande för vissa människor, eftersom du behöver gå igenom klassfiler mer för att se hur en specifik stor uppgift fungerar. Utöver det hela är varje klass en liten modul som ska vara minimalt relaterad till andra. Denna isolering minskar antalet ändringar vi behöver göra när vi lägger till ytterligare logik i en klass. s sammanhållning leder till en minskning av klassstorleken, och naturligtvis ökar antalet klasser. Detta är irriterande för vissa människor, eftersom du behöver gå igenom klassfiler mer för att se hur en specifik stor uppgift fungerar. Utöver det hela är varje klass en liten modul som ska vara minimalt relaterad till andra. Denna isolering minskar antalet ändringar vi behöver göra när vi lägger till ytterligare logik i en klass.

Föremål

Inkapsling

Här ska vi först prata om en OOP-princip: inkapsling. Att dölja implementeringen innebär inte att skapa en metod för att isolera variabler (tänklöst begränsa åtkomsten genom individuella metoder, getters och sättare, vilket inte är bra, eftersom hela poängen med inkapslingen går förlorad). Att dölja åtkomst syftar till att bilda abstraktioner, det vill säga att klassen tillhandahåller delade konkreta metoder som vi använder för att arbeta med vår data. Och användaren behöver inte veta exakt hur vi arbetar med denna data – det fungerar och det räcker.

Demeters lag

Vi kan också överväga Demeterlagen: det är en liten uppsättning regler som hjälper till att hantera komplexitet på klass- och metodnivå. Anta att vi har ett bilobjekt och det har en move(Object arg1, Object arg2) -metod. Enligt lagen om Demeter är denna metod begränsad till att anropa:
  • metoder för själva bilobjektet (med andra ord "det här" objektet);
  • metoder för objekt skapade inom flyttmetoden ;
  • metoder för objekt som skickas som argument ( arg1 , arg2 );
  • metoder för interna bilobjekt (igen, "detta").
Med andra ord, lagen om Demeter är ungefär det som föräldrar kan säga till ett barn: "du kan prata med dina vänner, men inte med främlingar".

Datastruktur

En datastruktur är en samling relaterade element. När man betraktar ett objekt som en datastruktur, finns det en uppsättning dataelement som metoder fungerar på. Existensen av dessa metoder antas implicit. Det vill säga en datastruktur är ett objekt vars syfte är att lagra och arbeta med (bearbeta) den lagrade datan. Dess nyckelskillnad från ett vanligt objekt är att ett vanligt objekt är en samling metoder som verkar på dataelement som implicit antas existera. Förstår du? Huvudaspekten av ett vanligt objekt är metoder. Interna variabler underlättar deras korrekta funktion. Men i en datastruktur finns metoderna till för att stödja ditt arbete med de lagrade dataelementen, vilket är av största vikt här. En typ av datastruktur är ett dataöverföringsobjekt (DTO). Detta är en klass med publika variabler och inga metoder (eller bara metoder för att läsa/skriva) som används för att överföra data när man arbetar med databaser, tolkar meddelanden från sockets etc. Data lagras vanligtvis inte i sådana objekt under en längre period. Den konverteras nästan omedelbart till den typ av enhet som vår applikation fungerar. En entitet är i sin tur också en datastruktur, men dess syfte är att delta i affärslogik på olika nivåer av applikationen. Syftet med en DTO är att transportera data till/från applikationen. Exempel på en DTO: är också en datastruktur, men dess syfte är att delta i affärslogik på olika nivåer av applikationen. Syftet med en DTO är att transportera data till/från applikationen. Exempel på en DTO: är också en datastruktur, men dess syfte är att delta i affärslogik på olika nivåer av applikationen. Syftet med en DTO är att transportera data till/från applikationen. Exempel på en DTO:

@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Allt verkar tydligt nog, men här lär vi oss om existensen av hybrider. Hybrider är objekt som har metoder för att hantera viktig logik, lagra interna element och även inkluderar accessor (get/set) metoder. Sådana föremål är röriga och gör det svårt att lägga till nya metoder. Du bör undvika dem, eftersom det inte är klart vad de är till för - lagra element eller exekvera logik?

Principer för att skapa variabler

Låt oss fundera lite över variabler. Mer specifikt, låt oss fundera över vilka principer som gäller när du skapar dem:
  1. Helst bör du deklarera och initiera en variabel precis innan du använder den (skapa inte en och glöm den).
  2. När det är möjligt, förklara variabler som slutgiltiga för att förhindra att deras värde ändras efter initialisering.
  3. Glöm inte räknarvariabler, som vi vanligtvis använder i någon form av for- loop. Det vill säga, glöm inte att nollställa dem. Annars kan all vår logik brista.
  4. Du bör försöka initiera variabler i konstruktorn.
  5. Om det finns ett val mellan att använda ett objekt med en referens eller utan ( new SomeObject() ), välj utan, eftersom efter att objektet har använts kommer det att tas bort under nästa sophämtningscykel och dess resurser kommer inte att slösas bort.
  6. Håll en variabels livslängd (avståndet mellan skapandet av variabeln och sista gången den refereras) så kort som möjligt.
  7. Initiera variabler som används i en loop precis före loopen, inte i början av metoden som innehåller loopen.
  8. Börja alltid med den mest begränsade omfattningen och expandera endast när det behövs (du bör försöka göra en variabel så lokal som möjligt).
  9. Använd varje variabel endast för ett ändamål.
  10. Undvik variabler med ett dolt syfte, t.ex. en variabel uppdelad mellan två uppgifter – det betyder att dess typ inte är lämplig för att lösa en av dem.

Metoder

Kodningsregler: Från att skapa ett system till att arbeta med objekt - 4

från filmen "Star Wars: Episod III - Revenge of the Sith" (2005)

Låt oss gå direkt till implementeringen av vår logik, dvs till metoder.
  1. Regel #1 — Kompakthet. Helst bör en metod inte överstiga 20 rader. Det betyder att om en offentlig metod "sväller" avsevärt måste du tänka på att bryta isär logiken och flytta den till separata privata metoder.

  2. Regel #2 — if , else , while och andra påståenden ska inte ha kraftigt kapslade block: massor av kapsling minskar kodens läsbarhet avsevärt. Helst bör du inte ha fler än två kapslade {} -block.

    Och det är också önskvärt att hålla koden i dessa block kompakt och enkel.

  3. Regel #3 — En metod bör endast utföra en operation. Det vill säga, om en metod utför alla möjliga typer av komplex logik, delar vi upp den i undermetoder. Som ett resultat kommer själva metoden att bli en fasad vars syfte är att anropa alla andra operationer i rätt ordning.

    Men vad händer om operationen verkar för enkel för att läggas in i en separat metod? Visserligen kan det ibland kännas som att skjuta en kanon mot sparvar, men små metoder ger ett antal fördelar:

    • Bättre kodförståelse;
    • Metoder tenderar att bli mer komplexa när utvecklingen fortskrider. Om en metod är enkel till att börja med, så blir det lite lättare att komplicera dess funktionalitet;
    • Implementeringsdetaljer är dolda;
    • Enklare kodåteranvändning;
    • Mer pålitlig kod.

  4. Stepdown-regeln — Koden bör läsas uppifrån och ned: ju lägre du läser, desto djupare fördjupar du dig i logiken. Och vice versa, ju högre du kommer, desto mer abstrakta metoder. Switch-satser är till exempel ganska icke-kompakta och oönskade, men om du inte kan undvika att använda en switch bör du försöka flytta den så lågt som möjligt, till metoderna på den lägsta nivån.

  5. Metodargument — Vilket är det ideala talet? Helst inga alls :) Men händer det verkligen? Som sagt, du bör försöka ha så få argument som möjligt, för ju färre det finns desto lättare är det att använda en metod och desto lättare är det att testa den. Om du är osäker, försök att förutse alla scenarier för att använda metoden med ett stort antal indataparametrar.

  6. Dessutom skulle det vara bra att separera metoder som har en boolesk flagga som indataparameter, eftersom detta helt och hållet innebär att metoden utför mer än en operation (om sant, gör en sak; om falskt, gör sedan en annan). Som jag skrev ovan är detta inte bra och bör undvikas om möjligt.

  7. Om en metod har ett stort antal inmatningsparametrar (en ytterlighet är 7, men du borde verkligen börja tänka efter 2-3), bör några av argumenten grupperas i ett separat objekt.

  8. Om det finns flera liknande (överbelastade) metoder måste liknande parametrar skickas i samma ordning: detta förbättrar läsbarheten och användbarheten.

  9. När du skickar parametrar till en metod måste du vara säker på att alla används, annars varför behöver du dem? Klipp bort alla oanvända parametrar från gränssnittet och gör klart med det.

  10. try/catch ser inte särskilt trevlig ut i naturen, så det skulle vara en bra idé att flytta den till en separat mellanmetod (en metod för att hantera undantag):

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

Jag talade om dubblettkod ovan, men låt mig upprepa en gång till: Om vi ​​har ett par metoder med upprepad kod måste vi flytta den till en separat metod. Detta kommer att göra både metoden och klassen mer kompakt. Glöm inte reglerna som styr namn: detaljer om hur man korrekt namnger klasser, gränssnitt, metoder och variabler kommer att diskuteras i nästa del av artikeln. Men det är allt jag har för dig idag.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION