Tilbake

Luke 12

Syver Storm-FurruSanta hat

Hvor dypt er APIet ditt?

Av Syver Storm-Furru

Jeg tror mange utviklere kjenner godt til følelsen av å lese sin egen seks måneder gamle kode, rive seg i håret over hvor innfløkt, uleselig og dårlig dokumentert den er, banne på at neste gang skal de gjøre det skikkelig, og så seks måneder senere sitte med en kraftig følelse av deja vu. Jeg tror også mange utviklere har selvhjelpsbibler (også kalt bøker om programvaredesign) for å prøve å unnslippe spiralen. Dagens luke er et tips fra min favorittbibel, boken "A Philosophy of Software Design" av John Ousterhout, og er det jeg føler er kanskje det største bidraget til at jeg innimellom bare river meg litt i håret når jeg leser egen kode seks måneder etter at jeg har skrevet den.

John Ousterhout (som er utvikler og Stanford-professor) er som mange andre dyktige utviklere som har skrevet om temaet opptatt av grensesnitt i kode, for å sørge for isolerte bestanddeler som kan endres på uten å påvirke hverandre. Som mange andre, mener han at gode grensesnitt skal skjule uvesentlige detaljer om den underliggende implementasjonen, og jo flere uvesentlige detaljer, jo bedre.

Ousterhout bruker begrepet "bredt" om grensesnitt som eksponerer uvesentlige detaljer, og "grunt" om grensesnitt som ikke skjuler så mange vesentlige detaljer som de burde. Målsetningen når man designer grensesnitt er å sørge for at grensesnittet er så dypt og smalt som mulig og dermed skjuler flest mulig uvesentlige detaljer uten å dekke over de vesentlige detaljene. Dette gjelder uavhengig av om grensesnittet er et bibliotek, et REST-API, eller et objekt.

Siden det er ganske abstrakt, kan jeg friste med et konkret eksempel på et relativt bredt og grunt grensesnitt, med et forslag til hva som kan gjøres for å gjøre det dypere og smalere:

Følgende kodesnutt er et litt forenklet utdrag fra API-dokumentasjonen til et autentiseringsbibliotek utviklet av et ikke navngitt Seattle-basert teknologiselskap med turkisfargede skytjenester. APIet tilbyr disse fire funksjonene for å hente et token.

acquireTokenByCode(request: AuthRequest): Promise<Result>;
acquireTokenPopup(request: AuthRequest): Promise<Result>;
acquireTokenRedirect(request: AuthRequest): Promise<void>;
acquireTokenSilent(silentRequest: SilentRequest): Promise<Result>;

Hvordan bruker man APIet? Her kommer den anbefalte måten:

aquireTokenSilent(request).then((token) => { 
  // Gjør noe spennende med et autentisert token
}).catch((error) => {
  if (error instanceof UnauthenticatedError) {
    aquireTokenPopup(request).then((token) => {
        // Nå er du autentisert via en popup, gjør noe spennende
    })
  }
})

Altså: Først sjekk om du kan hente et nytt token uten at sluttbrukeren må forholde seg til noe (f.eks. et refresh token). Hvis det feiler, henter du et på nytt enten via en redirect, en popup, eller med en kode.

Tre av fire funksjoner henter tokenet ved å be om brukerinput. Bruksguiden for biblioteket nevner bare to av dem (aquireTokenRedirect og aquireTokenPopup), et tegn på at APIet er bredere enn det trenger å være. Om aquireTokenByCode ikke egentlig skal brukes av brukeren av grensesnittet, trenger det heller ikke eksponeres.

I tillegg burde man alltid bruke aquireTokenSilent før man bruker en av de andre funksjonene. Det betyr at aquireTokenSilent egentlig også er uvesentlig (for brukeren av biblioteket) siden det alltid bør skje. Her kunne man gjort APIet dypere og smalere ved å sette det opp på følgende måte:

enum InteractionDialog {
    Popup,
    Redirect,
}

aquireToken(request: Request, interactionDialog: InteractionDialog = "popup"): Promise<Request>;

Her gjennomfører alltid aquireToken kallet til aquireTokenSilent, og har tatt et standpunkt om hvordan autentiseringen skal gjennomføres (f.eks. via en popup som default). Temmelig mye enklere å forholde seg til, og det skjuler de uvesentlige detaljene.

Ousterhout kommer også med et annet eksempel på et dypt og smalt grensesnitt som er utrolig vellykket. I moderne Unix-systemer som MacOS og Linux er det hundretusenvis av linjer med kode som jobber for å håndtere I/O, men grensesnittet som er eksponert er så dypt og smalt at det bare inneholdet de følgende fem funksjonene: open, write, read, seek, og close. Med disse kan man håndtere de aller fleste typer input og output man måtte trenge, uten å måtte forholde deg til noe av den underliggende koden. Jeg tror de fleste som har jobbet med I/O på operativsystemet fra det tidligere nevnte Seattle-baserte teknologiselskapet vil være enige i at de fem overstående funksjonene er en forbedring.

For å oppsummere: Bruk tid på å finne dype og smale grensesnitt. Det høres veldig banalt ut, men er veldig lett å glemme, og den fremtidige deg vil beholde mer av vettet og håret.

PS: Les boken "A philosophy of Software Design" av John Ousterhout, den er veldig god!

ForrigeNeste