Categories
Teknik

Säg bara nej till null!

En av de minst framträdande nyheterna i Java 8 är Optional-klassen. Rätt använd kan den göra din kod tydligare och mer kortfattad.

En av de minst framträdande nyheterna i Java 8 är Optional-klassen. Rätt använd kan den göra din kod tydligare och mer kortfattad.

 

Optional-klassen kan till utseende och gränssnitt se minst sagt kryptisk ut, vilket nedanstående lista över publika metoder visar:

 static <T> Optional<T> empty()

 Optional<T> filter(Predicate<? super T> predicate)

 <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)

 T get()

 void ifPresent(Consumer<? super T> consumer)

 boolean isPresent()

 <U> Optional<U> map(Function<? super T,? extends U> mapper)

 static <T> Optional<T> of(T value)

 static <T> Optional<T> ofNullable(T value)

 T orElse(T other)

 T orElseGet(Supplier<? extends T> other)

 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

Men vad denna klass kan göra för oss är inget mindre än att hjälpa oss att designa API:er utan null-värden. Detta genom att agera som en wrapper för andra klassers objekt, vilket gör att de aldrig tillåts vara null.

Hur ofta ser man inte kod som ser ut som nedan:

//Special case! May be null or may not be null!

if (specialCaseData != null) {
    doWork(specialCaseData.getData())
} else { 
    specialCaseData = DEFAULT_DATA;&nbsp;
    doWork(data)
}

eller som denna:

//null parameters will be ignored

doWorkUsingParams(id, null, emailAddress, null, phoneNumber, null);

Det finns ett antal problem i båda dessa kodstycken:

  • Null används för att representera ett tillstånd i systemet, vilket gör det svårt att veta om tillståndet i fråga är avsiktlig eller oavsiktlig avsaknad av värdet.
  • Det är omöjligt att se vilken parameter null-värdet representerar utan att ha tillgång till dokumentationen eller implementationen av doWorkUsingParams-metoden.
  • Typsäkerheten går delvis förlorad då godtyckliga null-värdesobjekt kan användas.


Om man i det första fallet använde Optional, så skulle man kunna skriva om koden till följande:

if (specialCaseData.isPresent()) {
    doWork(specialCaseData.get())
} else {
    doWork(specialCaseData.orElse(DEFAULT_DATA))
}

Här gör man det tydligt att man vill veta om värdet är närvarande eller inte, samt vad som ska göras om värdet saknas. Detta kan i sin tur förkortas ytterligare:

doWork(specialCaseData.orElse(DEFAULT_DATA))

orElse-metoden innebär här att det ordinarie värdet för specialCaseData ska användas om det är närvarande, annars ska DEFAULT_DATA användas.

I det andra fallet blir nyttan av Optional än mer tydlig:

doWorkUsingParams(id, Optional.<Name>empty(), emailAddress, 

Optional.<PostalAddress>empty(), phoneNumber, Optional.<MobilePhoneNumber>empty());

Här framgår det både vilka värden som medvetet lämnats tomma och vilka datatyper de har.

Att omvandla existerande kod som använder null-värden finns det också stöd för i Optional-klassen, i form av metoden Optional.ofNullable(). I scenariot som visas nedan:

private String voluntaryComment;

//Can be set to null
public void setVoluntaryComment(String voluntaryComment) {
    this.voluntaryComment = voluntaryComment;
}

Så kan vi istället skriva:

private Optional<String> voluntaryComment;

public void setVoluntaryComment(String voluntaryComment) {
    this.voluntaryComment = Optional.ofNullable(voluntaryComment);
}

Optional.ofNullable tar in ett värde och omvandlar det till en Optional som sen returneras. Om värdet är null så returneras en tom Optional (alltså en Optional.empty()).

Även i fallet med get-metoder så kan Optional hjälpa oss att tydliggöra kodens intentioner. Betänk följande kodstycke:

public Integer getNumberOfCustomers() {
    return this.numberOfCustomers;
}

Som Javautvecklare bör metodgränssnitt med detta utseende instinktivt göra dig misstänksam. Frågor som “Om ett heltal ska returneras, varför returneras det inte som en primitiv int?” och “Om jag får tillbaka ett nullvärde, betyder det att numberOfCustomers faktiskt är 0 eller att uppgiften saknas?” dyker upp. Men samtidigt kan det vara lätt att missa att det som returneras är ett objekt och inte ett primitivt värde.

Denna osäkerhet och otydlighet kan stävjas med ett gränssnitt som returnerar Optionals:

public Optional<Integer> getNumberOfCustomers() {
    return this.numberOfCustomers;
}

Här blir det tydligt redan i gränssnittet att fallet då värdet saknas måste hanteras.

Ett annat vanligt förekommande mönster i Java-kod är följande fall, där vi vill utföra ett arbete med det givna datat endast om det inte är null:

if (param != null) {
    doWork(param);
}

Skillnaden mot fallet ovan är att vi här inte vill göra något om datat inte finns. Else-satsen är alltså medvetet utelämnad.

Detta kan med Optional-klassen uttryckas enligt följande:

if (param.isPresent()) {
    doWork(param.get());
}

Om vi däremot har flera av dessa if-satser efter varandra så är det lätt att Javas långdragenhet börjar gå en på nerverna. Till vår räddning kommer då Optional.ifPresent:

param.ifPresent(p -> doWork(p));

Optional.ifPresent-metoden ser efter om den Optional den anropas på är tom eller ej, och endast om den inte är tom så körs den Consumer-instansen eller, som i vårt exempel, lambda-funktionen.

Om du i dagsläget arbetar med en äldre Java-version än 1.8 så kan du dock ändå använda knepen från denna artikel med hjälp av ett bibliotek från Google kallat Guava, som bland annat innehåller en implementation av Optional-klassen kompatibel med Java 1.6 och senare.

Vid denna punkt finns det dock en viktig sak att betänka: även om Optional-klassen ger oss fina verktyg att ersätta nullvärden och göra koden mer tydlig så kan en kodbas med alltför många förekomster av antingen null eller Optionals vara ett tecken på dålig design. Om man inte behöver kontrollera förekomsten av värden så blir koden mer lättläst och presterar bättre.

Som vi sett ovan så hjälper Optional-klassen oss att göra koden tydligare då den eliminerar tvetydigheter i de fall då vi inte säkert kan veta om ett null-värde betyder att värdet utelämnats medvetet eller på grund av ett fel. Optional-klassen gör även anrop till metoder där vissa argument kan utelämnas tydligare då de ersätter null-värden med typade tomma Optional-instanser. Dessutom ges vi metoden Optional.ofNullable som gör det enkelt att konvertera nullvärden till tomma Optional-instanser.

Allt som återstår nu är att kavla upp ärmarna och refaktorera bort null ur din kod!

Kom i kontakt med Ola Rende

 

Leave a Reply

Your email address will not be published. Required fields are marked *