CodeGym /Java blog /Tilfældig /Kodningsregler: Fra oprettelse af et system til arbejde m...
John Squirrels
Niveau
San Francisco

Kodningsregler: Fra oprettelse af et system til arbejde med objekter

Udgivet i gruppen
God dag, alle sammen! I dag vil vi gerne tale med dig om at skrive god kode. Selvfølgelig er det ikke alle, der ønsker at tygge på bøger som Clean Code med det samme, da de indeholder rigelige mængder information, men ikke meget er klart i starten. Og når du er færdig med at læse, kan du dræbe alt dit ønske om at kode. I betragtning af alt dette vil jeg i dag give dig en lille guide (et lille sæt anbefalinger) til at skrive bedre kode. Lad os i denne artikel gennemgå de grundlæggende regler og begreber relateret til at skabe et system og arbejde med grænseflader, klasser og objekter. At læse denne artikel vil ikke tage meget tid, og jeg håber ikke, det vil kede dig. Jeg vil arbejde mig fra toppen til bunden, dvs. fra den generelle struktur af en applikation til dens smallere detaljer. Kodningsregler: Fra oprettelse af et system til arbejde med objekter - 1

Systemer

Følgende er generelt ønskelige egenskaber ved et system:
  • Minimal kompleksitet. Alt for komplicerede projekter skal undgås. Det vigtigste er enkelhed og klarhed (simpelere = bedre).
  • Nem vedligeholdelse. Når du opretter en applikation, skal du huske, at den skal vedligeholdes (selvom du personligt ikke er ansvarlig for at vedligeholde den). Det betyder, at koden skal være klar og tydelig.
  • Løs kobling. Det betyder, at vi minimerer antallet af afhængigheder mellem forskellige dele af programmet (maksimerer vores overholdelse af OOP-principperne).
  • Genanvendelighed. Vi designer vores system med mulighed for at genbruge komponenter i andre applikationer.
  • Bærbarhed. Det skal være nemt at tilpasse et system til et andet miljø.
  • Ensartet stil. Vi designer vores system efter en ensartet stil i dets forskellige komponenter.
  • Udvidbarhed (skalerbarhed). Vi kan forbedre systemet uden at krænke dets grundlæggende struktur (tilføjelse eller ændring af en komponent bør ikke påvirke alle de andre).
Det er praktisk talt umuligt at bygge en applikation, der ikke kræver ændringer eller ny funktionalitet. Vi bliver hele tiden nødt til at tilføje nye dele for at hjælpe vores idérige med at følge med tiden. Det er her skalerbarhed kommer i spil. Skalerbarhed er i bund og grund at udvide applikationen, tilføje ny funktionalitet og arbejde med flere ressourcer (eller, med andre ord, med en større belastning). Med andre ord, for at gøre det nemmere at tilføje ny logik, holder vi os til nogle regler, såsom at reducere systemets kobling ved at øge modulariteten.Kodningsregler: Fra at skabe et system til at arbejde med objekter - 2

Billedkilde

Stadier af design af et system

  1. Software system. Design applikationen overordnet.
  2. Opdeling i delsystemer/pakker. Definer logisk adskilte dele og definer reglerne for interaktion mellem dem.
  3. Inddeling af delsystemer i klasser. Opdel dele af systemet i specifikke klasser og grænseflader, og definer interaktionen mellem dem.
  4. Inddeling af klasser i metoder. Opret en komplet definition af de nødvendige metoder for en klasse, baseret på dens tildelte ansvar.
  5. Metode design. Opret en detaljeret definition af funktionaliteten af ​​individuelle metoder.
Normalt håndterer almindelige udviklere dette design, mens applikationens arkitekt håndterer de ovenfor beskrevne punkter.

Generelle principper og koncepter for systemdesign

Doven initialisering. I dette programmeringsformsprog spilder applikationen ikke tid på at skabe et objekt, før det rent faktisk er brugt. Dette fremskynder initialiseringsprocessen og reducerer belastningen på affaldssamleren. Når det er sagt, skal du ikke gå for langt, for det kan være i strid med modularitetsprincippet. Måske er det værd at flytte alle forekomster af konstruktion til en bestemt del, for eksempel hovedmetoden eller til en fabriksklasse . Et kendetegn ved god kode er fraværet af gentagne koder. Som regel placeres en sådan kode i en separat klasse, så den kan kaldes, når det er nødvendigt.

AOP

Jeg vil også gerne bemærke aspektorienteret programmering. Dette programmeringsparadigme handler om at introducere gennemsigtig logik. Det vil sige, at gentagen kode lægges ind i klasser (aspekter) og kaldes, når visse betingelser er opfyldt. For eksempel når du kalder en metode med et bestemt navn eller får adgang til en variabel af en bestemt type. Nogle gange kan aspekter være forvirrende, da det ikke umiddelbart er klart, hvor koden kaldes fra, men dette er stadig meget nyttig funktionalitet. Især ved caching eller logning. Vi tilføjer denne funktionalitet uden at tilføje yderligere logik til almindelige klasser. Kent Becks fire regler for en simpel arkitektur:
  1. Udtryksevne — Hensigten med en klasse skal udtrykkes klart. Dette opnås gennem korrekt navngivning, lille størrelse og overholdelse af enkeltansvarsprincippet (som vi vil overveje mere detaljeret nedenfor).
  2. Minimum antal klasser og metoder — I dit ønske om at gøre klasserne så små og snævert fokuserede som muligt, kan du gå for langt (hvilket resulterer i et haglgeværkirurgi-anti-mønster). Dette princip kræver, at systemet holdes kompakt og ikke går for langt, at der skabes en separat klasse for enhver mulig handling.
  3. Ingen duplikering — Dubleret kode, som skaber forvirring og er en indikation af suboptimalt systemdesign, udtrækkes og flyttes til en separat placering.
  4. Kører alle testene — Et system, der består alle testene, er overskueligt. Enhver ændring kan få en test til at mislykkes, hvilket afslører for os, at vores ændring i en metodes interne logik også ændrede systemets adfærd på uventede måder.

SOLID

Når man designer et system, er de velkendte SOLID principper værd at overveje:

S (enkelt ansvar), O (åben-lukket), L (Liskov substitution), I (grænsefladesegregation), D (afhængighedsinversion).

Vi vil ikke dvæle ved hvert enkelt princip. Det ville være lidt uden for denne artikels rammer, men du kan læse mere her .

Interface

Måske er et af de vigtigste trin i at skabe en veldesignet klasse at skabe en veldesignet grænseflade, der repræsenterer en god abstraktion, skjule klassens implementeringsdetaljer og samtidig præsentere en gruppe af metoder, der klart er i overensstemmelse med hinanden. Lad os se nærmere på et af SOLID-principperne — grænsefladeadskillelse: klienter (klasser) bør ikke implementere unødvendige metoder, som de ikke vil bruge. Med andre ord, hvis vi taler om at skabe en grænseflade med det mindste antal metoder, der sigter mod at udføre grænsefladens eneste job (som jeg synes er meget lig enkeltansvarsprincippet), er det bedre at oprette et par mindre i stedet. af en oppustet grænseflade. Heldigvis kan en klasse implementere mere end én grænseflade. Husk at navngive dine grænseflader korrekt: Navnet skal afspejle den tildelte opgave så præcist som muligt. Og selvfølgelig, jo kortere den er, jo mindre forvirring vil den forårsage. Dokumentationskommentarer skrives normalt på grænsefladeniveau. Disse kommentarer giver detaljer om, hvad hver metode skal gøre, hvilke argumenter den kræver, og hvad den vil returnere.

klasse

Kodningsregler: Fra at skabe et system til at arbejde med objekter - 3

Billedkilde

Lad os tage et kig på, hvordan undervisningen er arrangeret internt. Eller rettere, nogle perspektiver og regler, som man bør følge, når man skriver undervisning. Som regel skal en klasse starte med en liste over variabler i en bestemt rækkefølge:
  1. offentlige statiske konstanter;
  2. private statiske konstanter;
  3. private instansvariabler.
Dernæst kommer de forskellige konstruktører, i rækkefølge fra dem med færrest argumenter til dem med flest. De efterfølges af metoder fra de mest offentlige til de mest private. Generelt er private metoder, der skjuler implementeringen af ​​en eller anden funktionalitet, vi ønsker at begrænse, helt nederst.

Klassestørrelse

Nu vil jeg gerne tale om klassernes størrelse. Lad os huske et af de SOLIDE principper - princippet om enkelt ansvar. Den siger, at hvert objekt kun har ét formål (ansvar), og logikken i alle dets metoder sigter mod at opnå det. Dette fortæller os, at vi skal undgå store, oppustede klasser (som faktisk er gudsobjektets anti-mønster), og hvis vi har en masse metoder med alverdens forskellig logik proppet ind i en klasse, er vi nødt til at tænke på at dele den op i en klasse. par logiske dele (klasser). Dette vil igen øge kodens læsbarhed, da det ikke vil tage lang tid at forstå formålet med hver metode, hvis vi kender det omtrentlige formål med en given klasse. Hold også øje med klassenavnet, som skal afspejle den logik, det indeholder. For eksempel, hvis vi har en klasse med mere end 20 ord i navnet, vi skal tænke på refaktorisering. Enhver klasse med respekt for sig selv bør ikke have så mange interne variabler. Faktisk fungerer hver metode med en eller nogle få af dem, hvilket forårsager en masse sammenhæng i klassen (hvilket er præcis, som det skal være, da klassen skal være en samlet helhed). Som følge heraf fører en øget klasses sammenhængskraft til en reduktion i klassestørrelsen, og selvfølgelig stiger antallet af klasser. Dette er irriterende for nogle mennesker, da du er nødt til at gennemse klassefiler mere for at se, hvordan en specifik stor opgave fungerer. Oven i det hele er hver klasse et lille modul, der skal være minimalt relateret til andre. Denne isolation reducerer antallet af ændringer, vi skal foretage, når vi tilføjer yderligere logik til en klasse. hver metode arbejder med en eller nogle få af dem, hvilket forårsager en masse sammenhæng i klassen (hvilket er præcis, som det skal være, da klassen skal være en samlet helhed). Som følge heraf fører en øget klasses sammenhængskraft til en reduktion i klassestørrelsen, og selvfølgelig stiger antallet af klasser. Dette er irriterende for nogle mennesker, da du er nødt til at gennemse klassefiler mere for at se, hvordan en specifik stor opgave fungerer. Oven i det hele er hver klasse et lille modul, der skal være minimalt relateret til andre. Denne isolation reducerer antallet af ændringer, vi skal foretage, når vi tilføjer yderligere logik til en klasse. hver metode arbejder med en eller nogle få af dem, hvilket forårsager en masse sammenhæng i klassen (hvilket er præcis, som det skal være, da klassen skal være en samlet helhed). Som følge heraf fører en øget klasses sammenhængskraft til en reduktion i klassestørrelsen, og selvfølgelig stiger antallet af klasser. Dette er irriterende for nogle mennesker, da du er nødt til at gennemse klassefiler mere for at se, hvordan en specifik stor opgave fungerer. Oven i det hele er hver klasse et lille modul, der skal være minimalt relateret til andre. Denne isolation reducerer antallet af ændringer, vi skal foretage, når vi tilføjer yderligere logik til en klasse. s sammenhængskraft fører til en reduktion i klassestørrelsen, og selvfølgelig stiger antallet af klasser. Dette er irriterende for nogle mennesker, da du er nødt til at gennemse klassefiler mere for at se, hvordan en specifik stor opgave fungerer. Oven i det hele er hver klasse et lille modul, der skal være minimalt relateret til andre. Denne isolation reducerer antallet af ændringer, vi skal foretage, når vi tilføjer yderligere logik til en klasse. s sammenhængskraft fører til en reduktion i klassestørrelsen, og selvfølgelig stiger antallet af klasser. Dette er irriterende for nogle mennesker, da du er nødt til at gennemse klassefiler mere for at se, hvordan en specifik stor opgave fungerer. Oven i det hele er hver klasse et lille modul, der skal være minimalt relateret til andre. Denne isolation reducerer antallet af ændringer, vi skal foretage, når vi tilføjer yderligere logik til en klasse.

Objekter

Indkapsling

Her vil vi først tale om et OOP-princip: indkapsling. At skjule implementeringen er ikke ensbetydende med at skabe en metode til at isolere variable (tænkeløst begrænse adgangen gennem individuelle metoder, gettere og sættere, hvilket ikke er godt, da hele pointen med indkapsling er tabt). At skjule adgang har til formål at danne abstraktioner, det vil sige, at klassen giver fælles konkrete metoder, som vi bruger til at arbejde med vores data. Og brugeren behøver ikke at vide præcis, hvordan vi arbejder med disse data – det virker, og det er nok.

Demeters lov

Vi kan også overveje Demeterloven: det er et lille sæt regler, der hjælper med at håndtere kompleksitet på klasse- og metodeniveau. Antag, at vi har et bilobjekt , og det har en move(Object arg1, Object arg2) metode. Ifølge Demeterloven er denne metode begrænset til at kalde:
  • metoder til selve bilobjektet (med andre ord "dette" objektet);
  • metoder til objekter, der er oprettet inden for flytningsmetoden ;
  • metoder for objekter, der sendes som argumenter ( arg1 , arg2 );
  • metoder til interne bilobjekter (igen "dette").
Med andre ord er Demeterloven noget i retning af, hvad forældre kan sige til et barn: "du kan tale med dine venner, men ikke med fremmede".

Datastruktur

En datastruktur er en samling af relaterede elementer. Når man betragter et objekt som en datastruktur, er der et sæt dataelementer, som metoderne opererer på. Eksistensen af ​​disse metoder antages implicit. Det vil sige, at en datastruktur er et objekt, hvis formål er at lagre og arbejde med (behandle) de lagrede data. Dens vigtigste forskel fra et almindeligt objekt er, at et almindeligt objekt er en samling af metoder, der opererer på dataelementer, der implicit antages at eksistere. Forstår du? Hovedaspektet ved et almindeligt objekt er metoder. Interne variabler letter deres korrekte drift. Men i en datastruktur er metoderne til for at understøtte dit arbejde med de lagrede dataelementer, som er altafgørende her. En type datastruktur er et dataoverførselsobjekt (DTO). Dette er en klasse med offentlige variabler og ingen metoder (eller kun metoder til læsning/skrivning), der bruges til at overføre data, når man arbejder med databaser, parser beskeder fra sockets osv. Data lagres normalt ikke i sådanne objekter i en længere periode. Det konverteres næsten øjeblikkeligt til den type enhed, som vores applikation fungerer. En enhed er til gengæld også en datastruktur, men dens formål er at deltage i forretningslogik på forskellige niveauer af applikationen. Formålet med en DTO er at transportere data til/fra applikationen. Eksempel på en DTO: er også en datastruktur, men dens formål er at deltage i forretningslogik på forskellige niveauer af applikationen. Formålet med en DTO er at transportere data til/fra applikationen. Eksempel på en DTO: er også en datastruktur, men dens formål er at deltage i forretningslogik på forskellige niveauer af applikationen. Formålet med en DTO er at transportere data til/fra applikationen. 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 af ​​hybrider. Hybrider er objekter, der har metoder til at håndtere vigtig logik, gemme interne elementer og også inkluderer accessor (get/set) metoder. Sådanne genstande er rodede og gør det svært at tilføje nye metoder. Du bør undgå dem, fordi det ikke er klart, hvad de er til - lagring af elementer eller eksekvering af logik?

Principper for at skabe variable

Lad os overveje lidt om variabler. Mere specifikt, lad os tænke over, hvilke principper der gælder, når du opretter dem:
  1. Ideelt set bør du deklarere og initialisere en variabel lige før du bruger den (opret ikke en og glem det).
  2. Når det er muligt, skal du erklære variabler som endelige for at forhindre deres værdi i at ændre sig efter initialisering.
  3. Glem ikke tællervariabler, som vi normalt bruger i en form for for- løkke. Det vil sige, glem ikke at nulstille dem. Ellers kan al vores logik bryde.
  4. Du bør prøve at initialisere variabler i konstruktøren.
  5. Hvis der er et valg mellem at bruge et objekt med en reference eller uden ( new SomeObject() ), skal du vælge uden, da det, efter at objektet er brugt, vil blive slettet under den næste skraldesamlingscyklus, og dets ressourcer vil ikke blive spildt.
  6. Hold en variabels levetid (afstanden mellem oprettelsen af ​​variablen og sidste gang, den refereres) så kort som muligt.
  7. Initialiser variabler, der bruges i en løkke lige før løkken, ikke i begyndelsen af ​​den metode, der indeholder løkken.
  8. Start altid med det mest begrænsede omfang og udvid kun, når det er nødvendigt (du bør prøve at lave en variabel så lokal som muligt).
  9. Brug kun hver variabel til ét formål.
  10. Undgå variabler med et skjult formål, f.eks. en variabel opdelt mellem to opgaver - det betyder, at dens type ikke er egnet til at løse en af ​​dem.

Metoder

Kodningsregler: Fra oprettelse af et system til arbejde med objekter - 4

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

Lad os gå direkte til implementeringen af ​​vores logik, altså til metoder.
  1. Regel #1 — Kompakthed. Ideelt set bør en metode ikke overstige 20 linjer. Det betyder, at hvis en offentlig metode "svulmer" markant, skal du tænke på at bryde logikken fra hinanden og flytte den over i separate private metoder.

  2. Regel #2 — if , else , while og andre udsagn bør ikke have tungt indlejrede blokke: masser af indlejring reducerer kodens læsbarhed markant. Ideelt set bør du ikke have mere end to indlejrede {} blokke.

    Og det er også ønskeligt at holde koden i disse blokke kompakt og enkel.

  3. Regel #3 — En metode bør kun udføre én operation. Det vil sige, at hvis en metode udfører alle mulige former for kompleks logik, opdeler vi den i undermetoder. Som følge heraf vil selve metoden være en facade, hvis formål er at kalde alle de andre operationer i den rigtige rækkefølge.

    Men hvad nu, hvis operationen virker for enkel til at sætte ind i en separat metode? Sandt nok kan det nogle gange føles som at affyre en kanon mod spurve, men små metoder giver en række fordele:

    • Bedre kodeforståelse;
    • Metoder har en tendens til at blive mere komplekse, efterhånden som udviklingen skrider frem. Hvis en metode er enkel til at begynde med, så vil det være lidt lettere at komplicere dens funktionalitet;
    • Implementeringsdetaljer er skjult;
    • Nemmere genbrug af kode;
    • Mere pålidelig kode.

  4. Stepdown-reglen - Koden skal læses fra top til bund: Jo lavere du læser, jo dybere dykker du ned i logikken. Og omvendt, jo højere du kommer, jo mere abstrakte er metoderne. For eksempel er switch-sætninger temmelig ukompakte og uønskede, men hvis du ikke kan undgå at bruge en switch, bør du prøve at flytte den så lavt som muligt til metoderne på det laveste niveau.

  5. Metodeargumenter — Hvad er det ideelle tal? Ideelt set slet ingen :) Men sker det virkelig? Når det er sagt, så bør du forsøge at have så få argumenter som muligt, for jo færre der er, jo lettere er det at bruge en metode, og jo nemmere er det at teste den. Hvis du er i tvivl, så prøv at forudse alle scenarierne for brug af metoden med et stort antal inputparametre.

  6. Derudover ville det være godt at adskille metoder, der har et boolesk flag som inputparameter, da dette i sig selv indebærer, at metoden udfører mere end én operation (hvis sandt, så gør én ting; hvis falsk, så gør en anden). Som jeg skrev ovenfor, er dette ikke godt og bør undgås, hvis det er muligt.

  7. Hvis en metode har et stort antal inputparametre (en ekstrem er 7, men du bør virkelig begynde at tænke efter 2-3), bør nogle af argumenterne grupperes i et separat objekt.

  8. Hvis der er flere lignende (overbelastede) metoder, skal lignende parametre sendes i samme rækkefølge: dette forbedrer læsbarheden og brugervenligheden.

  9. Når du videregiver parametre til en metode, skal du være sikker på, at de alle er brugt, hvorfor skal du ellers bruge dem? Klip eventuelle ubrugte parametre ud af grænsefladen og vær færdig med det.

  10. try/catch ser ikke særlig pæn ud i naturen, så det ville være en god idé at flytte det til en separat mellemmetode (en metode til at håndtere undtagelser):

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

Jeg talte om duplikatkode ovenfor, men lad mig gentage endnu en gang: Hvis vi har et par metoder med gentaget kode, skal vi flytte det til en separat metode. Dette vil gøre både metoden og klassen mere kompakt. Glem ikke de regler, der styrer navne: detaljer om, hvordan man korrekt navngiver klasser, grænseflader, metoder og variabler, vil blive diskuteret i den næste del af artiklen. Men det er alt, hvad jeg har til dig i dag.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION