今回は Error Handling
の 移譲
のところから眺めていきます。一般によくある例外処理と比べてどんな違いがあるのか、そこからどんな効果が期待できるのか、そんなふうにして眺めてみると前回の話を踏まえて Swift におけるエラー処理の特色が窺えてくる気がします。
今回は参加者の一般公募はなかったため、ゆめみ社内の人たちのみでの参加になりそうです。どうぞよろしくお願いしますね。
—————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #181
00:00 開始 00:32 前回のおさらい 01:13 エラーを送出するからには理由で区別したいかもしれない? 03:12 API を使った側がエラーの内容を再定義するのに支障がありそう? 03:57 エラーを文字列で表現しても意味をなさない可能性 07:50 型としての表現範囲が広すぎる可能性 10:32 文字列をエラーとして使うことへの所感 10:47 影響範囲も気になるところ 13:59 エラーの移譲 16:08 移譲しないときは対応必須 16:29 エラーが起こる可能性を可視化 18:15 メインルーチンでのエラー処理 19:49 エラーを部分的に捕捉するとき 21:28 部分的な移譲の明記について 23:46 catch ブロック内からのエラー送出 24:48 エラーが送出されていく流れ 25:51 正常系と異常系 26:45 クロージングと次回の展望 ——————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #181
はい、今日の勉強会はエラーハンドリングについての話です。前回の続きになりますが、前回はどんな話をしたのかを振り返りましょう。今日はエラー処理そのものについて、前回話しきれなかった部分を中心に見ていきます。
前回のコードを見てみましょう。オプショナルを使う方法とエラーハンドリングを使う方法の違いについて話しました。エラーハンドリングを行うためには独自のエラー型を作成する必要があるため、若干の手間がかかります。しかし、その手間によってしっかりと意味を持たせることができます。また、面白いアイデアとして、ハンドリングを拡張して文字列そのものをエラーメッセージとして扱う方法も紹介しました。
例えば、C言語でよくある方法では、戻り値を int
型にしてOSのステータスコードやエラー番号を返す方法があります。以下のようなコードです。
int status = function();
if (status == ERROR_CODE) {
printf("Error: %d\\n", status);
}
同様のアプローチで、Swiftでもエラーメッセージを直接文字列として扱うことができます。しかし、この方法は実際の運用で少し難があると感じています。エラーの理由を細かく判断して処理することはあまりないようですが、それが必要な場合は通常のエラー型を返すべきです。
具体的には、以下のようにエラーハンドリングを行うことが可能です。成功か失敗かだけを返し、その結果を受け取った側でどのように扱うかを決めます。
func stringToInt(_ str: String) throws -> Int {
guard let intValue = Int(str) else {
throw StringError.invalidString
}
return intValue
}
enum StringError: Error {
case invalidString
}
このように throws
を使ってエラーを投げることができます。受け取る側でそのエラーを処理することで、意味のあるエラーメッセージに変換できます。
エラーメッセージをどう設計するかはアプリケーションの目的や使用 context によりますが、単純にエラーを伝えるだけで良いときもあります。その場合、エラーメッセージの内容が具体的でないと、受け取る側で再度エラー処理を行う必要が出てしまいます。
例えば以下のようにエラーハンドリングを行い、受け取ったエラーメッセージを特定のメッセージに変換します。
do {
let result = try stringToInt("123")
print("Conversion successful: \\(result)")
} catch StringError.invalidString {
print("The provided string could not be converted to an integer.")
} catch {
print("An unknown error occurred.")
}
このようにして、エラーを中央で管理し、その内容を適切に処理できます。最終的にはエラーメッセージが具体的であり、ユーザーにも理解されやすくなるようにすることが重要です。
今回の話はここまでです。エラーハンドリングについて、次回もさらに深掘りしていきますので、よろしくお願いします。 APIデザインガイドラインについての話題から始まります。まず、最初に話している内容として「ぼちゃぼちゃしすぎる可能性がある」という点が気になります。また、APIデザインガイドラインの中で特にパラメータの名前付けについて触れており、「ストリング」や「エニー」といった汎用的な型を例に挙げています。
これらの汎用的な型については、ラベルでしっかりと説明を加えないといけないとされています。例えば、エラーハンドリングでストリングのような汎用的な型を使うことについてです。受け取る側がエラーをキャッチしたときに、そのエラーが何者なのかがわからなくなるという問題があるようです。
ここで具体的に示される例としては、「トライキャッチで受けたときに受けるエラーがストリング型になる」という状況です。もしエラー型がちゃんと定義されていれば、それぞれのエラーについて詳細な情報が得られるため、最終的には安全です。しかし、ストリング型を使うとこの情報が失われることになります。
次に、汎用的な型を拡張する際の影響範囲について触れています。「影響範囲が広すぎる」とのことで、それがどのような悪影響を呼ぶかも考慮する必要があります。具体的な心配事としては、例えばエラーローカライズディスクリプションが適切でない場所で呼ばれてしまう可能性があるという点です。
さらに、この心配を少し減らす方法として、ローカライズディスクリプションをエクステンションする際に、ファンクションを味付けしておくという意見も出ています。でも、これもやりすぎると「無理やりやっている感」が強くなってくるとのことです。
ここでのポイントは、やみくもにストリングを拡張するのは避けたほうが良いということです。例えば、ストリングのエクステンションはどこかしらで行われていると思われますが、その際には非常に注意が必要です。具体例として、ローアーケースなどのケースが挙げられました。
総じて、このセッションの中心テーマは、APIデザインにおける汎用的な型の使用とその拡張についての注意点です。特にエラーハンドリングの際にどのように情報を伝えるか、そして拡張する際の影響範囲と慎重さが重要であることが強調されています。 お話ししますと、今日はエラー処理についての内容です。プログラミングでは、エラーを適切に処理することが重要です。Swiftでは、エラーを返す関数にはthrows
を使用します。例えば、関数からエラーが発生した場合、そのエラーをハンドリングしなければなりません。そのための方法が、try
キーワードを使ったエラーハンドリングです。
基本的には、エラーを返す関数を呼び出す際にtry
を使います。もしエラーが発生した場合は、その場で処理するか、外にエラーを伝播するかを判断します。外側に伝播するためには、関数定義にthrows
を付けます。これにより、エラーが関数内で処理されない場合でも、その外側のスコープでハンドリングされます。例えば、以下のように記述します。
func someFunction() throws {
// 何らかのエラーを発生させる処理
}
この関数を呼び出す際には、try
を使ってエラーハンドリングを行います。エラーハンドリングは、基本的にdo-catch
ブロックで行います。
do {
try someFunction()
} catch {
// エラーを処理
}
ここで、もし関数内でエラーが発生し、その場で処理できない場合は、throws
を付けることで外側にエラーを移譲できます。これにより、エラー処理の責任が呼び出し元に移るため、柔軟なエラーハンドリングが可能になります。
例えば、以下のようにメソッド内でもエラーが発生する関数を呼び出し、エラーハンドリングをする場合を考えます。この場合、エラーが発生しても、その場で処理できない場合には、throws
を使って外側にエラーを移譲できます。
func anotherFunction() throws {
try someFunction()
}
これにより、さらに外側のスコープでエラー処理が行われます。try
の後にエラーが発生した場合、そのエラーを適切にハンドリングする必要がありますが、throws
を使用することでそのスコープを適切に設定できるのは良い点です。
この点を理解すると、エラー処理の流れがはっきり見えてきます。エラーが発生する可能性のある場所が明示されるため、コードの可読性が向上します。一つの関数内でエラーハンドリングをするケースでも、スローズを使わない関数と使う関数の違いを理解することで、エラー処理の設計がスムーズに行えるようになります。
例えば、次のケースを考えます。
func yetAnotherFunction() throws {
do {
try anotherFunction()
} catch {
// エラー処理
}
}
このようにネストされた構造でも、各レベルでエラー処理を行うことが可能になります。
また、Swiftのプレイグラウンドのような環境では、メイン関数内にエラーハンドリングを組み込むことができます。このようなスクリプト実行型のコードでは、エラー処理が簡略化されているため、トライキャッチを使用せずにエラー処理が行える点も便利です。
このように、エラー処理を適切に行うことで、信頼性の高いコードを書くことができます。それにより、プログラムの予期しない終了などを防ぐことができます。Swiftにおけるエラーハンドリングの基本とその重要性を理解し、実際のコードに適用していくことで、さらに良いプログラムを作成していきましょう。 とりあえず、算数の処理については全てエラーハンドリングを行って何かをするということになります。距離を考慮している際に、このコードをパスすることがあるかもしれませんが、それが少し怖いと感じることはありませんか。移動しているつもりでも、仮に移動が全くない場合でもエラーが外に拾われずに全て投げられる可能性があります。
個人的には、この状況を明確にしたほうが良かったのかもしれませんが、処理が面倒なのかもしれません。他の言語では、エラーが完全に投げられて自動的にキャッチされることがありますが、自分の好みで書いていたときの記憶が曖昧です。このような場合、エラーハンドリングを移動する際に明確にしておくほうが安全だとよく思います。
レビューの際にも、このような明記があれば安心ですが、なければ少し不安に感じることがあります。すべてを網羅しようとしても、その先で新たなエラーが発生する可能性も否定できません。このあたりは少し不安ですが、工夫して移動する方法を考える必要があります。
Swiftでは、throws
を使用してエラーを移動させる仕組みがあります。throws
ブロックの中でキャッチされなかったエラーは上位に移動されます。この仕組みを考えると少し怖いように思うこともありますが、大きな問題が起こったことはあまりありません。
エラーハンドリングの基本として、エラーが発生したときにキャッチされなければ、その呼び出し元にエラーが移動するのが自然です。移動の概念としては、これに尽きると思います。ただし、エラーハンドリングの中でキャッチされ、さらにスローされることもあり得ます。これにより、キャッチブロックの外側にエラーが再度移動されることになります。
さらに、本来のエラー理由(reason)を新しい理由に変えるなど、新しいエラー定義を適用することもできます。この方法は、エラーハンドリングを深く理解するためには重要です。
次回の勉強会でもエラーハンドリングについての具体的なコードの書き方やキャッチの方法についてさらに深掘りしていきます。具体的な例を取り上げて、どのような流れでエラーが処理されるのか、通常の戻り値と異常時の戻り値の使い分けについて詳しく見ていく予定です。
そろそろ時間なので、今日の勉強会はこれで終わりにしますね。お疲れ様でした。ありがとうございました。