今回は再び本流の The Basics
に戻ってその Assertions and Preconditions
の項について眺めていきます。引き続きその初歩的なところを見ていくことになりますけれど、着目点は表明と前提条件とのそれぞれの違いみたいなところになりそうです。どうぞよろしくお願いしますね。
今回は参加者の一般参加者を募って、ゆめみの外の人たちも訪れての開催になる見通しです。
—————————————————————————— 熊谷さんのやさしい Swift 勉強会 #193
00:00 開始 01:23 無効な状況を予測可能にする 06:09 コンソールプログラムとアサーション 07:10 通常のアプリで落ちたときの印象 11:12 コンソールプログラムで落ちたときの印象 15:18 コンソールアプリの強制終了は驚きが少ない 15:55 シェルスクリプト的に動かしたときの印象 19:22 最適化と検査タイミング 21:14 クロージング ——————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #193
さて、そろそろ始めていきましょう。
アサーションと前提条件について話しますね。略して表明としてもいいですが、一般的ではないのでアサーションと呼ぶことにしましょう。以前の勉強会で概要について説明しましたが、ランタイムで想定していない状況を検出する仕組みについて話しました。
今日はアサーションと前提条件の特徴を見て、それぞれについて詳しく見ていきます。
アサーションを使用してもコードが完全に安全になるわけではありませんが、予測可能に修正ができるようになります。無効な状態に起因する被害を抑えることができます。たとえば、nil
が入るはずがない場合に入り込んでデータベースに書き込んだ場合などですね。
具体的な例として、データ構造にInt
型のID、String
型の名前、String
型の説明がある状況を考えます。このデータをデータベースに書き込む際には、IDが付与されるまでの間にオプショナルではないフィールドに値を入れたい場合です。この時、アサーションを使うと、未設定のIDに強制的に設定される、予期せぬデータで上書きされるようなことが防げます。
例えば、以下のようにデータベースに新しいレコードを追加する関数を作成する場合です。
func addNewRecord(data: Data) {
assert(data.id == nil, "新しいレコードにはIDが設定されていないはずです")
// データベースにインサートする処理
}
このように、アサーションによって、誤ったデータが渡されないか検証できます。これにより、データの不整合を防ぐことができます。
アサーションは、コードが劇的に変わるわけではありません。しかし、不具合を早期に発見し、後で発生する可能性がある被害を防ぐことができます。特にAPIを提供する側にとって、正確に使えるように導くツールとして有用です。
また、コンソールアプリケーションでは、アサーションの効果がわかりやすいかもしれません。ちょっと試してみましょう。
新しいプロジェクトとして、コンソールアプリと普通のアプリを作成します。例えば、次のように関数を作ってみます。
func divide(value: Int, by n: Int) -> Int {
assert(n != 0, "除数が0になっています")
return value / n
}
AppDelegate
のapplication(_:didFinishLaunchingWithOptions:)
で divide
関数を使ってみましょう。
import MyModule
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 正しい使い方
let result = divide(value: 10, by: 2)
print(result)
// 誤った使い方(アサーションが発動)
let wrongResult = divide(value: 10, by: 0)
print(wrongResult)
return true
}
}
アサーションを使うことによって、意図しない使い方を早期に発見し、デバッグが容易になります。これで、プログラムの品質を向上させることができます。
このように、アサーションを使うと予測不能なデータによる被害を抑え、コードの信頼性を高めることができます。特に、APIの利用シーンやコンソールアプリケーションの開発には有効です。 なので、ここで import MyModule
を行います。そして、「フィニッシュランチング」あたりで divide(dividingBy: 0)
とかを実行すると、示した通りの結果になりますね。アプリに移るかな、実行待ちでしょうか。そうですね、結果はその通りでした。ありがとうございます。一気に落ちてしまいましたね。
逆にアサーションを抜けたら、まさしくその通りです。実行してリリースビルドのほうが分かりやすいですね。リリースの実行は Command + I
だったと思うのですが、プロファイラの設定もちょっと必要かもしれません。しかし、プロファイラは面倒なので、プロジェクト設定の Info.plist
やビルドセッティングでオプティマイズを行います。デバッガで動かしたくないので、それでいいでしょう。
今、Command + I
で実行していますけど、別の画面に出ています。「プレートアップランク」を選んで動かします。あまり丁寧にやらなくても分かることですが、ここで落ちました。今、Command + I
で実行して落ちたのか、それとも他の問題か、とにかくうまくいっていないようです。
とにかく、これがコンソールプログラムだと印象が違うわけです。コンソールプログラムでは、同じようにメインの中で import MyModule
を行い、この中で divide(dividingBy: 0)
を実行します。ビルドができて、実行するとどうなるか。Xcodeだと、実行してもそんなに動作が変わらないですね。 普通のアプリとして動かすときに、デベロッパーアイテムは入っていませんね。ですが、コンソールで実行すると、一連の流れとしてちゃんと動きます。これでアプリはどこにあるかというと、最近表示されなくなってしまいました。プロダクトがこの辺に表示されるときにビルドやアサートテストの話をしていましたが、例えばアサートテストを実行するとき、ちゃんと動いているか確認できますね。
これを実行してビルドをかけてみると、アサートテストが動いていることが確認できます。しかし、デバイディングバイ(除算)によって問題が発生する場合があります。たとえば、アサートテストを動かそうとすると、プログラムがクラッシュすることがあります。コンソールアプリの場合、コマンドラインから操作するので、どこで落ちたかが分かりやすいです。
もしライブラリが読み込めていない場合、適切に動作しません。ライブラリを分けるのは少し面倒です。モジュールを同一レベルに置いてもダメな場合は、rpath
(ランタイムライブラリサーチパス)を設定する必要があります。しかし、モジュールを認識させる方法は忘れてしまいましたね。ただ、デバッグを行っているときに問題が発生しているなら、それを解決するために少し直感的に動かす方法もあります。
モジュールを適切な場所に配置し直してビルドすると、ライブラリが不要になることがあります。その場合、アサートテストが正しく動作し、期待通りの結果が得られます。普通のアプリでは、クラッシュしたときに驚きが大きいかもしれませんが、コンソールアプリやコマンドラインではそれが分かりやすいです。
さらに、スクリプトを追加してリアルタイムで動かす方法も考えられます。Swiftのコードをコマンドラインで実行し、アサートテストを確認するのも一つの方法です。例えば、swift
コマンドを使うと、コードをその場で実行して結果を確認できます。このように操作することで、より直感的にコードの動作を理解できますね。 このコマンドラインでランタイム・インタープリター的に動かしたときも同じ感じでした。とにかく、ここでやらかしたいです。しかし、こうやってどのファイルのどの環境でアサーションが発生したかがわかるので、修復するときにはコードを直すなど、サクサクと対応できます。それは、別にアプリでもコアアプリでもコンソールアプリでも同じです。コンソールアプリだと、よりサクサクと書いていけるので便利です。
コンソールを狙っている場合、つまりコンソールで使うときには、余計なエラーメッセージを出すなどといったことを考えなくても、エラーが勝手に出て止まってくれるので便利です。このような使い方は、必ずしもいきなり落ちることが問題ではなく、むしろ便利だよという場面もあります。もっと綺麗に出た気もしますが、アサーションだからか、フェイタルエラーだともっと綺麗に落ちてくれるかもしれません。
とりあえずアサーションとプレコンディションの違いについて、これからスライドで見ていくことになります。アサーションとプレコンディションの違いですが、これは検査されるタイミングが違います。
タイミングの違いと言えば、コンパイルタイムかランタイムかなどを思い浮かべるかもしれませんが、そういう大きな話ではありません。デバッグビルドのときか実運用、つまりリリースビルドのどちらかで検査されるかの違いです。これは非常に有名な話ですが、要するに最適化フラグがあるかどうかで検査されるかが変わるということです。
デバッグビルドだけとかリリースビルドだけとか言っていますが、結局はビルドセッティングスの設定によります。オプティマイズの設定で、オプティマイゼーションレベルを設定します。例えば「-O」をつけるとアサーションはチェックされなくなります。このオプティマイズレベルを下げると、デバッグ時にもチェックされるようになります。
今日は時間になったのでこれくらいにしておきますが、次回はアサーションとプレコンディションの実行タイミングの違いについて、さらに詳しく見ていく予定です。今日はこれで終わりにします。お疲れ様でした。ありがとうございました。