Det er ikke så vanskelig å skrive en fin, veldesignet og pen kode som det ser ut til å være. Det krever litt innsats for å bli kjent med hovedreglene og bare bruke dem i koden din. Og det trenger ikke å være som alt på en gang, men når du føler deg komfortabel med en ting, prøv å tenke på en annen, og så videre.
Bygg solid, ikke dum kode
La oss starte med å introdusere de mest elementære reglene som kalles SOLID. Det er et begrep som beskriver en samling designprinsipper for god kode som ble oppfunnet av Robert C. Martin, og hvordan det går:
S står for Single Responsibility-prinsippet
Den sier at en klasse bør ha én og bare én grunn til å endre seg. Med andre ord bør en klasse bare ha én jobb å gjøre, så vi bør unngå å skrive store klasser med mange ansvarsområder. Hvis klassen vår må gjøre mange ting, bør hver av dem delegeres til separate klasser.
klassen Notification
def initialize(params)
@params = params
end
def call
EmailSender.new(melding).call
end
privat
def melding
# noe implementering
slutt
slutt
O betyr åpent/lukket prinsipp
Den sier at du skal kunne utvide en klasses oppførsel uten å modifisere den. Det skal med andre ord være enkelt å utvide en klasse uten å gjøre noen endringer i den. Dette kan vi oppnå ved å bruke f.eks. strategimønster eller dekoratorer.
klassen Notification
def initialize(params, avsender)
@params = params
@sender = avsender
end
def call
sender.call melding
end
privat
def melding
# noe implementering
slutt
slutt
klasse EmailSender
def call(melding)
# noe implementering
end
end
klasse SmsSender
def call(melding)
# noe implementering
end
end
Med dette designet er det mulig å legge til nye avsendere uten å endre koden.
L betyr Liskovs substitusjonsprinsipp
Den sier at avledede klasser må kunne erstattes av sine basisklasser. Med andre ord skal det være enkelt å erstatte klasser som kommer fra samme stamfar, med andre etterkommere.
klasse Logger {
info (melding) {
console.info(this._prefixFor('info') + melding)
}
error (melding, err) {
console.error(this._prefixFor('error') + melding)
if (err) {
console.error(err)
}
}
_prefixFor (type) {
// noe implementering
}
}
class ScepticLogger utvides til Logger {
info (melding) {
super.info(melding)
console.info(this._prefixFor('info') + 'Og det var alt jeg hadde å si.')
}
error (message, err) {
super.error(message, err)
console.error(this._prefixFor('error') + 'Big deal!')
}
}
Vi kan enkelt bytte ut navnet på klassen, fordi begge har nøyaktig samme brukergrensesnitt.
Jeg mener grensesnittseparasjonsprinsippet
Den sier at du bør lage finkornede grensesnitt som er klientspesifikke. Hva er et grensesnitt? Det er en gitt måte å bruke en del av koden på. Et brudd på denne regelen kan f.eks. være en klasse med for mange metoder eller en metode med for mange argumentalternativer. Et godt eksempel for å visualisere dette prinsippet er et repository-mønster, ikke bare fordi vi ofte legger mange metoder inn i en enkelt klasse, men også fordi disse metodene er utsatt for en risiko for å akseptere for mange argumenter.
# ikke det beste eksempelet denne gangen
klasse NotificationRepository
def find_all_by_ids(ids:, info:)
notifications = Notification.where(id: ids)
info ? notifications.where(type: :info) : notifications
end
end
# og en bedre
klasse NotificationRepository
def find_all_by_ids(ids:)
Notification.where(id: ids)
end
def find_all_by_ids_info(ids:)
find_all_by_ids(ids).where(type: :info)
end
end
D betyr Dependency Inversion-prinsippet
Den sier at man skal være avhengig av abstraksjoner, ikke av konkretioner. Med andre ord, en klasse som bruker en annen, bør ikke være avhengig av implementasjonsdetaljene, alt som er viktig er brukergrensesnittet.
klassen Notification
def initialize(params, avsender)
@params = params
@sender = avsender
end
def call
sender.call melding
end
privat
def melding
# noe implementering
slutt
slutt
Alt vi trenger å vite om avsenderobjektet, er at det har en "call"-metode som forventer meldingen som argument.
Ikke den beste koden noensinne
Det er også veldig viktig å vite hvilke ting som bør unngås når man skriver kode, så her kommer en ny samling med DUMME prinsipper som gjør at koden ikke er vedlikeholdbar, vanskelig å teste og gjenbruke.
S betyr Singleton
Singletons anses ofte som et anti-mønster og bør generelt unngås. Men hovedproblemet med dette mønsteret er at det er en slags unnskyldning for globale variabler/metoder, og det kan fort bli overbrukt av utviklere.
T betyr Tight Coupling (tett kobling)
Den bevares når en endring i én modul også krever endringer i andre deler av applikasjonen.
U betyr uprøvbarhet
Hvis koden din er god, bør det å skrive tester høres gøy ut, ikke som et mareritt.
P betyr prematur optimalisering
Ordet "for tidlig" er nøkkelen her, hvis du ikke trenger det nå, er det bortkastet tid. Det er bedre å fokusere på en god, ren kode enn på noen mikrooptimaliseringer - som generelt fører til mer kompleks kode.
Jeg mener Indescriptive Naming
Det er det vanskeligste med å skrive god kode, men husk at det ikke bare er for resten av team men også for fremtidige deg, så behandle deg riktig 🙂 Det er bedre å skrive et langt navn for en metode, men det sier alt, enn kort og gåtefullt.
D betyr duplisering
Hovedårsaken til duplisering i kode er å følge prinsippet om tett kobling. Hvis koden din er tett koblet, kan du ikke gjenbruke den, og duplisert kode vises, så følg DRY og ikke gjenta deg selv.
Det er ikke så viktig akkurat nå
Jeg vil også nevne to veldig viktige ting som ofte blir utelatt. Den første har du sikkert hørt om - det er YAGNI, som betyr: du kommer ikke til å trenge det. Fra tid til annen observerer jeg dette problemet når jeg gjør kodegjennomgang eller til og med skriver min egen kode, men vi bør endre tankegangen vår når vi implementerer en funksjon. Vi bør skrive akkurat den koden vi trenger akkurat nå, ikke mer eller mindre. Vi bør ha i bakhodet at alt endrer seg veldig raskt (spesielt applikasjonskrav), så det er ikke noe poeng å tro at noe en dag vil komme til nytte. Ikke kast bort tiden din.
Jeg skjønner ikke
Og det siste, som kanskje ikke er helt opplagt, og som kan være ganske kontroversielt for noen, er en beskrivende kode. Med det mener jeg ikke bare å bruke de riktige navnene på klasser, variabler eller metoder. Det er veldig, veldig bra når hele koden er lesbar fra første øyekast. Hva er hensikten med en veldig kort kode som er så gåtefull som den kan bli, og som ingen vet hva den gjør, bortsett fra den som har skrevet den? Etter min mening er det bedre å skrive noen tegntilstandserklæringernoe annet mer enn ett ord og så i går sitte og lure: vent hva er resultatet, hvordan det skjedde, og så videre.
const params = [
{
filmer: [
{ tittel: 'The Shawshank Redemption' },
{ tittel: 'En fløy over gjøkeredet' }
]
},
{
filmer: [
{ tittel: 'Saving Private Ryan' },
{ tittel: "Pulp Fiction" },
{ tittel: "The Shawshank Redemption" },
]
}
]
// første forslag
function uniqueMovieTitlesFrom (params) {
const titles = params
.map(param => param.movies)
.reduce((prev, nex) => prev.concat(next))
.map(movie => movie.title)
return [...new Set(titles)]
}
// andre forslag
function uniqueMovieTitlesFrom (params) {
const titles = {}
params.forEach(param => {
param.movies.forEach(movie => titles[movie.title] = true)
})
return Object.keys(titles)
}
For å oppsummere
Som du ser, er det mange regler å huske på, men som jeg nevnte innledningsvis, er det et spørsmål om tid å skrive god kode. Hvis du begynner å tenke på en forbedring av kodevanene dine, vil du se at en annen god regel vil følge, fordi alle gode ting oppstår av seg selv, akkurat som de dårlige.
Les mer om dette:
Hva er Ruby on Jets, og hvordan bygger man en app ved hjelp av det?
Vuelkalender. Et nytt Codest-prosjekt basert på Vue.js
Codests ukentlige rapport med de beste teknologiartiklene. Bygge programvare for 50 millioner samtidige stikkontakter (10)