web-dev-qa-db-ja.com

なぜ「スロー」はSwiftでタイプセーフではないのですか?

Swiftでの最大の誤解は、throwsキーワードです。次のコードを検討してください。

func myUsefulFunction() throws

どのようなエラーがスローされるのか、実際には理解できません。私たちが知っている唯一のことは、それがいくつかのエラーをスローするかもしれないということです。エラーが何であるかを理解する唯一の方法は、ドキュメントを参照するか、実行時にエラーを確認することです。

しかし、これはSwiftの性質に反するのではないでしょうか。 Swiftは、コードを表現力のあるものにする強力なジェネリックと型システムを備えていますが、関数を見てもエラーについて何も得られないため、throwsは正反対であるように感じます署名。

どうしてこんなことに?または、私は重要な何かを見逃し、コンセプトを誤解しましたか?

47
Noobass

選択は、意図的な設計の決定です。

Objective-C、C++、およびC#のように例外のスローを宣言する必要がない状況では、呼び出し側はすべての関数が例外をスローすることを想定し、ボイラープレートを含めて、発生しない可能性のある例外を処理するか、または例外の可能性を無視してください。どちらも理想的ではなく、スタックがほどかれたときにコールスタック内のすべての関数がリソースの割り当てを正しく解除したことを保証できないため、プログラムを終了したい場合を除いて、2番目は例外を使用できなくなります。

もう1つの極端な例は、あなたが提唱したアイデアであり、スローされる例外の各タイプを宣言できるということです。残念ながら、人々はこの結果に異議を唱えているようです。つまり、さまざまな種類の例外を処理できるように、catchブロックが多数あるということです。したがって、たとえば、JavaではExceptionをスローし、状況をSwiftまたはそれよりも悪い状況と同じように減らし、チェックされていない例外を使用するため、問題を無視できます。 GSONライブラリは、後者のアプローチの例です。

解析の失敗を示すために、チェックされていない例外を使用することを選択しました。これは主に行われます。通常、クライアントは不正な入力から回復できないため、チェックされた例外をキャッチするようクライアントに強制すると、catch()ブロックでずさんなコードになります。

https://github.com/google/gson/blob/master/GsonDesignDocument.md

それはひどく悪い決断です。 「こんにちは。独自のエラー処理を実行することは信頼できないため、代わりにアプリケーションがクラッシュするはずです。」.

個人的には、Swiftが適切なバランスを得ることができると思います。エラーを処理する必要がありますが、それを行うために一連のcatchステートメントを記述する必要はありません。さらに進んだ場合、メカニズムを破壊する方法を見つけてください。

設計決定の完全な根拠は https://github.com/Apple/Swift/blob/master/docs/ErrorHandlingRationale.rst にあります

[〜#〜]編集[〜#〜]

私が言ったことのいくつかに問題を抱えている人がいるようです。だからここに説明があります。

プログラムが例外をスローする理由には、大きく分けて2つのカテゴリがあります。

  • プログラムの外部環境におけるIOファイルのエラーや不正な形式のデータなど)の予期しない状態。これらは、アプリケーションが通常処理できるエラーです。たとえば、ユーザーにエラーを報告したり、彼らが別の行動方針を選択できるようにする。
  • Nullポインタや配列バインドエラーなどのプログラミングエラー。これらを修正する適切な方法は、プログラマーがコードを変更することです。

2番目のタイプのエラーは、プログラムのデータが破損していることを意味する可能性のある環境に関する誤った想定を示しているため、一般的には検出されません。安全に続行する方法がないので、中止する必要があります。

通常、最初のタイプのエラーは回復できますが、安全に回復するには、すべてのスタックフレームを正しく巻き戻す必要があります。つまり、各スタックフレームに対応する関数は、呼び出す関数が例外をスローしてステップを実行する可能性があることを認識している必要があります。例外がスローされた場合に、たとえば、finallyブロックまたは同等のものを使用して、すべてが一貫してクリーンアップされるようにします。コンパイラーが例外を計画するのを忘れたことをプログラマーに伝えるサポートを提供しない場合、プログラマーは常に例外を計画するわけではなく、リソースをリークするか、データを矛盾した状態のままにするコードを記述します。

Gsonの態度が非常に恐ろしいのは、解析エラーから回復できないと彼らが言っているからです(実際、さらに悪いことに、解析エラーから回復するためのスキルがないと言われています)。これは馬鹿げたことで、人々は常に無効なJSONファイルを解析しようとします。誰かが誤ってXMLファイルを選択した場合、プログラムがクラッシュするのは良いことですか?いいえ、違います。問題を報告し、別のファイルを選択するように依頼します。

ちなみに、gsonの問題は、回復できないエラーに未チェックの例外を使用することが悪い理由のほんの一例にすぎません。 XMLファイルを選択している誰かから回復したい場合は、Javaランタイム例外をキャッチする必要がありますが、どの例外ですか?ええ、Gsonのドキュメントを調べて、それらが正しいと仮定して調べることができますチェックされた例外が発生した場合、APIは予想される例外を通知し、コンパイラーはそれらを処理しない場合に通知します。

23
JeremyP

私はSwiftでの入力エラーの初期の提唱者でした。これがSwiftチームが私が間違っていると私に確信させた方法です。

強く型付けされたエラーは、APIの進化を損なう可能性がある方法で脆弱です。 APIが正確に3つのエラーの1つだけをスローすることを約束している場合、後のリリースで4番目のエラー条件が発生したときに、私は選択肢があります。既存の3に何らかの形で埋め込むか、すべての呼び出し元にエラー処理コードを強制的に書き直させますそれに対処する。これは元の3にはなかったため、あまり一般的な状態ではない可能性があり、特にフレームワークが長期間にわたって広範囲に使用されると、エラーのリストを拡張しないようにAPIに強い圧力をかけます(考えてください:Foundation )。

もちろん、オープン列挙型ではそれを回避できますが、オープン列挙型は強く型付けされたエラーの目的を達成しません。 「デフォルト」が必要なため、基本的には型なしエラーです。

「少なくとも、エラーがどこからオープン列挙型で発生するかはわかっています」とまだ言うかもしれませんが、これは事態を悪化させる傾向があります。ロギングシステムがあり、書き込みを試みてIOエラーが返されます。何を返す必要がありますか?Swiftには代数データタイプがありません(I _() -> IOError | LoggingError_)とは言えないため、IOErrorLoggingError.IO(IOError)にラップする必要があります(これにより、すべてのレイヤーが明示的に再ラップされます。rethrowsが非常に頻繁に発生します。ADTがあったとしても、本当に_IOError | MemoryError | LoggingError | UnexpectedError | ..._が必要ですか?いくつかのレイヤーができたら、いくつかの根本的な「根本原因」をラップするレイヤーを重ねます。対処するために痛々しいほど包まれていない。

そして、あなたはそれをどのように扱いますか?圧倒的多数のケースで、catchブロックはどのように見えますか?

_} catch {
    logError(error)
    return
}
_

Cocoaプログラム(つまり「アプリ」)がエラーの正確な根本原因を深く掘り下げ、それぞれの正確なケースに基づいて異なる操作を実行することは非常にまれです。 1つまたは2つのケースで回復が見られる場合があり、残りはとにかく何もできなかったものです。 (これは、Javaの一般的な問題であり、Exceptionだけではないチェック済みの例外があります。これまでにこのパスを誰も行ったことがないようではありません。私は Yegor BugayenkoのJavaでのチェック済み例外に対する引数 これは、基本的には彼の好みであると主張していますJava正確にSwiftソリューションです。)

これは、強く型付けされたエラーが非常に役立つケースがないということではありません。ただし、これには2つの答えがあります。1つ目は、列挙型を使用して独自に厳密に型指定されたエラーを実装し、コンパイラを適切に適用することです。完璧ではありません(デフォルトのキャッチoutsideがswitchステートメントを必要としていますが、inside)、しかし、あなたが自分でいくつかの慣習に従うならかなり良いです。

第2に、このユースケースが重要であることが判明した場合(そうである可能性がある場合)、かなり一般的なエラー処理を必要とする一般的なケースを壊すことなく、それらのケースに対して後で強く型付けされたエラーを追加することは難しくありません。彼らは構文を追加するだけです:

_func something() throws MyError { }
_

そして呼び出し側はそれを強い型として扱わなければならないでしょう。

最後に、強く型付けされたエラーが役立つようにするには、Foundationがシステムで最大のエラープロデューサーであるため、それらをスローする必要があります。 (Foundationによって生成されたものと比較して、実際にNSErrorを最初から作成する頻度はどれくらいですか?)これはFoundationの大規模なオーバーホールであり、既存のコードとObjCとの互換性を維持することは非常に困難です。したがって、タイプされたエラーは、デフォルトの動作として検討する価値がある非常に一般的なCocoaの問題を解決する上で絶対に素晴らしいものである必要があります。それは少しだけ良くなることはできません(ましてや上記の問題があります)。

したがって、これらのどれも、型なしエラーがすべての場合のエラー処理の100%完全な解決策であると言っているわけではありません。しかし、これらの議論は、Swift今日に行くのが正しい方法であると確信しました。

31
Rob Napier