Pytanie Najlepsza praktyka do sprawdzania wartości null w Scali


Rozumiem, że null w Scali jest niezadowolony i że zawsze należy zawijać opcjonalne wartości w obrębie Option (lub "typ zerowy", jeśli jest dostępny).

Korzyści są oczywiste, nigdy nie powinno być potrzeby sprawdzania null wartości, a wynikowy kod jest zarówno bezpieczniejszy, jak i przyjemniejszy do odczytania.

W praktyce jednak nic nie stoi na przeszkodzie, aby wartość była null - reguła nie jest egzekwowana przez kompilator i jest bardziej zgodą dżentelmena. Może to łatwo zepsuć się, na przykład podczas interakcji z interfejsami Java API.

Czy istnieje do tego najlepsza praktyka? Powiedz na przykład, że muszę napisać Url klasy case, a wystąpienia tej klasy można utworzyć z poziomu a java.net.URI:

object Url {
  def apply(uri: URI): Url = Url(uri.getScheme, uri.getHost, uri.getRawPath)
}

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Url = copy(protocol = value)
  def host(value: String): Url = copy(host = value)
  def path(value: String): Url = copy(path = value)
}

Na URI instancja może powrócić null dla getScheme, getHost i getRawPath. Czy uważa się za wystarczające do ochrony przed nimi w apply(URI) metoda (z require wypowiedzi, na przykład)? Technicznie, aby być całkowicie bezpiecznym, lepiej byłoby chronić protocol, host i path metody pomocnicze, a także konstruktor, ale to brzmi jak strasznie dużo pracy i podstawa.

Zamiast tego uważa się za bezpieczne zabezpieczenie przed API znanym z akceptowania / zwracania null wartości (URI w naszym przykładzie) i założyć, że zewnętrzni rozmówcy albo nie przejdą null wartości czy tylko sami są winni, jeśli tak się dzieje?


11
2018-06-02 10:07


pochodzenie




Odpowiedzi:


Kiedy mamy do czynienia z czystymi funkcjami, zawsze lepiej jest trzymać się całkowitych implementacji i zamiast używać null, require lub wyrzucanie wyjątków, aby zakodować wszystkie błędy w danych. Typy jak Option i Either czy to właśnie jest? Oba są monadami, więc możesz użyć "za" -syntax z nimi.

Następująca modyfikacja kodu pokazuje całkowitą funkcję apply, który produkuje tylko znaczącą wartość, gdy wejściowy Java URI zapewnia nie nulls. Kodujemy pojęcie "znaczący", zawijając wynik Option.

object Url {
  def apply(uri: java.net.URI): Option[Url] =
    for {
      protocol <- Option(uri.getScheme)
      host <- Option(uri.getHost)
      path <- Option(uri.getRawPath)
    }
    yield Url(protocol, host, path)
}

case class Url(protocol: String, host: String, path: String)

Funkcja Option jest standardową funkcją sprawdzania wartości null, która mapuje null do None i zawija wartości w Some.


Odnośnie funkcji "ustawiania", możesz zaimplementować nullsprawdzanie w nich podobnie, używając Option. Na przykład.:

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Option[Url] = 
    Option(value).map(v => copy(protocol = v))
  // so on...
}

Jednak radziłbym temu. W Scala konwencjonalne jest, aby nigdy nie używać nulls, więc konwencjonalne jest tylko wdrażanie null- obsługa API "mostu", gdy otrzymasz wartości z bibliotek Java. Dlatego właśnie nullSprawdzanie ma sens podczas konwersji java.net.URI, ale potem jesteś w świecie Scala, gdzie nie powinno być nulls, a więc nullsprawdzanie staje się po prostu zbędne.


9
2018-06-02 10:51



Warto o tym wspomnieć nullCheck jest po prostu Option  obiekt w Scala. - Ionuț G. Stan
Dzięki, twój punkt widzenia jest dobry i rozumiem to. Uważam, że nie dotyczy to ochrony pomocnika protocol, host i path metody, czy też czegoś brakuje? Czy powinienem po prostu uważać, że rozmówcy nigdy nie przejdą null? - Nicolas Rinaudo
Co masz na myśli, że ich nie chroni? Żadne z nich nigdy nie może się stać null. - goral
@ IonuţG.Stan Dzięki za poprawkę. Całkowicie o tym zapomniałem. - Nikita Volkov
@NicolasRinaudo Proszę zobaczyć aktualizacje. - Nikita Volkov