CodeGym /Java-blogg /Tilfeldig /Kodingsregler: Fra å lage et system til å jobbe med objek...
John Squirrels
Nivå
San Francisco

Kodingsregler: Fra å lage et system til å jobbe med objekter

Publisert i gruppen
God dag, alle sammen! I dag vil vi gjerne snakke med deg om å skrive god kode. Selvfølgelig er det ikke alle som ønsker å tygge på bøker som Clean Code med en gang, siden de inneholder store mengder informasjon, men ikke mye er klart med det første. Og når du er ferdig med å lese, kan du drepe alt ditt ønske om å kode. Med tanke på alt dette, vil jeg i dag gi deg en liten guide (et lite sett med anbefalinger) for å skrive bedre kode. I denne artikkelen skal vi gå gjennom de grunnleggende reglene og konseptene knyttet til å lage et system og arbeide med grensesnitt, klasser og objekter. Å lese denne artikkelen vil ikke ta mye tid, og jeg håper ikke det vil kjede deg. Jeg skal jobbe meg fra toppen til bunnen, dvs. fra den generelle strukturen til en applikasjon til dens smalere detaljer. Kodingsregler: Fra å lage et system til å jobbe med objekter - 1

Systemer

Følgende er generelt ønskelige egenskaper ved et system:
  • Minimal kompleksitet. Altfor kompliserte prosjekter må unngås. Det viktigste er enkelhet og klarhet (enklere = bedre).
  • Enkel vedlikehold. Når du oppretter en applikasjon, må du huske at den må vedlikeholdes (selv om du personlig ikke er ansvarlig for å vedlikeholde den). Dette betyr at koden må være klar og tydelig.
  • Løs kobling. Dette betyr at vi minimerer antall avhengigheter mellom ulike deler av programmet (maksimerer vår etterlevelse av OOP-prinsippene).
  • Gjenbrukbarhet. Vi designer systemet vårt med muligheten til å gjenbruke komponenter i andre applikasjoner.
  • Bærbarhet. Det skal være enkelt å tilpasse et system til et annet miljø.
  • Ensartet stil. Vi designer systemet vårt med en enhetlig stil i de ulike komponentene.
  • Utvidbarhet (skalerbarhet). Vi kan forbedre systemet uten å krenke dets grunnleggende struktur (å legge til eller endre en komponent bør ikke påvirke alle de andre).
Det er praktisk talt umulig å bygge en applikasjon som ikke krever modifikasjoner eller ny funksjonalitet. Vi må hele tiden legge til nye deler for å hjelpe hjernebarnet vårt å følge med i tiden. Det er her skalerbarhet kommer inn i bildet. Skalerbarhet er i hovedsak å utvide applikasjonen, legge til ny funksjonalitet og jobbe med flere ressurser (eller, med andre ord, med større belastning). Med andre ord, for å gjøre det lettere å legge til ny logikk, holder vi oss til noen regler, som å redusere systemets kobling ved å øke modulariteten.Kodingsregler: Fra å lage et system til å jobbe med objekter - 2

Bildekilde

Stadier for å designe et system

  1. Programvaresystem. Design søknaden overordnet.
  2. Inndeling i delsystemer/pakker. Definer logisk distinkte deler og definer reglene for interaksjon mellom dem.
  3. Inndeling av delsystemer i klasser. Del deler av systemet inn i spesifikke klasser og grensesnitt, og definer interaksjonen mellom dem.
  4. Inndeling av klasser i metoder. Lag en fullstendig definisjon av de nødvendige metodene for en klasse, basert på dens tildelte ansvar.
  5. Metodedesign. Lag en detaljert definisjon av funksjonaliteten til individuelle metoder.
Vanligvis håndterer vanlige utviklere dette designet, mens applikasjonens arkitekt håndterer punktene beskrevet ovenfor.

Generelle prinsipper og konsepter for systemdesign

Lat initialisering. I dette programmeringsspråket kaster ikke applikasjonen bort tid på å lage et objekt før det faktisk er brukt. Dette fremskynder initialiseringsprosessen og reduserer belastningen på søppeloppsamleren. Når det er sagt, bør du ikke ta dette for langt, fordi det kan bryte med prinsippet om modularitet. Kanskje det er verdt å flytte alle forekomster av konstruksjon til en bestemt del, for eksempel hovedmetoden eller til en fabrikkklasse . Et kjennetegn ved god kode er fravær av repeterende kode. Som regel plasseres slik kode i en egen klasse slik at den kan ringes opp ved behov.

AOP

Jeg vil også merke meg aspektorientert programmering. Dette programmeringsparadigmet handler om å introdusere transparent logikk. Det vil si at repeterende kode settes inn i klasser (aspekter) og kalles når visse betingelser er oppfylt. For eksempel når du kaller en metode med et spesifikt navn eller får tilgang til en variabel av en bestemt type. Noen ganger kan aspekter være forvirrende, siden det ikke umiddelbart er klart hvor koden kalles fra, men dette er fortsatt svært nyttig funksjonalitet. Spesielt ved caching eller logging. Vi legger til denne funksjonaliteten uten å legge til ekstra logikk til vanlige klasser. Kent Becks fire regler for en enkel arkitektur:
  1. Ekspressivitet — Hensikten med en klasse skal være tydelig uttrykt. Dette oppnås gjennom riktig navn, liten størrelse og overholdelse av enkeltansvarsprinsippet (som vi vil vurdere mer detaljert nedenfor).
  2. Minimum antall klasser og metoder — I ditt ønske om å gjøre klassene så små og smalt fokuserte som mulig, kan du gå for langt (noe som resulterer i haglekirurgi-anti-mønsteret). Dette prinsippet krever å holde systemet kompakt og ikke gå for langt, og skape en egen klasse for hver mulig handling.
  3. Ingen duplisering — Duplikatkode, som skaper forvirring og er en indikasjon på suboptimal systemdesign, trekkes ut og flyttes til et eget sted.
  4. Kjører alle testene — Et system som består alle testene er håndterbart. Enhver endring kan føre til at en test mislykkes, og avsløre for oss at vår endring i en metodes interne logikk også endret systemets oppførsel på uventede måter.

FAST

Når du designer et system, er de velkjente SOLID-prinsippene verdt å vurdere:

S (enkelt ansvar), O (åpen-lukket), L (Liskov-substitusjon), I (grensesnittsegregering), D (avhengighetsinversjon).

Vi vil ikke dvele ved hvert enkelt prinsipp. Det ville vært litt utenfor rammen av denne artikkelen, men du kan lese mer her .

Grensesnitt

Kanskje et av de viktigste trinnene i å lage en godt designet klasse er å lage et godt designet grensesnitt som representerer en god abstraksjon, skjule implementeringsdetaljene til klassen og samtidig presentere en gruppe metoder som er tydelig i samsvar med hverandre. La oss se nærmere på et av SOLID-prinsippene — grensesnittsegregering: klienter (klasser) bør ikke implementere unødvendige metoder som de ikke vil bruke. Med andre ord, hvis vi snakker om å lage et grensesnitt med minst antall metoder som tar sikte på å utføre grensesnittets eneste jobb (som jeg synes er veldig lik enkeltansvarsprinsippet), er det bedre å lage et par mindre i stedet. av ett oppblåst grensesnitt. Heldigvis kan en klasse implementere mer enn ett grensesnitt. Husk å navngi grensesnittene dine riktig: navnet skal gjenspeile den tildelte oppgaven så nøyaktig som mulig. Og selvfølgelig, jo kortere den er, jo mindre forvirring vil den forårsake. Dokumentasjonskommentarer skrives vanligvis på grensesnittnivå. Disse kommentarene gir detaljer om hva hver metode bør gjøre, hvilke argumenter den krever, og hva den vil returnere.

Klasse

Kodingsregler: Fra å lage et system til å jobbe med objekter - 3

Bildekilde

La oss ta en titt på hvordan timene arrangeres internt. Eller rettere sagt, noen perspektiver og regler som bør følges når man skriver undervisning. Som regel bør en klasse starte med en liste over variabler i en bestemt rekkefølge:
  1. offentlige statiske konstanter;
  2. private statiske konstanter;
  3. private instansvariabler.
Deretter kommer de ulike konstruktørene, i rekkefølge fra de med færrest argumenter til de med flest. De følges av metoder fra de mest offentlige til de mest private. Generelt sett ligger private metoder som skjuler implementeringen av en eller annen funksjonalitet vi ønsker å begrense, helt nederst.

Klassestørrelse

Nå vil jeg gjerne snakke om størrelsen på klassene. La oss minne om ett av de SOLID-prinsippene – enkeltansvarsprinsippet. Den sier at hvert objekt bare har ett formål (ansvar), og logikken i alle metodene har som mål å oppnå det. Dette forteller oss å unngå store, oppsvulmede klasser (som faktisk er gudsobjektets anti-mønster), og hvis vi har mange metoder med all slags forskjellig logikk stappet inn i en klasse, må vi tenke på å dele den opp i en klasse. par logiske deler (klasser). Dette vil igjen øke lesbarheten til koden, siden det ikke vil ta lang tid å forstå formålet med hver metode hvis vi vet det omtrentlige formålet med en gitt klasse. Hold også øye med klassenavnet, som skal gjenspeile logikken den inneholder. For eksempel, hvis vi har en klasse med 20+ ord i navnet, vi må tenke på refaktorisering. Enhver klasse med respekt for seg selv bør ikke ha så mange interne variabler. Faktisk fungerer hver metode med en eller noen få av dem, noe som forårsaker mye samhørighet i klassen (noe som er akkurat som det skal være, siden klassen skal være en enhetlig helhet). Som et resultat fører økning av en klasses samhold til en reduksjon i klassestørrelse, og selvfølgelig øker antallet klasser. Dette er irriterende for noen mennesker, siden du trenger å lese mer rundt klassefiler for å se hvordan en spesifikk stor oppgave fungerer. På toppen av det hele er hver klasse en liten modul som skal være minimalt relatert til andre. Denne isolasjonen reduserer antallet endringer vi må gjøre når vi legger til ekstra logikk til en klasse. hver metode fungerer med en eller noen få av dem, og forårsaker mye samhørighet i klassen (noe som er akkurat slik den skal være, siden klassen skal være en enhetlig helhet). Som et resultat fører økning av en klasses samhold til en reduksjon i klassestørrelse, og selvfølgelig øker antallet klasser. Dette er irriterende for noen mennesker, siden du trenger å lese mer rundt klassefiler for å se hvordan en spesifikk stor oppgave fungerer. På toppen av det hele er hver klasse en liten modul som skal være minimalt relatert til andre. Denne isolasjonen reduserer antallet endringer vi må gjøre når vi legger til ekstra logikk til en klasse. hver metode fungerer med en eller noen få av dem, og forårsaker mye samhørighet i klassen (noe som er akkurat slik den skal være, siden klassen skal være en enhetlig helhet). Som et resultat fører økning av en klasses samhold til en reduksjon i klassestørrelse, og selvfølgelig øker antallet klasser. Dette er irriterende for noen mennesker, siden du trenger å lese mer rundt klassefiler for å se hvordan en spesifikk stor oppgave fungerer. På toppen av det hele er hver klasse en liten modul som skal være minimalt relatert til andre. Denne isolasjonen reduserer antallet endringer vi må gjøre når vi legger til ekstra logikk til en klasse. s samhold fører til en reduksjon i klassestørrelse, og selvfølgelig øker antallet klasser. Dette er irriterende for noen mennesker, siden du trenger å lese mer rundt klassefiler for å se hvordan en spesifikk stor oppgave fungerer. På toppen av det hele er hver klasse en liten modul som skal være minimalt relatert til andre. Denne isolasjonen reduserer antallet endringer vi må gjøre når vi legger til ekstra logikk til en klasse. s samhold fører til en reduksjon i klassestørrelse, og selvfølgelig øker antallet klasser. Dette er irriterende for noen mennesker, siden du trenger å lese mer rundt klassefiler for å se hvordan en spesifikk stor oppgave fungerer. På toppen av det hele er hver klasse en liten modul som skal være minimalt relatert til andre. Denne isolasjonen reduserer antallet endringer vi må gjøre når vi legger til ekstra logikk til en klasse.

Objekter

Innkapsling

Her skal vi først snakke om et OOP-prinsipp: innkapsling. Å skjule implementeringen betyr ikke å lage en metode for å isolere variabler (tankeløst begrense tilgangen gjennom individuelle metoder, gettere og settere, noe som ikke er bra, siden hele poenget med innkapsling går tapt). Å skjule tilgang er rettet mot å danne abstraksjoner, det vil si at klassen gir delte konkrete metoder som vi bruker for å jobbe med dataene våre. Og brukeren trenger ikke å vite nøyaktig hvordan vi jobber med disse dataene – det fungerer og det er nok.

Demeters lov

Vi kan også vurdere Demeterloven: det er et lite sett med regler som hjelper til med å håndtere kompleksitet på klasse- og metodenivå. Anta at vi har et bilobjekt , og det har en move(Object arg1, Object arg2) metode. I følge Law of Demeter er denne metoden begrenset til å ringe:
  • metoder for selve bilobjektet (med andre ord "dette" objektet);
  • metoder for objekter opprettet i flyttemetoden ;
  • metoder for objekter sendt som argumenter ( arg1 , arg2 );
  • metoder for interne bilobjekter (igjen, "dette").
Med andre ord er Demeterloven noe sånt som det foreldre kan si til et barn: "du kan snakke med vennene dine, men ikke med fremmede".

Data struktur

En datastruktur er en samling av relaterte elementer. Når man vurderer et objekt som en datastruktur, er det et sett med dataelementer som metodene opererer på. Eksistensen av disse metodene antas implisitt. Det vil si at en datastruktur er et objekt hvis formål er å lagre og arbeide med (behandle) de lagrede dataene. Den viktigste forskjellen fra et vanlig objekt er at et vanlig objekt er en samling metoder som opererer på dataelementer som implisitt antas å eksistere. Forstår du? Hovedaspektet ved et vanlig objekt er metoder. Interne variabler letter deres korrekte drift. Men i en datastruktur er metodene der for å støtte arbeidet ditt med de lagrede dataelementene, som er avgjørende her. En type datastruktur er et dataoverføringsobjekt (DTO). Dette er en klasse med offentlige variabler og ingen metoder (eller kun metoder for lesing/skriving) som brukes til å overføre data når man arbeider med databaser, analyserer meldinger fra sockets osv. Data lagres vanligvis ikke i slike objekter over en lengre periode. Den konverteres nesten umiddelbart til den typen enhet som applikasjonen vår fungerer. En enhet er på sin side også en datastruktur, men dens formål er å delta i forretningslogikk på ulike nivåer av applikasjonen. Formålet med en DTO er å transportere data til/fra applikasjonen. Eksempel på en DTO: er også en datastruktur, men dens formål er å delta i forretningslogikk på ulike nivåer av applikasjonen. Formålet med en DTO er å transportere data til/fra applikasjonen. Eksempel på en DTO: er også en datastruktur, men dens formål er å delta i forretningslogikk på ulike nivåer av applikasjonen. Formålet med en DTO er å transportere data til/fra applikasjonen. Eksempel på en DTO:

@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Alt virker klart nok, men her lærer vi om eksistensen av hybrider. Hybrider er objekter som har metoder for å håndtere viktig logikk, lagre interne elementer, og som også inkluderer tilgangsmetode (get/set). Slike gjenstander er rotete og gjør det vanskelig å legge til nye metoder. Du bør unngå dem, fordi det ikke er klart hva de er for - lagre elementer eller utføre logikk?

Prinsipper for å lage variabler

La oss gruble litt over variabler. Mer spesifikt, la oss tenke på hvilke prinsipper som gjelder når du lager dem:
  1. Ideelt sett bør du deklarere og initialisere en variabel rett før du bruker den (ikke lag en og glem den).
  2. Når det er mulig, erklær variabler som endelige for å forhindre at verdien endres etter initialisering.
  3. Ikke glem tellervariablene, som vi vanligvis bruker i en slags for- løkke. Det vil si, ikke glem å nullstille dem. Ellers kan all vår logikk gå i stykker.
  4. Du bør prøve å initialisere variabler i konstruktøren.
  5. Hvis det er et valg mellom å bruke et objekt med en referanse eller uten ( new SomeObject() ), velg uten, siden etter at objektet er brukt vil det bli slettet i løpet av neste søppelinnsamlingssyklus og ressursene vil ikke bli bortkastet.
  6. Hold en variabels levetid (avstanden mellom opprettelsen av variabelen og siste gang den refereres) så kort som mulig.
  7. Initialiser variabler som brukes i en løkke rett før løkken, ikke i begynnelsen av metoden som inneholder løkken.
  8. Start alltid med det mest begrensede omfanget og utvid bare når det er nødvendig (du bør prøve å lage en variabel så lokal som mulig).
  9. Bruk hver variabel kun til ett formål.
  10. Unngå variabler med et skjult formål, f.eks. en variabel delt mellom to oppgaver - dette betyr at typen ikke er egnet for å løse en av dem.

Metoder

Kodingsregler: Fra å lage et system til å jobbe med objekter - 4

fra filmen "Star Wars: Episode III - Revenge of the Sith" (2005)

La oss gå direkte til implementeringen av vår logikk, dvs. til metoder.
  1. Regel #1 — Kompakthet. Ideelt sett bør en metode ikke overstige 20 linjer. Dette betyr at hvis en offentlig metode "svulmer" betydelig, må du tenke på å bryte logikken fra hverandre og flytte den inn i separate private metoder.

  2. Regel #2 — if , else , while og andre utsagn skal ikke ha tungt nestede blokker: massevis av nesting reduserer kodens lesbarhet betydelig. Ideelt sett bør du ikke ha mer enn to nestede {} -blokker.

    Og det er også ønskelig å holde koden i disse blokkene kompakt og enkel.

  3. Regel #3 — En metode skal bare utføre én operasjon. Det vil si at hvis en metode utfører all slags kompleks logikk, deler vi den inn i undermetoder. Som et resultat vil selve metoden være en fasade hvis formål er å kalle alle de andre operasjonene i riktig rekkefølge.

    Men hva om operasjonen virker for enkel til å settes inn i en egen metode? Riktignok kan det noen ganger føles som å skyte en kanon mot spurver, men små metoder gir en rekke fordeler:

    • Bedre kodeforståelse;
    • Metoder har en tendens til å bli mer komplekse etter hvert som utviklingen skrider frem. Hvis en metode er enkel til å begynne med, vil det være litt lettere å komplisere funksjonaliteten;
    • Implementeringsdetaljer er skjult;
    • Enklere gjenbruk av kode;
    • Mer pålitelig kode.

  4. Stepdown-regelen - Koden bør leses fra topp til bunn: jo lavere du leser, desto dypere går du inn i logikken. Og omvendt, jo høyere du kommer, jo mer abstrakte er metodene. For eksempel er switch-setninger ganske lite kompakte og uønskede, men hvis du ikke kan unngå å bruke en switch, bør du prøve å flytte den så lavt som mulig, til metodene på laveste nivå.

  5. Metodeargumenter – Hva er det ideelle tallet? Ideelt sett ingen i det hele tatt :) Men skjer det virkelig? Når det er sagt, bør du prøve å ha så få argumenter som mulig, for jo færre det er, jo lettere er det å bruke en metode og jo lettere er det å teste den. Når du er i tvil, prøv å forutse alle scenariene for bruk av metoden med et stort antall inndataparametere.

  6. I tillegg ville det være greit å skille metoder som har et boolsk flagg som inngangsparameter, siden dette i seg selv innebærer at metoden utfører mer enn én operasjon (hvis sant, så gjør en ting; hvis usant, så gjør en annen). Som jeg skrev ovenfor, er ikke dette bra og bør unngås om mulig.

  7. Hvis en metode har et stort antall inndataparametere (en ekstrem er 7, men du bør virkelig begynne å tenke etter 2-3), bør noen av argumentene grupperes i et eget objekt.

  8. Hvis det er flere lignende (overbelastede) metoder, må lignende parametere sendes i samme rekkefølge: dette forbedrer lesbarheten og brukervennligheten.

  9. Når du sender parametere til en metode, må du være sikker på at alle brukes, ellers hvorfor trenger du dem? Klipp eventuelle ubrukte parametere ut av grensesnittet og bli ferdig med det.

  10. try/catch ser ikke særlig pent ut i naturen, så det vil være en god idé å flytte den inn i en egen mellommetode (en metode for å håndtere unntak):

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

Jeg snakket om duplikatkode ovenfor, men la meg gjenta igjen: Hvis vi har et par metoder med gjentatt kode, må vi flytte den til en egen metode. Dette vil gjøre både metoden og klassen mer kompakt. Ikke glem reglene som styrer navn: detaljer om hvordan du navngir klasser, grensesnitt, metoder og variabler riktig, vil bli diskutert i neste del av artikkelen. Men det er alt jeg har til deg i dag.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION