JavaからのnullをScalaとKotlinで 安全に扱う方法

# null Pointer Exception!

---

# 皆さん嫌いですよね？

---

# 僕も嫌いです

---

# なぜなら実行時にしか気づかないエラーだから

---

# 無駄にコンパイル時間と手動テストの時間を増やしたくない

---

# そんなあなたにnull安全な言語をオススメします。

---

Kotlinは、null安全な言語です。
Scalaは、null安全じゃないですが、
Scalaでは、nullは、Option型でラップして、
Noneに変換することにより、
コンパイル前に警告が出て時間を節約できます

<img width="468" alt="スクリーンショット 2017-10-13 3.46.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/e318367c-9205-7450-7e3e-c3a099a42e64.png">

```scala
scala> Option(null)
res5: Option[Null] = None
```
---

# 具体的な例

---

HelloJava.javaでnullを返すメソッドを用意します

```scala
public class HelloJava {
    public String get1() {
        return "1";
    }

    public String get2() {
        return "2";
    }

    public String getNull() {
        return null;
    }
}
```

---

## Scalaの場合

---

HelloScala.scala

```scala
object HelloScala {
  def main(args: Array[String]): Unit = {
    val helloJava = new HelloJava()
    // In Scala, null is to wrap with option type.
    val string1 = Option(helloJava.get1()) // Option[String]
    val string2 = Option(helloJava.get2()) // Option[String]
    val string3 = Option(helloJava.get3()) // Option[String]
    val string4: String  = helloJava.getNull() // null 型検査エラーなし
    val string5: String = None // コンパイル前に警告がでて、型検査エラーあり

    println(string1) // Some(1)
    println(string2) // Some(2)
    println(string3) // None
    println(string4) // null
    println(string4.toInt)
    // Exception in thread "main" java.lang.NumberFormatException: null
    // 実行時エラー
    println(string5)
}
```

---

## Scalaの場合
String型にnullを代入してもエラーにならないので
null.toIntで、null Pointer Exceptionの危険性
nullをOption型でラップして、None型に変換すると
コンパイル前に警告が出るので、事前に気づいて
コンパイル時間を節約できる

<img width="468" alt="スクリーンショット 2017-10-13 3.46.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/e318367c-9205-7450-7e3e-c3a099a42e64.png">

---

```scala
scala> var string = Option(null)
string: Option[Null] = None
scala> string match {
     |   case Some(string) => println(string)
     |   case None => println("string is None")
     | }
string is None
```

---

## Scalaの場合、
コンパイル前に
String型は、Noneを代入できない警告がでる
<img width="456" alt="スクリーンショット 2017-10-13 3.43.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/528162f4-050a-6179-614b-7ed31deab1f0.png">

String型は、Option(null)を代入できない警告が出る
<img width="468" alt="スクリーンショット 2017-10-13 3.46.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/e318367c-9205-7450-7e3e-c3a099a42e64.png">


---

### Kotlinの場合

---

HelloKotlin.kt

```kotlin
fun main(args: Array<String>) {
    val string1: String? = HelloJava().get1() // String!
    val string2: String? = HelloJava().get2() // String!
    val string3: String? = HelloJava().getNull() // String!
    val string4 = HelloJava().getNull() // String!
     
    println(string1) // String?
    println(string2) // String?
    println(string3) // String?
    println(string4) // null
    println(string4.toInt())
    // java.lang.NumberFormatException: null
    // 実行時エラー
    println(string5)

    val string5: String = HelloJava().getNull() // String!
    // Exception in thread "main" java.lang.IllegalStateException: HelloJava().getNull() must not be null
    // 警告なし、コンパイル後に型検査エラーが出る
    
 }
```

---

## Kotlinの場合
JavaからくるnullをStringに代入しても、
コンパイル前に警告はでない、
コンパイル後に、代入できないエラーが出るので、コンパイルを待つ時間が必要です。

---

(注)Javaではなく、Kotlinのnullであれば、
String型に代入するとコンパイル前に警告が出ます

<img width="348" alt="スクリーンショット 2017-10-13 3.47.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/25434/0632a1c0-d8cf-578b-ed1d-69d53e97c989.png">

Javaからくるnullは必ずString?みたいに?をつけて
Option型にして、安全にアンラップしましょう！

---

## ScalaとKotlinのOptionのアンラップの違い

---

## Scalaの場合

---

HelloScala.scala

```scala
import scala.util.{Success, Try}

object HelloScala {
  def main(args: Array[String]): Unit = {
    val helloJava = new HelloJava()
    // In Scala, null is to wrap with option type.
    val string1 = Option(helloJava.get1()) // Option[String]
    val string2 = Option(helloJava.get2()) // Option[String]
    val string3 = Option(helloJava.getNull()) // Option[String]
    // val string4  = helloJava.getNull() // String

    // Expression of type None.type doesn't confirm to expected type String
    // In Scala,null of Java wraps with Option, converts null to None,
    // and checks None before run,but Kotlin need run
    // In Scala, if you assign None to String type, the IDE checks it before executing it.

    println(string1) // Some(1)
    println(string2) // Some(2)
    println(string3) // None
    // println(string4) // null
    // println(string4.toInt)
    // Exception in thread "main" java.lang.NumberFormatException: null
    
    try { // 例1
      (string1, string2, string3) match {
        case (Some(string1), Some(string2), Some(string3)) =>
          println(string1) // String
          println(string2) // String
          println(string3) // String
          println(s"$string1 + $string2 + $string3 = ${string1.toInt + string2.toInt + string3.toInt}") // Int
        case _ => throw new Exception("string1 is None or string2 is None or string3 is None")
      }
    } catch {
      case numberFormatException: NumberFormatException =>
        println(numberFormatException.getMessage)
      case exception: Exception => println(exception.getMessage)
    }

    def toIntTry(option: Option[String]): Option[Try[Int]] = option.map{string => Try(string.toInt)}

    try { // 例2
      for {
        try1 <- toIntTry(string1) // Try[Int] <- Option[Try[Int]]
        try2 <- toIntTry(string2) // Try[Int] <- Option[Try[Int]]
        try3 <- toIntTry(string3) // Try[Int] <- Option[Try[Int]]
      } yield {
        (try1, try2, try3) match {
          case (Success(string1), Success(string2), Success(string3)) =>
            println(s"string1 + string2 + string3 = ${string1 + string2 + string3}")
          case _ =>
            throw new Exception("string1 is None or string2 is None or string3 is None")
        }
      }
    } catch {
      case exception: Exception =>
        println(exception.getMessage)
    }

    try { // 例3 ただの条件分岐(アンラップなし) getはオススメしない
      if (string1 != None && string2 != None && string3 != None) {
        // get is Not Recommend
        println(s"string1 + string2 + string3 = ${string1.get.toInt + string2.get.toInt + string3.get.toInt}")
      } else {
        throw new Exception("string1 is None or string2 is None or string3 is None")
      }
    } catch {
      case numberFormatException: NumberFormatException =>
        println(numberFormatException.getMessage)
      case exception: Exception =>
        println(exception.getMessage)
    }
 }
}
```

---

```scala
    try { // 例1 Tupleのパターンマッチで、アンラップ
      (string1, string2, string3) match {
        case (Some(string1), Some(string2), Some(string3)) =>
          println(string1) // String
          println(string2) // String
          println(string3) // String
          println(s"$string1 + $string2 + $string3 = ${string1.toInt + string2.toInt + string3.toInt}") // Int
        case _ => throw new Exception("string1 is None or string2 is None or string3 is None")
      }
    } catch {
      case numberFormatException: NumberFormatException =>
        println(numberFormatException.getMessage)
      case exception: Exception => println(exception.getMessage)
    }
```

---

```scala
    def toIntTry(option: Option[String]): Option[Try[Int]] = option.map{string => Try(string.toInt)}
    try { // 例2 for comprehensionでアンラップして、toIntの結果を、TryでSuccessとFailureを分ける
      for {
        try1 <- toIntTry(string1) // Try[Int] <- Option[Try[Int]]
        try2 <- toIntTry(string2) // Try[Int] <- Option[Try[Int]]
        try3 <- toIntTry(string3) // Try[Int] <- Option[Try[Int]]
      } yield {
        (try1, try2, try3) match {
          case (Success(string1), Success(string2), Success(string3)) =>
            println(s"string1 + string2 + string3 = ${string1 + string2 + string3}")
          case _ =>
            throw new Exception("string1 is None or string2 is None or string3 is None")
        }
      }
    } catch {
      case exception: Exception =>
        println(exception.getMessage)
    }
```
---

```scala
    try { // 例3 ただの条件分岐(アンラップなし) Option#getはオススメしない
      if (string1 != None && string2 != None && string3 != None) {
        // get is Not Recommend
        println(s"string1 + string2 + string3 = ${string1.get.toInt + string2.get.toInt + string3.get.toInt}")
      } else {
        throw new Exception("string1 is None or string2 is None or string3 is None")
      }
    } catch {
      case numberFormatException: NumberFormatException =>
        println(numberFormatException.getMessage)
      case exception: Exception =>
        println(exception.getMessage)
    }
```

---

## Kotlinの場合

---

HelloKotlin.kt

```kotlin
fun main(args: Array<String>) {
    val string1: String? = HelloJava().get1()
    val string2: String? = HelloJava().get2()
    val string3: String? = HelloJava().getNull()
    // val string4: String = HelloJava().getNull()
    // Exception in thread "main" java.lang.IllegalStateException: HelloJava().getNull() must not be null
    // at HelloKotlinKt.main(HelloKotlin.kt:5)

    println(string1) // 1
    println(string2) // 2
    println(string3) // null
    // println(string4) // string

    try { // 例1 ?.let
        string1?.let { string1 ->
            string2?.let { string2 ->
                string3?.let { string3 ->
                    println(string1) // String
                    println(string2) // String
                    println(string3) // String
                    println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}") // Int
                } ?: run {
                    throw Exception("string3 is null")
                }
            } ?: run {
                throw Exception("string2 is null")
            }
        } ?: run {
            throw Exception("string1 is null")
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }

    try { // 例2 パターンマッチ
        when (string1) {
            null -> throw Exception("string1 is null")
            else -> when (string2) {
                null -> throw Exception("string2 is null")
                else -> when (string3) {
                    null -> throw Exception("string3 is null")
                    else ->
                        // string1: String?
                        // string2: String?
                        // string3: String?
                        println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}")
                }

            }
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }

    try { // 例3 スマートキャスト
        if (string1 != null && string2 != null && string3 != null) {
            println(string1) // String?
            println(string2) // String?
            println(string3) // String?
            println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}") // Int
        } else {
            throw Exception("string1 is null or string2 is null or string3 is null")
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }
 }
```

---

```kotlin
    try { // 例1 ?.let
        string1?.let { string1 ->
            string2?.let { string2 ->
                string3?.let { string3 ->
                    println(string1) // String
                    println(string2) // String
                    println(string3) // String
                    println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}") // Int
                } ?: run {
                    throw Exception("string3 is null")
                }
            } ?: run {
                throw Exception("string2 is null")
            }
        } ?: run {
            throw Exception("string1 is null")
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }
```
---

```scala
    try { // 例2 パターンマッチ
        when (string1) {
            null -> throw Exception("string1 is null")
            else -> when (string2) {
                null -> throw Exception("string2 is null")
                else -> when (string3) {
                    null -> throw Exception("string3 is null")
                    else ->
                        // string1: String?
                        // string2: String?
                        // string3: String?
                        println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}")
                }

            }
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }
```

---

```kotlin
    try { // 例3 スマートキャスト
        if (string1 != null && string2 != null && string3 != null) {
            println(string1) // String?
            println(string2) // String?
            println(string3) // String?
            println("$string1 + $string2 + $string3 = ${string1.toInt() + string2.toInt() + string3.toInt()}") // Int
        } else {
            throw Exception("string1 is null or string2 is null or string3 is null")
        }
    } catch(numberFormatException: NumberFormatException) {
        println(numberFormatException.message)
    } catch(exception: Exception) {
        println(exception.message)
    }
```

---

## まとめ
KotlinのJavaからのnullの殺し方は
String?などのOption型に代入して
null安全にアンラップすると
無駄なコンパイル時間を減らせる

---

ScalaのJavaからのnullの殺し方は
Option(null)などOption型でラップして、
Noneに変換して安全にアンラップすると
無駄なコンパイル時間を減らせる

---

ScalaのOptionのget、Kotlinの!!などで、
Option型を強制アンラップすると
null,Noneの時に、実行時エラーになって、
クラッシュする可能性があるので
安全にアンラップして
適切なエラーハンドリングしましょう！

---

# エラーハンドリング
ユーザーが離脱しないように、
発生したエラーに応じて、
ユーザーや開発者に現在の状態を示し、
必要に応じて次の行動を示してあげること

---

### オフライン
(例)Free Wifiに繋がっているけど認証してない場合
× ユーザーに何も伝えずリロードボタンだけ表示
ユーザーがオフラインに気づかないと、
クレームされたり、離脱してしまう
○ ユーザーにネットワークの接続がないことを伝えて、ネットに接続すれば使えることを伝える

---

## 不正な入力データ

× ユーザーに何も伝えず、全てのサービスがクラッシュする
× ユーザーに何も伝えず、不正な入力データを受け付けない
○ 不正な入力データであることを伝えて、正常な入力を促す

---

null安全,None安全な言語で
?のオプショナルチェーンや安全なアンラップで、
バグになる仕様の場合にバグっても動くだけだと
障害に気づかないでバグを出し続けるので
必要に応じて、エラーハンドリングしたり、
エラー検知しましょう！
[null安全な言語でも、バグ検知を怠れば安心ではない](https://qiita.com/applideveloper/items)

---

# Fin

---