Filip er lei av fasader: - Skal mye til før jeg bruker dette i koden min

- Ikke skjul standardmetoder bak en fasade, ber Filip Van Laenen i Computas.

Filip Van Laenen er fagdirektør for applikasjonsutvikling i Computas. 📸: Computas
Filip Van Laenen er fagdirektør for applikasjonsutvikling i Computas. 📸: Computas Vis mer

Fra tid til annen må man sjekke om et argument eller en variabel er null, og i Java kan man gjøre det på (minst) to måter: sammenligne direkte med null (obj == null), eller bruke metoden Objects.isNull(obj).

Men det hender at jeg kommer bort i kodebaser der man har valgt å lage et tredje alternativ: en egen metode, gjerne i en utility-klasse, som heter noe à la isObjectNull(obj).

Er det så smart da?

Ingen ekstra magi

La oss starte med standardmetoden Objects.isNull(obj). Denne metoden var ikke med i de første versjonene av Java-språket. Sjekker vi dokumentasjonen, finner vi også raskt ut hvorfor:

«This method exists to be used as a Predicate, filter(Objects::isNull)».

Det er altså egentlig ikke meningen å bruke denne metoden i if-setninger for å sjekke om et objekt er null, for da kan man jo bare sammenligne direkte med null. Metoden er bare kjekt å ha når du skal bygge opp et predikat.

Og hvis du skulle være i tvil, implementasjonen er faktisk så enkel som «return obj == null;». Det er altså ingen ekstra magi å finne i Objects.isNull(obj) utover at den sjekker om argumentet er null.

Teoretisk ekstra metodekall

Siden implementasjonen av Objects.isNull(obj) er nøyaktig det samme som en direkte sammenligning med null, kan man spørre seg om det kanskje er noen ulemper forbundet med bruken av denne metoden. Jeg ser to mulige ulemper, men om de også har en praktisk betydning er jeg veldig usikker på.

Første ulempe er at bruk av Objects.isNull(obj) kan forvirre utviklere som ikke er så godt kjent med denne metoden. Det kan hende at de tror at den likevel gjør noe mer enn bare å sammenligne med null, og bruker den bare fordi de ser at noen andre gjør det og tenker at det da sikkert er en smart ting å gjøre. Eller motsatt, kanskje holder de seg unna metoden fordi de ikke helt har kontroll på den.

I praksis gjør det lite forskjell, og egentlig er det et symptom på at du har et helt annet problem enn bruk av Objects.isNull(obj): et kompetanseproblem, som sikkert ikke er begrenset til bare dette her.

«Er det virkelig nødvendig å gi kompilatoren en ekstra oppgave?»

Den andre ulempen er at bruk av Objects.isNull(obj) utgjør et ekstra metodekall, og det kan kanskje ha en negativ effekt på ytelsen. Eller har det det? Det som vil skje i praksis er at kompilatoren rask vil finne ut at logikken fra Objects.isNull(obj) kan «in-lines» der den blir brukt. Dette betyr at operasjonene i metoden blir kopiert over til stedet der metoden blir kalt fra, slik at det som kjøres ikke er et kall til Objects.isNull(obj), men en lokal sammenligning med null.

Man kan dermed si at bruk eller ikke-bruk av Objects.isNull(obj) egentlig koker ned til et spørsmål om smak og behag. Men på den andre siden: er det virkelig nødvendig å gi kompilatoren en ekstra oppgave, nemlig å in-line kallene våre til Objects.isNull(obj)?

Uansett hvor liten den er, så blir det en ekstra kost, så jeg pleier å sammenligne med null direkte.

Enda et metodekall

Enda verre blir det hvis man velger å legge kallet til Objects.isNull(obj) bak en egen fasademetode. Om man virkelig kan kalle det en fasade da, for det er jo ikke akkurat en forenkling å skjule Objects.isNull(obj) bak ObjectUtils.isObjectNull(obj).

Og når det allerede finnes to alternativer fra før, trenger man ikke å bygge en tredje. Dessuten gjør man oppgaven til kompilatoren enda mer komplisert: nå skal den ikke bare in-line ett metodekall, men oppdage at den kan in-line operasjoner som ligger to metodekall unna!

«Når det allerede finnes to alternativer fra før, trenger man ikke å bygge en tredje.»

Også for lesbarheten er dette en dårlig ide. I motsetning til Objects.isNull(obj), som man bør kunne forvente at en Java-utvikler vet hva det er, er det ikke gitt på forhånd at ObjectUtils.isObjectNull(obj) virkelig ikke gjør noe mer enn bare en sammenligning med null. Kanskje den logger også? Eller har en annen side-effekt? Og sjekker den virkelig bare om objektet er null, eller også om det er tomt hvis det er en String eller en Collection?

Ser man i tillegg at kodebasen bruker både == null, Objects.isNull(obj) og ObjectUtils.isObjectNull(obj), så begynner man som utvikler å lure på hva som er riktig.

Så har jeg noen ganger hørt argumentet at det kan være lurt å lage en egen fasademetode i tilfelle man senere ønsker å endre litt på oppførselen. Jeg vil påstå at det er en dårlig ide, både det å planlegge for en eventualitet som man ikke ennå aner hva vil kunne være for noe, og senere å gjennomføre en slik endring.

Hvem tør å endre oppførsel på en metode med ganske basal funksjonalitet som har blitt brukt mange steder i kodebasen? Selv med hundre prosent testdekning på alle metoder og enhetstester av høy kvalitet hadde jeg vært veldig skeptisk til en slik endring.

Ikke lag fasademetoder med mindre du vet hvorfor du trenger dem

Og så er spørsmålet også: hvor mange fasademetoder skal du lage fordi du tenker at det på et eller annet tidspunkt kan være kjekt å legge litt ekstra funksjonalitet rundt basisoperasjoner?

I beste fall utgjør de ikke noe forskjell – hvis kompilatoren klarer å in-line alt, men jeg har også sett forsøk på å reimplementere metoder som Collection.contains(element) som mildt sagt ikke så helt optimale ut. Jeg er sikker på at det finnes kodebaser der ute som faktisk har flere varianter av det også, den ene dårligere enn den andre.

Betyr det at det alltid er feil å lage fasademetoder foran funksjonalitet i standardbiblioteket? Nei, det ville jeg ikke sagt.

Faktisk hender det av og til at jeg lager en liten fasade selv, men da med en konkret grunn.

Noen ganger har jeg for eksempel bruk for Random, men ønsker å kunne kjøre testene på en deterministisk måte. Da er det veldig kjekt å ha en fasademetode som bruker java.util.Random under vanlig kjøring, men er koblet til den velkjente XKCD-versjonen 4 når jeg kjører enhetstester.

📸: XKCD
📸: XKCD Vis mer

Det blir en slags fattigmannsmock, men hvis det gjør at jeg kan slippe å dra inn et mockrammeverk, så synes jeg at det er verdt det.

Men ellers skal det mye til før jeg bruker Objects.isNull(obj) i mine kodebaser.