Hvis-erklæringer design: beskyttelsesklausuler kan være alt hvad du har brug for

Der er en masse debat om, hvordan if-udsagnene skal bruges til bedre kodeklarhed og læsbarhed. De fleste af dem koges ned til en opfattelse af, at det er helt subjektivt og æstetisk. Og normalt er det sandt, men nogle gange kan vi stadig finde en fælles grund. Dette indlæg sigter mod at udlede et lille sæt råd for et par sådanne sager. Forhåbentlig reducerer dette mængden af ​​tænkning og debattering, når man skriver noget så simpelt som en if-statement.

Vagtsklausuler

Den mest dokumenterede sag er sandsynligvis beskyttelsesklausulen, også kendt som påstand eller forudsætning. Ideen er, at når du har noget at hævde i begyndelsen af ​​en metode - gør dette ved hjælp af en hurtig retur.

offentlig tomgangskørsel (Timer timer) {
  if (! timer.isEnabled ())
    Vend tilbage;
  
  if (! timer.valid ())
    kaste ny InvalidTimerException ();
  timer.run ();
}

På trods af at det ser indlysende ud, bliver det ofte efterladt på grund af det faktum, at vores hjerner fungerer anderledes: Vi har en tendens til at tænke "køre timeren, hvis den er aktiveret", ikke "køre timeren, men hvis den ikke er aktiveret, gør intet". I koden fører sidstnævnte imidlertid til en bedre adskillelse af hjørnesager fra hovedmetodelogikken, hvis vi strengt følger, at kun noget, der hævdes i starten, kan betragtes som en beskyttelsesklausul.

Som det fremgår, er beskyttelsesklausulen ikke næsten en ny idé. Alligevel ser jeg det som en af ​​de mest ligetil og enkle, der kan accepteres uden megen debat. Men den største åbenbaring for mig var, at de fleste af de tilfælde, hvis hvis-erklæring bruges, kan omdannes til at bruge en beskyttelsesklausul i stedet, og det er dette, jeg vil uddybe.

Store if-blocks

Det er temmelig almindeligt at finde noget lignende i en kodebase:

if (noget.is aktiveret ()) {
  // smuk
  // lang
  // logik
  // af
  // kører
  // noget
}

Normalt er det ikke designet sådan fra starten. I stedet for vises sådanne blokke, når der vises et nyt krav, der siger ”vi skal køre det noget kun i tilfælde af X”. Den mest ligefremme løsning, selvfølgelig, ville være at pakke “run that noget” -delen ind i en if-statement. Let.

Problemet er, at det faktisk kun er let at skrive, men ikke at læse. Se, for at forstå koden, man har brug for for at udlede hele billedet fra det, et koncept, man kan arbejde videre med - fordi mennesker er meget bedre til at arbejde med koncepter, ikke på algoritmer. Introduktion af denne type tilstand i starten skaber en ekstra kontekst, som skal huskes, når man arbejder med hovedmetodelogikken. Det er klart, at vi bør tilstræbe at reducere den nødvendige mentale kontekst på ethvert givet tidspunkt.

Et andet stort problem opstår, når logikken i en sådan kode bliver lang nok til ikke at passe på skærmen lodret. På et tidspunkt kan du endda glemme, at det er der, hvilket ville ødelægge dit billede helt.

For at undgå alt dette, skal du bare invertere if-erklæringen, så betingelsen bliver en beskyttelsesklausul:

if (! noget.is aktiveret ())
  Vend tilbage;
// samme
// lang
// logik
// af
// kører
// noget

Igen er der en adskillelse af bekymringer: først slipper du af forudsætningerne i begyndelsen (kaster dem også væk fra dit sind - hvilket er vigtigt for bedre at kunne fokusere på hovedlogikken), og derefter bare gøre, hvad metoden kræves for at gøre.

Vender tilbage i midten

Flere afkast betragtes som en dårlig idé af grunde, og dette er sandsynligvis en af ​​de største. I modsætning til en tilbagevenden i en beskyttelsesklausul, opdages et retur eller kast midt i metoden ikke så let ved første øjekast. Som enhver anden betinget logik komplicerer det processen med at opbygge et mentalt billede af metoden. Derudover kræver det dig at overveje, at denne metodes hovedlogik ikke under nogle omstændigheder udføres fuldstændigt, og hver gang du skal beslutte: skal du placere den nye kode før eller efter denne tilbagevenden?

Her er et eksempel på en reel kode:

public void executeTimer (String timerId) {
  logger.debug ("Eksekvering af timer med ID {}", timerId);
  TimerEntity timerEntity = timerRepository.find (timerId);
  logger.debug ("Found TimerEntity {} for timer ID {}", timerEntity, timerId);
  if (timerEntity == null)
    Vend tilbage;
  Timer timer = Timer.fromEntity (timerEntity);
  timersInvoker.execute (timer);
}

Ser ud som en forudsætning, er det ikke? Lad os sætte det, hvor en forudsætning skal være:

public void executeTimer (String timerId) {
  logger.debug ("Eksekvering af timer med ID {}", timerId);
  TimerEntity timerEntity = timerRepository.find (timerId);
  logger.debug ("Found TimerEntity {} for timer ID {}", timerEntity, timerId);
  executeTimer (timerEntity);
}
private void executeTimer (TimerEntity timerEntity) {
  if (timerEntity == null)
    Vend tilbage;
  Timer timer = Timer.fromEntity (timerEntity);
  timersInvoker.execute (timer);
}

Denne refacturerede kode er meget lettere at læse, da der overhovedet ikke er nogen betingelser i hovedlogikken, der kun er en række enkle handlinger. Hvad jeg ønskede at vise, er, at det næsten altid er muligt at opdele en metode, der har et afkast i midten, så forudsætningerne er pænt adskilt fra hovedlogikken.

Små betingede handlinger

Det er også en almindelig praksis at have masser af små betingede udsagn som denne:

if (timer.getMode ()! = TimerMode.DRAFT)
  timer.validate ();

Dette er generelt legitimt i generelt tilfælde, men er ofte bare en skjult forudsætning, som bedre bør placeres i en metode selv. Du vil sandsynligvis bemærke dette yderligere, når hver gang du påberåber sig metoden, du har brug for at tilføje denne if-statement, fordi forretningslogikken siger det. I betragtning af det ville den rigtige løsning være:

offentlig ugyldiggør validering () {
  if (tilstand == TimerMode.DRAFT)
    Vend tilbage;
  
  // valideringslogik
}

Og brugen er nu så enkel som:

timer.validate ();

Konklusion

Brug af beskyttelsesklausuler er en god praksis for at undgå unødvendig forgrening og dermed gøre din kode mere mager og læsbar. Jeg tror, ​​at hvis disse små råd overvejes systematisk, vil det i høj grad reducere vedligeholdelsesomkostningerne for dit stykke software.

Og til sidst er der generelt intet dårligt ved at have en betinget erklæring her eller der i din kode. Selvom min erfaring viser, at hvis du aldrig er interesseret i at prøve at undgå det, så vil du på et tidspunkt ende med at spilde timer med at opbygge et mentalt billede af et komplekst meget forgrenet stykke kode, mens du prøver at lægge den næste ting, en virksomhed ønsker det, .