Nysnø: Jeg tror ikke prosjektet ditt følger DRY-dogme
«Every piece of knowledge must have a single, unambiguous, authoritative representation within a system»
Dette er definisjonen av programmeringsdogmet DRY. DRY står for “Don’t Repeat Yourself”, som mange tolker som “ikke bruk copy-paste i koden din”. Dette er riktig, men en triviell og liten del av DRY-konseptet. Det er dybden i det som har vært en tankevekker for meg. Syretesten man kan stille seg er: hvis du skal endre en bestemt funksjon, logikk eller en ressurs, fører det til endringer flere steder og i flere formater? Må du endre annen kode, dokumentasjon, databaseskjema og strukturen som tjener den? I så fall er ikke prosjektet DRY.
Duplisering i kode
Trivielt, men duplisering er fortsatt veldig vanlig. Her er et enkelt eksempel:
function printBalance(account) {console.log(`Debits: ${account.debits.toFixed(2).padStart(10, ' ')}\n`);console.log(`Credits: ${account.credits.toFixed(2).padStart(10, ' ')}\n`);if (account.fees < 0) {console.log(`Fees: ${(-account.fees).toFixed(2).padStart(10, ' ')}-\n`);} else {console.log(`Fees: ${account.credits.toFixed(2).padStart(10, ' ')}\n`);}console.log(' ----\n');if (account.balance < 0) {console.log(`Balance: ${(-account.balance).toFixed(2).padStart(10, ' ')}-\n`);} else {console.log(`Balance: ${account.balance.toFixed(2).padStart(10, ' ')}\n`);}}
Mulig det fins flere DRY-forbedringer, men jeg ser umiddelbart tre:
- Håndteringen av negative tall
- console.log-statements
- Håndtering av spaces
Alle disse representerer ulike logiske steg, som kan ønskes å endres på. Hva om man skal sende til et API istedenfor for å console logge, hva om man ønsker færre whitespaces i loggingen osv. Dette bør kunne endres ett sted, ikke hver eneste linje.
En løsning vil kunne se sånn ut:
function formatAmount(value) {const result = value.toFixed(2).padStart(10, ' ');return value < 0 ? result + '-' : result + ' ';}function printLine(label, value) {console.log(`${label.padEnd(9, ' ')}${value}`);}function reportLine(label, amount) {printLine(label + ":", formatAmount(amount));}function printBalance(account) {reportLine("Debits", account.debits);reportLine("Credits", account.credits);reportLine("Fees", account.fees);printLine("", "----");reportLine("Balance", account.balance);}
En implementasjon for en regel kan være lik som for en annen. Dette betyr ikke at det er riktig å gjøre det om til èn funksjon.
For eksempel:
function validate_age(value):validate_type(value, :integer)validate_min_integer(value, 0)function validate_quantity(value):validate_type(value, :integer)validate_min_integer(value, 0)
Her er det fristende å slå dem sammen siden implementasjonen er helt lik. Funksjonene står dog for ulik kunnskap, og bør derfor ikke slås sammen. Senere kan det være at minimumskvantum endrer seg til noe annet enn alderen.
Duplisering av dokumentasjon
Kodedokumentasjon vet vi å unngå, men vi kan fortsatt komme over det fra andre utviklere
// Calculate the fees for this account.//// * Each returned check costs $20// * If the account is in overdraft for more than 3 days,// charge $10 for each day// * If the average account balance is greater than $2,000// reduce the fees by 50%function fees(a) {let f = 0;if (a.returned_check_count > 0) {f += 20 * a.returned_check_count;}if (a.overdraft_days > 3) {f += 10 * a.overdraft_days;}if (a.average_balance > 2000) {f /= 2;}return f;}
Gjør heller funksjonsnavnet selvforklarende og dropp dokumentasjonen (dupliseringen)
function calculateAccountFees(account) {let fees = 20 * account.returned_check_count;if (account.overdraft_days > 3) {fees += 10 * account.overdraft_days;}if (account.average_balance > 2000) {fees /= 2;}return fees;}
Duplisering av data
I motsetning til eksempelet over, er det nok flere som er offer for duplisering av data. Dette blir sikkert Anine glad for at vi blir bedre på.En typisk klasse kunne vært
class Line {Point start;Point end;double length;}
Som ser helt rimelig ut. Problemet er at length er angitt av start og end, og er en duplikat i informasjonsflyten. Så kunne man i stedet regnet ut length selv, noe sånt som (tilgi forenklet implementasjon her, ville blitt håndtert konstruktør, privat funksjon e.l.)
class Line {Point start;Point end;double length() { return calculateDistance(start, end); }}
Representasjons-duplikasjon
En veldig vanlig duplikasjon er at man har en interface på frontend-siden og et API på andre siden. Det én endring i den ene siden gjør at det kreves en endring på den andre siden, gjør at det bryter med DRY. Det er for mange prosjekter uungåelig, men her kommer noen tips for å minimere problemet:
- Interne APIer: Se etter verktøy som kan hjelpe deg spesifisere APIet i et nøytralt format (Swagger mfl.). Det kan være til generering av dokumentasjon, mock api, funksjonelle tester og API klienter.
- Eksterne APIer: Oftere og oftere, så er eksterne APIer kompatible/bruker OpenAPI. Det er en tjeneste som lar deg importere API-spesifikasjonene inn i ditt lokale API-verktøy og integrere med sin egen tjeneste på en god måte.
- Ulike datakilder: èn kan bruke verktøy for å (Entity Framework mfl.) definere strukturen i kode, som kan reverseres og brukes til å generere database-strukturen og koden du trenger, ut fra din modell.
Utvikler-duplikasjon
Kanskje den vanskeligste å håndtere. Her handler det om å ha god kultur. Et eksempel som illustrere problemet er at det i et statlig program (tenk Digdir) i USA var under audit, og det ble oppdaget mer enn 10.000 programmer som brukte forskjellige måter å validere personnummeret på.
Løsningen her er å lage gode team som klarer å utnytte hverandres — og systemets tidligere utvikleres — styrker, samt lage gode rigger for gjenbruk og kunnskapsdeling. Alt fra komponentsbibliotek til felles funksjonsbibliotek er gode ting å innføre hvis man ikke allerede har det i prosjektet sitt.
Oppsummert
Jeg håper dere kan ta med dere at DRY handler om mye mer enn å unngå copy-paste. Det er et type mindset som kan benyttes til langt mer og er en god måte å formidle nødvendige kodekvalitetsoppgraderinger til andre utviklere og folk i ledelsen, på en måte de kan forstå.
PS: Konseptene og kodeeksemplene er hentet fra boken «The Pragmatic Programmer». Det er en veldig god bok jeg kan anbefale alle utviklere å lese gjennom. Den går gjennom mye både konkrete tips til utvikling nå — og hvilket mindset / øvelser du bør gjøre for å bli en bedre utvikler i fremtiden.