Slide 1

Slide 1 text

「Result型」のすゝめ 第20回「技術共有会」

Slide 2

Slide 2 text

今回お話する内容 リンク https://qiita.com/Kodak_tmo/items/ d48eb3497be18896b999

Slide 3

Slide 3 text

「Result型」とは? 何かしら処理の成功、または失敗を表す型のこと。 Swift、Scala、最近流行りのRust等には言語自体に「Result型」を定義できる仕組みがある。 残念ながら、TypeScriptには、そのような仕組みが無いため、自分で実装する必要がある。 Result型いいなぁ.... Result型あるー Result型あるー Result型あるー ....

Slide 4

Slide 4 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } }

Slide 5

Slide 5 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } } 成功クラス 失敗クラス

Slide 6

Slide 6 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } } コンストラクターで成功の 結果を受け取る。 インスタンス変数から結果 を参照できるようにする。 コンストラクターで失敗の 結果を受け取る。 インスタンス変数から結果 を参照できるようにする。

Slide 7

Slide 7 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } } 成功 or 失敗の判定メソッ ドを用意する。 成功 or 失敗の判定メソッ ドを用意する。

Slide 8

Slide 8 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } } ※実は、各クラスの反対のメソッド(成功なら失 敗を判定するメソッド)は使用していない。 しかし、このメソッドを実装しておかないと、 コード補完が上手く働かない。 ※実は、各クラスの反対のメソッド(成功なら失敗を判定 するメソッド)は使用していない。 しかし、このメソッドを実装しておかないと、コード補完が 上手く働かない。 仕組みは以下と同じ。TypeScriptは「構造的部分型」を 採用しているのでこのような結果になる。

Slide 9

Slide 9 text

「Result型」を見てみよう type Result = Success | Failure; class Success { readonly value: T; constructor(value: T) { this.value = value; } isSuccess(): this is Success { return true; } isFailure(): this is Failure { return false; } } class Failure { readonly error: E; constructor(error: E) { this.error = error; } isSuccess(): this is Success { return false; } isFailure(): this is Failure { return true; } } 最後に、Result型を定義する。 Success型とFailure型をUnion型にする。 何かしら処理の成功、または失敗を表す型と なる。

Slide 10

Slide 10 text

「Result型」の使い方(その1)

Slide 11

Slide 11 text

「Result型」の使い方(その2)

Slide 12

Slide 12 text

「Result型」の特徴・まとめ - 「Result型」は、エラーハンドリング手法の1つ。 - try-catchを(可能な限り)用いないので、以下の恩恵が得られる。 - エラーハンドリングが容易。 - 型安全。 - エラーのテストが容易。

Slide 13

Slide 13 text

エラーハンドリングが容易とは?(その1) // こんなことになったことはない? ライブラリ、 API、JSON parse するたびに try-catch して、エラーハンドリングが複雑化。。。 type JsonType = { msg: string } function fooChildren (str: string) { try { return JSON.parse(str) as T } catch(error) { // ...省略 } } function foo(str: string) { try { const json1 = fooChildren (str) const json2 = fooChildren (str) return { json1, json2 } } catch(error) { // ...省略(json1でエラーが出た場合、 json2でエラーが出た場合でエラーの出し分け、どうコントロールするかを考える必要がある) } } function main() { try { return foo('{ "msg":"hello" }' ) // {“json1”:{“msg”:”hello”}, “json2”:{“msg”:”hello”}} } catch(error) { // ...省略(json1でエラーが出た場合、 json2でエラーが出た場合でエラーの出し分け、どうコントロールするかを考える必要がある) } } ・エラーハンドリング箇所が多い ・考えることが多い ・コードが読み辛い

Slide 14

Slide 14 text

エラーハンドリングが容易とは?(その2) // こんなことになったことはない? ライブラリ、 API、JSON parse するたびにtry-catchして、エラーハンドリングが複雑化。。。 type JsonType= { msg: string } function fooChildren (str: string) { try { return JSON.parse(str) as T } catch(error) { // ...省略 } } function foo(str: string) { const json1 = fooChildren (str) const json2 = fooChildren (str) return { json1, json2 } } function main() { try { return foo('{ "msg":"hello" }' ) // {“json1”:{“msg”:”hello”}, “json2”:{“msg”:”hello”}} } catch(error) { if (error instanceof Error) { // エラー判別処理が巨大化・複雑化 if (error.name === 'xxxx') { // ...省略 } if (error.name === 'xxxx') { // ...省略 } } throw new Error('unknown Error' ) } } ・エラー判定処理が巨大化・複雑化

Slide 15

Slide 15 text

「Result型」を使うと?

Slide 16

Slide 16 text

エラーハンドリングが容易とは?(Result型) type JsonType= { msg: string } function fooChildren (str: string): Result { try { const json = JSON.parse(str) as T return new Success(json) } catch(error) { // ...省略 return new Failure(new Error('unknown Error' )) } } function foo(str: string): Result<{json1: T,json2: T }, Error> { const result1 = fooChildren (str) if (result1.isFailure()) { // joson1のエラー return new Failure(result1.error) } const result2 = fooChildren (str) if (result2.isFailure()) { // joson2のエラー return new Failure(result2.error) } return new Success({json1:result1.value, json2: result2.value}) } function main() { const result = foo('{ "msg":"hello" }' ) // {“json1”:{“msg”:”hello”}, “json2”:{“msg”:”hello”}} if(result.isFailure()) { // ...省略(そのまま出力で OK。ここでごちゃごちゃしない) return } return result.value } ・エラー判定処理がif文の下にすぐ書ける ・1つ1つのエラーを個別に考えることができる ・コードが見易い

Slide 17

Slide 17 text

型安全とは? type JsonType = { msg: string } function fooChildren (str: string) { try { return JSON.parse(str) as T } catch(error) { // ...省略 } } function foo(str: string) { // foo() の戻り値の型も {json1:T, json2:T} となり、catchが考慮されない。。。 try { const json1 = fooChildren (str) // json1 の型、fooChildren() の戻り値の型は T。つまり、 catchされたエラーが考慮されていない。。。 const json2 = fooChildren (str) // json2 の型、fooChildren() の戻り値の型は T。つまり、 catchされたエラーが考慮されていない。。。 return { json1, json2 } } catch(error) { // ...省略 } } function main() { try { return foo('{ "msg":"hello" }' ) // {“json1”:{“msg”:”hello”}, “json2”:{“msg”:”hello”}} } catch(error) { // ...省略 } } ・catchされたエラーが型として表現されない。呼び出した関数 がエラーになるかどうかは、実際にコードを見なくてはならな い。「Result型」を使えば、このあたりは考慮しなくて良い。

Slide 18

Slide 18 text

エラーのテストが容易とは? テストにおいて、エラー用のマッチャー( toThrow()、toThrowError())を使用しなくてよくなり、 toBe() や toEqual() を使用 して評価することができる。 const result = main() expect(result.isFailure () ? result.error.statusCode : undefined ).toBe(500) expect(result.isFailure () ? result.error.code : undefined ).toBe('APP_FOO_FUNCTION_ERROR' )

Slide 19

Slide 19 text

「Qiita」で頂いたコメントを見る

Slide 20

Slide 20 text

Qiitaでのコメント(その1) Go言語は、各プログラミング言語が抱える問題の 1つである言語自体の複雑化。それによる批判などを解決した上で、 各言語の良い部分を参考にして作られた比較的新しい言語です。 そうやって産まれた言語で、 try-catchが無い。。。というのは、考えさせられるものがありますね。 (ちなみにGo言語のエラーハンドリングは、以下のように `if` で行います。Result型に似てますよね? 2009年生まれだよ

Slide 21

Slide 21 text

Qiitaでのコメント(その2) 上のリンク先から例外箇所について私なりに纏め てみると、「例外は実質的に見えない goto文であり、目に見える goto文よりもわかり辛い。例外があること で、そのコードが正しい かどうかを判断するには、 どこか別なところ を見なければならず、 多くのことを頭の中で一時的に記憶しておく必要があり、大変であ る。」という感じです。 例外は実質的に見えない goto文なんだよ。ということが全てを物語っている気がしますね。

Slide 22

Slide 22 text

「Result型」の特徴・まとめ(再) - 「Result型」は、エラーハンドリング手法の1つ。 - try-catchを(可能な限り)用いないので、以下の恩恵が得られる。 - エラーハンドリングが容易。 - 型安全。 - エラーのテストが容易。 是非、使ってみてください!!!!