本日も引き続き The Basics
の Assertions and Preconditions
について眺めていきます。前回からこれらを実現するために実際に使う具体的な機能の定義を確認し始めていて、今回もその続きからじっくりと見ていこうと思っています。どうぞよろしくお願いしますね。
今回は参加者の一般参加者を募って、ゆめみの外の人たちも訪れての開催になる見通しです。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #196
00:00 開始 00:33 なぜ #line が UInt なのかは謎のまま 02:06 コンパイルタイムでの処理を意識している? 02:50 発生箇所がランタイムで決まる可能性 05:34 ファイル名をコンパイル時に決める必要性は? 07:02 #file の主な使い道 08:14 自動処理時にオリジナルの位置を伝えたい場面 08:54 Static String に拘った理由を知りたい 10:30 #file の使い道を再確認 16:17 独自の場所を報告する 17:40 どの位置を通知するのが妥当? 20:02 メッセージをオートクロージャーにすると? 21:43 ライブラリー内部の位置を通知されても困るところ 22:21 オートクロージャーにクロージャーを渡せない 22:33 次回予告とクロージング ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #196
では、今日も引き続きアサートとプリコンディションについての話を進めましょう。前回はアサート関数の定義について見ていましたが、時間内に終わらなかったので今回もその続きを見ていきます。
前回の内容の中で、《#line》や《#file》のリテラルに関して見落としてしまった部分がありましたが、型が自動で解釈されているのはすごいことですね。例えば、Int
ではなくUInt
を使用している部分についても少し話しました。UInt
を使う理由としては、表現範囲がInt
よりも大きいことがありますが、その範囲を超える行数のコードを作ったらどうなるのかという話題になりました。
試しに範囲を超える行数のソースコードを作ってみましたが、結局リソースの問題で実行ができませんでした。つまり、そもそもUInt
の範囲までの行番号を扱う必要はないはずです。APIデザインガイドラインからみても、Int
で十分と思いますが、なぜUInt
を用いているのかは結局まだ分からないままです。
それから、StaticString
を使っている部分についても少し妙に感じました。アサートはランタイムにチェックする機構なのに、なぜスタティックにこだわる必要があるのか疑問です。例えば、ストリングインターポレーション(文字列補間)がランタイムでできないと困る場面もあるでしょう。
具体的な例を見てみましょう。手元のコードをすべて消して、新しい例を試してみます。例えば、《#file》名はコンパイル時に決まると言われていますが、それがランタイム時に動的に決まらない必要があるのか疑問です。動的にファイルを生成する仕組みを考えた場合、コンパイルタイムに反映されるなら問題はないですが、その辺りがどう扱われるのか見てみたいと思います。
例えば、次のようなコードを書いてみます。
for i in 1...10 {
let fileName = "source\\(i).swift"
// ここでアサートがエラーを出すコードを追加する
}
この方法では、StaticString
だからダメというエラーが出るでしょう。スタティックな文字列にこだわることなく、ランタイムで生成されたエラーが出るようにすることも可能か考えてみます。具体的な例が思いつかないのですが、以前にランタイムの動的生成を行った経験があります。
このように、ランタイムとコンパイルタイムそれぞれの特性をどのように活かすか考える必要がありますね。 やったことがあるのはオブジェクト指向の頃の話ですね。もし完全にファイルが固定であれば、そもそもパラメーターはいらないと思いますけど、実際にはいろいろなパネルなどが必要です。基本的にはファイルパラメーターで指定できるということは、アサートとしてレポートするファイルをカスタマイズできる状況ということです。
もしカスタマイズができるとなれば、コンパイル時に決めなければならない理由は特にないのではないでしょうか。特定のソースファイルであれば、必ずコンパイルを通すことがあるので、その時点でファイル名が決まっているはずです。ここでファイル名をパラメーターとして受け取れるというのは、例えばエクステンションでそのアサートに関する何かメソッドを作って、テストでよく使う方法ですが。そのメソッドを呼び出すときにファイルを指定できるのが意味だと思います。
具体例として、あるエクステンションファイルでアサーションメソッドを作って、別のソースファイルから呼び出すときにファイルを渡すということです。これはテスト時によく使われる方法です。例えば、独自のマイアサートのようなモジュールで、「ファイルアクティブストリング=ジョブファイル」という形でファイルを指定して処理することがあります。アサート処理の呼び出し元をちゃんと認識させるためにファイルを指定するということです。
これを引き継げるというのは確かに基本ですが、もっと派手なことができても良さそうです。例えば、動的に多くのファイルをインポートすることなどです。ファイル名に追加情報を持たせるという方法もありますが、それを用いる人がいるかどうかはケースバイケースです。
スタティックストリングにこだわる理由についても考えました。もし、それが「ラップワーニング」のようにコンパイルレベルで機能するものであれば、スタティックストリングを取る理由も納得できます。ただ、アサート関数の定義が少し明確ではないです。例えば「@const」特性が実装された場合、ここは「@const string」になるかどうかも興味深いです。
具体的な例として、エクステンションのArrayに独自のメソッドを追加する場合を考えてみましょう。例えば、addAtIndex
というメソッドで、インデックスを指定して要素を返すようにし、その際アサートを使えるようにします。インデックスが範囲内かどうかを確認し、範囲外であればアサートを出すようにします。これにより、配列に対して安全にアクセスでき、問題があれば適切にレポートされます。こういったアプローチが実際の開発やテストで有用です。 ライブラリで組み込んでアサートを使うことで、コードが分かりやすくなります。たとえば、assert
文の中で書くと良いでしょう。ここでは、アプリクリプをインポートして動作させますが、インポートは不要のようです。
シンボルが見つからないというエラーが発生しているようですが、これはたぶん自分の勘違いかもしれません。試しにArray
をエクステンションしてみて、インデックスでエレメントを返すようにすると、エラーが解消されるか確認します。
時々、単純なミスをしてエラーが出ることもあります。たとえば、オプショナル型とノンオプショナル型の違いに気づかず、コンパイルエラーを発生させてしまうこともあります。Playground
ではなくXcode
で試した場合も似たようなエラーが出ることがあります。
また、アプリケーションクリプトを使わずに、モジュール内で直接assert
を使用することで、エラーの発生場所を特定することができます。これにより、例えばa.swift
の5行目でエラーが発生していると表示されるので、具体的にどの箇所で問題が発生しているかを確認できます。
ビルドフェーズでソースコードを書き換え、コンパイルエラーやアサートエラーの通知を行うことも試みたことがあります。これにより、元のソースコードのどの行でエラーが発生したのかを通知させることができます。ただし、この方法はアサートそのものではなく、エラーチェックの一環として機能します。
自作のアサート関数を作ることもできますが、他からファイル名や行番号を渡すような実装は現実的ではありません。標準のアサート機能を使う方が自然でしょう。アサートはライブラリ提供のものを使い、それが出た箇所が特定できれば十分です。
ファイルのエラーハンドリングは意外と複雑です。しかし、標準出力を使ってエラー報告することで、問題箇所をすぐに特定することが可能です。ファイル操作の有効利用については、まだまだ学ぶべきことが多いと感じています。 ここで、パラメータで対応できても嫌だなと思うことがあります。しかし、Xcodeの補完機能が使えるようになったんですよね。ただ、ここに触れるとファイルとラインが省略可能で存在しています。うん。他の方もやってね、とにかく省略可能を引き継いで状況を出さないようにしてくれますが、そのままだと使いにくい感じですね。使いやすさを求めるような機能ではないですが、アップファイルというのはどうやって背元を取っていくのに加えて、デフォルトパラメータと配信をよく作ることができるという感じの印象です。
これをオートクロージャーにしたらどうなるのか、すごく気になったんですよね。前回はここまでこのファイルで、AfterTypeString
はダメなのかなと思いました。あるいはアプリケーション型、あるいはオートクロージャー。そうそう、貫通する形でね。これでデフォルトでこんなふうにやったとしても、パラメータの段階で展開されるから全然問題ないんですよね。
デフォルトパラメータってそうか、オートクロージャーがこれでいいのか。もしかしてオートクロージャーのデフォルトパラメータを書いたことがないかもしれません。これで実行すればコンパイルが通りますよね。そうですよね、これで実行するとファイルとしてテストが出るでしょうか、『MyPlayground』が出るでしょうかという話です。ちょっとやってみると、『MyPlayground』が出ました。だからオートクロージャーを渡すときに完全にコンパイルタイムで決まるんですよね。つまり、ここでアップファイルが渡るわけですが、これがコンパイル時に決まるということです。具体的には細かい部分が省略されて、こういう形になります。
やっぱりライブラリのファイルやアップファイルがタッチされていても困りますよね。それがさらに階層深くなっていると、特に。そしてランタイムな状態ではディバッガをアタッチしている段階ならいいですけれど、プレコンディションをランタイムリリースに持ち込まれたときにログがそんなに残っていても追跡したくないわけですからね。
また、オートクロージャーとは逆にクロージャーを渡せない場合、相手がいないと渡せないです。意外とそうなんですよね。最近並行処理の都合でゲッターにthrows
を付けられるようになりましたよね。つまり、ゲッターがエラーを返すようになったということです。で、これをオートクロージャーに渡したときにエラーはどちらに投げられるのか問題を試してみたんですよ。ゲッターについてはちょっと記憶が曖昧ですが、オートクロージャーの内部でスローされるのか外側でスローされるのか、これによってrethrows
やスローイングファンクションでないファンクションがスローイングファンクションを取ったりと、解釈が難しくなっている感じがあるんですよね。そこも整理しないといけないですね。次回やってみますか、それ。
そんな感じで、オートクロージャーって意外と振り回される感がありますよね。強制的に値を使わざるを得ない状況が発生するので。それでもゆっくり聞いてみるのも面白いかもしれません。時間も来たので今日はこれぐらいにしましょうかね。次回はさっき言ったオートクロージャーとスローイングファンクションの話をしつつ、今はアサートだけしか見ていないので、エラーハンドリングとタプルのところも見ていこうかなと思います。
今日はこんな感じで勉強会を終わります。お疲れさまでした。ありがとうございました。