
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).

Stadier for å designe et system
- Programvaresystem. Design søknaden overordnet.
- Inndeling i delsystemer/pakker. Definer logisk distinkte deler og definer reglene for interaksjon mellom dem.
- Inndeling av delsystemer i klasser. Del deler av systemet inn i spesifikke klasser og grensesnitt, og definer interaksjonen mellom dem.
- Inndeling av klasser i metoder. Lag en fullstendig definisjon av de nødvendige metodene for en klasse, basert på dens tildelte ansvar.
- Metodedesign. Lag en detaljert definisjon av funksjonaliteten til individuelle metoder.
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:- 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).
- 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.
- Ingen duplisering — Duplikatkode, som skaper forvirring og er en indikasjon på suboptimal systemdesign, trekkes ut og flyttes til et eget sted.
- 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

- offentlige statiske konstanter;
- private statiske konstanter;
- private instansvariabler.
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").
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:- Ideelt sett bør du deklarere og initialisere en variabel rett før du bruker den (ikke lag en og glem den).
- Når det er mulig, erklær variabler som endelige for å forhindre at verdien endres etter initialisering.
- 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.
- Du bør prøve å initialisere variabler i konstruktøren.
- 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.
- Hold en variabels levetid (avstanden mellom opprettelsen av variabelen og siste gang den refereres) så kort som mulig.
- Initialiser variabler som brukes i en løkke rett før løkken, ikke i begynnelsen av metoden som inneholder løkken.
- 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).
- Bruk hver variabel kun til ett formål.
- 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

fra filmen "Star Wars: Episode III - Revenge of the Sith" (2005)
-
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.
-
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.
-
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.
-
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å.
-
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.
-
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.
-
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.
-
Hvis det er flere lignende (overbelastede) metoder, må lignende parametere sendes i samme rekkefølge: dette forbedrer lesbarheten og brukervennligheten.
-
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.
- 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(); } }
GO TO FULL VERSION