引き続き The Basics
の Assertions and Preconditions
について眺めていきます。今回はその用意されている具体的な機能について確認する感じになりそうです。どうぞよろしくお願いしますね。
今回は参加者の一般参加者を募って、ゆめみの外の人たちも訪れての開催になる見通しです。
—————————————————————————— 熊谷さんのやさしい Swift 勉強会 #194
00:00 開始 00:10 今回の展望 02:06 表明と前提条件の使い分け 02:19 自分は fatalError を使いがち 03:26 前提条件の基本 03:43 表明の基本 03:54 最適化を意識して使い分ける 06:44 技術ブログを読むときの心がけ 08:25 表明と前提条件をどう使い分けていくか 09:38 インスタンスの状態を加味した前提条件 10:33 前提条件を満たす責任は呼び出し側にある 11:40 最適化による前提条件の扱い 12:37 表明は内部的な検査 16:13 リリース後はエラーにならないことを期待 17:13 表明とアクセスコントロールの関係性 17:50 内的要因か外的要因か 19:01 配列と辞書に見る価値観の違い 20:20 エラーを伝えるさまざまな手段 21:07 予測可能な終了を可能に 22:50 表明と前提条件で使う関数 24:07 クロージング ——————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #194
では始めていきましょう。アサーションとプレコンディションについての話を続けたいと思います。前回、予告としてWebなどの資料でプレコンディションやそれに関連する違いを詳しく見ていく話をしました。細かく見ていく前に、もう少し具体的な話を見てみたいと思いましたが、やはり今のタイミングで見ていこうと思います。アサーションとプレコンディションについてどのように実際に使われているのか、少し具体例を見てみましょう。
現在、このスライドに示されているように、アサーションとプレコンディションの考え方が説明されています。ただ、これだけでは実際に使う際には少し物足りないかもしれません。デバッグビルドでの使い方や実運用時の使い方など、プログラマーとしては最適化オプションのことも考慮した上で、もっと細かい情報が欲しいですよね。そのため、Webでアサーションとプレコンディションの実行タイミングについてもう少し詳しく押さえておいたほうが良いと思います。
この資料を基に話を進めますが、5年以上経過している資料なので多少古いかもしれませんが、基本的な考え方は大丈夫だと思います。
アサーションとプレコンディションについて、どちらをどういうケースで使うのかを意識しないと効果的に使えません。また、私は以前はフェイタルエラーを使う癖がありました。具体的には条件を if
文などでしっかり書いた上で、その条件に漏れた場合にはフェイタルエラーを使うというスタイルでした。そのため、あまりアサーションとプレコンディションを意識して使い分けることはしていませんでした。そこで、しっかり意識して使えるような材料が欲しいと思っています。
知らない方のために説明すると、プレコンディションは指定された条件を満たさなかった場合にクラッシュさせることができます。これは if
文とフェイタルエラーを組み合わせた場合と似ています。アサートも同様に、条件を満たさなかった場合にエラーをチェックします。しかし、これらの違いはコンパイルオプションによって変わってきます。
具体的には、デバッグビルドのようにオプティマイズをしない設定では、プレコンディションもアサーションも両方とも効果を発揮します。しかし、オプティマイズをした場合、アサーションは無視されるようになり、チェックが行われなくなります。一方、プレコンディションはオプティマイズされても無視されません。この違いにより、パフォーマンスの最適化を意識する際には、どのチェックを使うかが大切になります。
プレコンディションは通常の if
文と違い、パフォーマンスにシビアな部分でのチェックを省略することができます。そのため、プレコンディションやアサーションを適切に使い分けることで、パフォーマンスに影響を与えずにコードの安全性を確保できます。逆に、イフ文でチェックをしっかりと書く場合もありますが、これはパフォーマンスに多少影響が出る可能性があります。
最終的には、どちらを使うかは状況に応じて選択する必要があります。テストによって安全に動作することが保証されている場合は、プレコンディションやアサーションを使ってパフォーマンスの向上を図ることができます。これらの選択肢を考慮しながら最適な使い方を考えていくことは、有意義なことだと思います。 実際にどのように動作するかについては、一旦置いておいて、使い分けについて話しますね。せっかくなので、この辺も少し見ておきます。プレコンディション(事前条件)とアサーションを実際にどう使い分けるかについて、あるブログに記載されています。おそらく、このブログを書いた方の価値観によって使い分ければよいという話になるのでしょう。
このブログの執筆者、コーヒーさんという方は、かなり言語仕様に詳しい方です。この人が言うのであれば基本的にその通りに捉えておけば良いだろうという感じがするので、読み進めていけば大丈夫でしょう。もちろん、どれだけ素晴らしい人でも、その意見に対して自分なりの解釈や意見を持つことは重要です。例えば、「こうは書いてあるけど、こうした方が良いのではないか」という発想を持つことは大切だと思います。
実行上の理由(忙しいからやらない、こっちの方が楽だからやらないなど)は大抵良いことにはなりませんが、理論的に考えて「こうではない方が良い」と感じたら、どんどん試してみると良いでしょう。ただ、基本的にはまずこのブログに書いてあることを学び、それを理解してから自分のトライアンドエラーを進めると良いと思います。
プレコンディションは、その名の通り事前条件・前提条件です。例えば、サブスクリプトにおけるインデックスが範囲外かどうかをチェックしないという状況です。このセクションでは、インデックスが範囲外の場合には例外を投げたいわけです。そのため、「インデックスが範囲内にあること」を前提条件としてチェックするわけですね。
実際の例は以下の通りです。こうすることによって、これから先はその前提条件を満たしている前提でコードを書いていくことができます。これにより、ランタイムエラーを防ぐガードセットのような感じです。
precondition(index >= 0 && index < array.count, "Index out of range")
このように書けば良いのです。プレコンディションは引数だけでなく、メソッドを呼び出す時のインスタンスの状態も前提条件として扱います。そのため、メソッドの機能としては、メソッドの中でメンバーの全体の状態を見てその状態が適切であることを確認する必要があります。
前提条件を満たすべきはメソッドを呼び出す側です。呼び出し側がその条件を破ることができるわけですから、実際にはその状況を想定してチェックを行う必要があるのです。たとえば、引数が空である場合に呼び出されることがあるので、それを前提条件として認識するという感じです。前提条件がいつ破られるかわからないため、省略せずにしっかりとチェックする必要があります。
最適化を行ったとしても、このチェックを意図的に省略しないよう配慮されています。アンチェックドの箇所では、意図的にチェックしないと言われているため、そのまま従う感じです。 アサーション(assert)は、プログラムの内部状態を検証するための手法です。自身の理解がまだ追いついていない部分もありますが、これをしっかり学ぶのが楽しみですね。アサーションが何の役に立つのかというと、内部的な条件のチェックに使います。以前は外部要因でチェックを行っていましたが、内部的なチェックに役立ちます。
例えば、与えられた要素の二乗の和を計算する関数があるとします。この関数で二乗の和を計算する際に、アサーションを使って必ずゼロ以上の値になることを保証できます。アサーションを実装しておけば、間違って条件が満たされなかった場合はすぐに検証できます。
具体的な例を挙げると、関数が二乗の和を計算している場面を考えます。この関数は、計算結果を使って他の処理を行い、最終的な結果を返します。もし計算に何らかのミスがあった場合、計算結果が期待したものと異なり、最終的な値も変になるかもしれません。そこで、アサーションを使っておけば、問題が発生した時点でエラーとして検出できるので、後から問題の原因を探す手間が省けます。
この使い方を理解すると、アサーションの有用性が見えてきます。具体例をもう少し掘り下げると、二乗の和の計算が成功する条件として「ゼロ以上」というアサーションを設けることで、条件が満たされているかどうかをチェックできます。
アサーションは、渡された値にかかわらず常に満たされるべき内部的な条件のチェックに使います。また、ライブラリをリリースする前に十分にテストを行えば、アサーションでエラーが発生することはまずないでしょう。そのような場合、アサーションを使うことで、最適化時にチェックを取り除いてパフォーマンスを向上させることができます。エラーが発生するはずがないという状況で使うと便利です。
内部チェックが十分にテストされている場合、アサーションはプレコンディションをチェックする手段よりも有用な場合があります。プレコンディションは外部から与えられた状態をチェックしますが、アサーションは内部的な条件をチェックします。そのため、それぞれの役割が異なるのです。勉強会でも話に出ていましたが、利用変更での問題を検証するためにアサーションを用いるのは理想的な使い方です。
最後に、アサーションとインデックスが範囲外の場合にエラーを実行する場合についての違いも興味深いですね。ディクショナリ内のキーが存在しない場合のエラー処理など、具体的には異なる状況でのエラーハンドリングが異なる点も面白いです。 実行してエラーの代わりに nil
を返したり、エラーをスローしたりすることも選択肢としてありますね。プレコンディションやアサートだけではなく、広い視点での話です。それをどういうふうな形でエラーとして通知するか、アサートやプレコンディション、nil
を返したり、エラーをスローしたりする方法をトータルで考えると良いでしょう。
この件に関しては、ツイストのエラーの4分類に関連する話で、同じ方が書いたブログを見ていくと、よく理解できるのではないかと思います。このブログもデータエラー周りをもう少し見ていけば、またご紹介できるかもしれません。
エラーを通知する手段として、プレコンディションを使う方法、ディクショナリに nil
を使う方法、そしてロードを使う方法があります。ただ、一般的なライブラリではエラーをスローすることはほとんどありませんので、具体例を挙げにくいのが現状です。このようにエラー通知の手段を選択する中で、プレコンディションやアサートの使い分けについて考えると、十分に使っていけるように思います。
アサートの使い方についてですが、予測可能に使用できることでデバッグを容易にすることができます。この点についても、先ほど参考にしたブログに書いてありました。
ここから具体的な使い方について見ていきたいと思います。いつの間にかスライドが少し戻ってしまいましたが、次のスライドの内容に入っていきます。おさらい程度に押さえておきたいのは、アサートはデバッグビルドだけで使用し、プレコンディションはデバッグビルドとリリースビルドの両方で使用するという点です。ただし、これだけではどう使っていけばいいのかイメージが掴みにくいかもしれません。さっきのブログも併せて考えるとよりよく分かってくると思います。
Nethersuitプログラミングランゲージでは語られていない重要なこともあり、これだけ知っておけばよいというわけではありません。細かい企画については、さっきのブログがおすすめです。
最後に、標準ライブラリにアサートという関数があります。この関数は引数を4つ取ります。第1引数に評価式を渡し、この評価式が真でなかったときにはランタイムエラーが発生し、第2引数で渡したメッセージと共にエラーが通知されます。この関数はすぐに使えますが、第4引数まで取れるようになっているので、そのあたりは次回に詳しく見ていきましょう。
ここまでで何かありますか? 大丈夫そうですね。今日話したことも、これまで何回か紹介してきましたので、今回のお話を踏まえれば理解がより深まったのではないかと思います。
はい、では時間になりそうなので、今日はこれくらいで終わりにしましょう。お疲れさまでした。ありがとうございました。