

Firestoreのドキュメントの中で以下のようなエラー処理について悩んでいます。  
参考<https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja>

[code]

    let docRef = db.collection("cities").document("BJ")
    
    docRef.getDocument { (document, error) in
        // Construct a Result type to encapsulate deserialization errors or
        // successful deserialization. Note that if there is no error thrown
        // the value may still be `nil`, indicating a successful deserialization
        // of a value that does not exist.
        //
        // There are thus three cases to handle, which Swift lets us describe
        // nicely with built-in Result types:
        //
        //      Result
        //        /\
        //   Error  Optional<City>
        //               /\
        //            Nil  City
        let result = Result {
          try document?.data(as: City.self)
        }
        switch result {
        case .success(let city):
            if let city = city {
                // A `City` value was successfully initialized from the DocumentSnapshot.
                print("City: \(city)")
            } else {
                // A nil value was successfully initialized from the DocumentSnapshot,
                // or the DocumentSnapshot was nil.
                print("Document does not exist")
            }
        case .failure(let error):
            // A `City` value could not be initialized from the DocumentSnapshot.
            print("Error decoding city: \(error)")
        }
    }
    
    
[/code]

この上記の中で、Resultというのはどこから来たのでしょうか？  
また、このResultをletでoptionalにしている理由が分かりません。

私が思うには、引数としてのdocumentに対してresultの処理をすれば分かるのですが  
成功した値であるdocumentとは別に、Resultを使っている理由が分かりません。  
他のコードを見ると、成功したとしてdocumentを使っているからです。

ご教授よろしくお願いいたします。


CC BY-SA 4.0
2021-12-13T15:46:07.463
84114
2021-12-15T00:57:17.910
47147
question
swift firebase
Swift Firestore Resultについて


通常であれば、データがなければnilあれば失敗、データがあれば成功でもいい時はありますが、おそらく通信に失敗した、Cityにデコード失敗などのことを考慮するために、Resultを使用しているんだと思います。

Result自体は、Swift標準の機能で、try-catchと同じような例外処理の1つです。


CC BY-SA 4.0
2021-12-14T01:55:54.587
84122
2021-12-14T02:05:35.750
2021-12-14T02:05:35.750
40856
40856
84114
answer


> Resultというのはどこから来たのでしょうか？

`Result`と言うのは、Swift標準ライブラリに含まれる型です。  
[Result](https://developer.apple.com/documentation/swift/result)

従来のように2つの変数を使って、「成功の場合は`result`に非nilの値を返し、`error`に`nil`を入れる。失敗の場合は`result`に`nil`を入れ、`error`に非nilの値を入れる」と言ったお約束では、`result`と`error`が両方nilになることはありえないことをSwiftの型システム上は表現できないので、そのような場面で使われることを想定しています。

`Result`の後ろにクロージャーが続いているのは、次のイニシャライザを使っています。

[init(catching:)](https://developer.apple.com/documentation/swift/result/3139399-init)

このイニシャライザでは、クロージャーの中で`Error`が投げられた時に、`Result.failure(...)`を、エラー終了せずに戻り値が返された時に`Result.success(...)`を生成するようになっています。

* * *

> このResultをletでoptionalにしている理由が分かりません。

ご掲載のコードで`let`は複数箇所に現れているので、どの部分を言っているのか明確にされると、より的確な回答を得やすくなると思います。

ここでは、`case .success(let city)`の部分だと仮定しています。

ここでテストしているのは、`try document?.data(as:
City.self)`が`Error`を投げたかどうかです。`document`はOptionalなので、`nil`であれば`data(as:)`メソッドが呼ばれないまま結果は`nil`になりますから、`try
document?.data(as:
City.self)`が成功時エラーを投げずに戻す値は`City`型ではなく、`Optional<City>`型になります。

* * *

> 成功した値であるdocumentとは別に、Resultを使っている理由が分かりません。

これは元のコードが著しく見にくい(天下のGoogleが発表しているコードだから良い書き方をしている、なんて思わない方が良い例ですね)せいもありますが、「何の成功・失敗」をどこで判定しているのかが十分伝わっていないようです。

  * `getDocument`の成功・失敗

成功: `document`が非nil, `error`が`nil`  
失敗: `document`が`nil`, `error `が非nil

  * `data(as:)`の成功・失敗

成功: `result`の値が`.success(City?)`  
失敗: `result`の値が`.failure(Error)`

※この場合の「成功」には`document`が`nil`のせいで`data(as:)`が呼ばれない場合を含む

つまり掲載されたコードは`getDocument`自体が失敗して、`document`には`nil`が入っている場合を後からチェックしていることになります。

* * *

少なくとも、どこでどんなエラーが発生しているのかチェックしていることをコードでわかりやすく表現したいなら、ご掲載のコードは及第点とは言えないですね。

例えばこんな風に書くともう少しわかりやすくなるでしょうか。

[code]

            let docRef = db.collection("cities").document("BJ")
    
            docRef.getDocument { (document, error) in
                if let error = error {
                    print(error)
                    return
                }
                guard let document = document else {
                    print("Document does not exist")
                    return
                }
                do {
                    let city = try document.data(as: City.self)
                    print("City: \(city)")
                } catch {
                    print("Error decoding city: \(error)")
                }
            }
    
[/code]


CC BY-SA 4.0
2021-12-15T00:57:17.910
84141
2021-12-15T00:57:17.910
13972
84114
answer