今回はこれまで数回に渡って話した Error Handling
について、資料に沿ってその具体例を再確認しつつ、その中で エラーからの復帰
に焦点を当てて眺めてみていけたらいいなと思ってます。どうぞよろしくお願いしますね。
今回は参加者の一般公募はなかったので、ゆめみ社内の人たちのみでの参加になる見込みです。
————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #182
00:00 開始 00:14 今回の展望 00:52 エラー送出の基本 01:28 エラーからの復帰とと実行時エラー 01:43 オプショナルによるエラー表現の特徴 02:07 異常系を表現可能 02:21 エラーの伝播について 02:56 包含スコープとは? 04:19 入れ子にしたスコープ? 並列に連ねたスコープ? 04:46 改めて、包含スコープとは? 05:33 静的スコープと動的スコープ 05:58 静的スコープとは 07:22 動的スコープとは 09:13 Perl 言語で確かめてみる 10:53 Perl における動的スコープの様子 12:22 関数スコープ 14:13 JavaScript のスコープについて 15:32 動的スコープは制御が難しそう 17:34 エラーの伝播についての復習 17:54 幾つかのエラーの可能性を想定 18:16 関数名の冠詞が気になる 19:25 エラーハンドリングの例 19:58 例題のコード表現で気になる細かなところ 22:30 エラーからの復帰の話はまた次回 24:27 関数スコープと変数の巻き上げ 26:31 クロージングと次回の展望 —————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #182
では、始めていきましょう。今日もエラーハンドリングについてのお話になりますが、一通りエラーハンドリングの基本はおさらいできているかなという気はします。ただ、スライドにまだ話していなかった部分が残っているので、スライドを見ながらおさらいしつつ、コードの具体的な組み立て方、特にエラー発生後のリカバリーについてお話しできたらいいなと思います。
前回も最後に簡単にラップアップしましたが、もう一度ざっと復習しましょう。まず、「異常状態」というのは、正常に動作できなくなったときに発生するもので、これをエラーとして定義します。エラーは呼び出し元が補足して適切に対応することで、プログラムを継続させたり、終了させたりすることが可能です。
異常をそのまま放置するのと、異常を正常に戻すのでは大きな違いがあります。フェイタルエラー(致命的なエラー)はランタイムエラーとしてプログラムを終了させますが、通常のエラーハンドリングではその異常を正常な状態に戻そうと試みます。一方、オプショナル型もエラー処理に利用されますが、それはあくまで正常系の中で使うものです。
エラーハンドリングの最大のメリットは、異常状態を適切に処理できる点です。呼び出し元は try
を使ってエラーを補足し、do-catch
ブロックでエラーを捕捉して対応します。エラーが捕捉されるまで自動的に現在のスコープの外へと伝播します。具体的な例がスライドにありますが、これはスコープの外でエラーが伝播する例です。
スライドに書かれている「フォーガンスコープ」という言葉は初めて聞いたので、調べてみる必要がありますが、ここでは do-catch
ブロックがエラーハンドリングのスコープを形成しているということですね。do-catch
ブロックは特異なスコープを持っており、エラーが発生した場合、そのスコープ内でエラーを捕捉します。
では、次にいきましょう。エラーが伝播される場合、適切に補足される必要があります。このエラーハンドリングの仕組みを理解することが重要です。コード例もありますので、具体的なエラーハンドリングの方法を学びましょう。 きっと、感覚的な話だとよく分からないかもしれませんが、一応もう一度手元で調べてみましょう。「コンティーニング スコープ」で検索すると、何かしらの情報は引っかかるのですが、必ずしも調べたいこととは一致しない気がしますね。
例えば、「コープ関数」など、関係ないものも出てきたりします。こういったときに静的スコープと動的スコープの2種類が存在するという話を思い出しました。Swiftは多分、静的スコープの言語ですね。
静的スコープとは、コードで表現されているスコープのことです。たとえば、変数s
があり、そのブロックの中でlet t = 2
とすれば、そのスコープ内ではt
が使えます。同じように、このスコープの中でもt
を使うことができます。ただし、関数内の別のスコープに切り替わった場合、その中で変数b
は使えないという感じです。関数が違えばスコープが異なるため、例えばクロージングオーバーのように元のスコープを参照できるものもありますが、通常は自分の直近スコープから辿ることしかできません。
一方で、動的スコープというのも存在します。Perl言語を使っていた人は馴染み深いかもしれません。例えば、Perlでは関数があって、その中で変数$var
を使うとします。動的スコープの場合、その変数は呼び出し元の環境を参照します。以下にPerlの例を示します。
sub something1 {
my $var = 100;
something2();
}
sub something2 {
print $var;
}
something1();
このコードでは、something2
が$var
を参照してプリントするため、$var
=100と表示されます。ここで、例えばsomething1
の中でローカル変数として$var
を宣言すると、呼び出し元と異なるスコープが適用されるため、この変数は参照できなくなります。
ちょっとこれを実際に動かしてみましょう。以下のようにPerlのコードを実行すると:
sub something1 {
my $var = 100;
something2();
}
sub something2 {
print "$var\\n";
}
something1();
このようにして実行すると、$var
が未定義なので何も表示されません。動的スコープと静的スコープの動きを比較すると分かりやすいですね。
また、この動的スコープの概念はJavaScriptでも似たような扱いがされています。実際の実行時にどう変数が参照されるのかを意識しながらプログラムを書くことが重要です。 変数を定義する場合に使う var
や let
について説明しましょう。JavaScriptでは変数を3つの方法で宣言できますが、ここでは特に var
について話します。
たとえば、以下のように var
で変数を定義するとします。
var exampleVar = 10;
var
は関数スコープを形成します。これは、どこで変数を宣言しても、関数全体でその変数が使えるということです。具体例としては、以下のようになります。
function exampleFunction() {
var exampleVar = 10;
if (true) {
var exampleVar = 20;
console.log(exampleVar); // ここでは 20 が出力されます
}
console.log(exampleVar); // ここでも 20 が出力されます
}
この例では、if
ブロック内で再び var
を使って変数を宣言していますが、関数スコープのため、どちらも同じ変数として扱われます。
さらに、メモリが関数の先頭で確保されるため、以下のように先に変数を使用してもエラーにはなりません。
function exampleFunction() {
console.log(exampleVar); // undefined が出力されます
var exampleVar = 10;
}
これは、変数の「ホイスティング」という特性です。var
で宣言された変数は、関数の先頭に持ち上げられるため、宣言前にでも使用可能で、未初期化の場合は undefined
になります。
一方で、let
と const
はブロックスコープを持ちます。例えば、以下のように let
で変数を定義すると、そのスコープはブロック内に限られます。
function exampleFunction() {
let exampleLet = 10;
if (true) {
let exampleLet = 20;
console.log(exampleLet); // ここでは 20 が出力されます
}
console.log(exampleLet); // ここでは 10 が出力されます
}
この例では、let
によって変数がブロックごとに異なるため、異なる出力が得られます。また、const
も同様にブロックスコープを持ち、変更不可(再代入不可)な変数を定義するために使われます。
こうした言語の特性を理解することで、コードの挙動を予測しやすくなります。特に複雑なスコープを持つ大規模なコードでは、この違いが大きな影響を与えることがあります。 こういったところで、ちょっとスコープの話に脱線しましたが、この「コンティニングスコープ」ってなんでしょうね。とりあえずネストされているものかなという感じがします。あとエラーの伝搬については前回お話ししたので、それで良いかなと思います。今回も話していると同じ感じになっちゃうんで、エラーの伝搬については、定義通りに伝搬していくことが大きなメリットですね。
次にキャッチの話をします。さまざまなエラーに対して処理を行っていくという話です。このエラーの場合はこういうエラーハンドリング、この場合はこういったエラーハンドリングを行うという感じですね。
すごい余談なんですけど、この関数名に makeSandwich
って書いてあるじゃないですか。ちょっと不自然じゃないですか。いくら英文に沿って関数名を定義すべしというAPIデザインガイドラインに従うとしても、makeSandwich
って作らないかなと思うんですけど。まあ、これは余談ですけどね。もしこの命名が正しいという意見があればぜひ教えてほしいですけど、多分いらないんじゃないかなと個人的には思います。ネイティブな方はどうなんでしょうね。気になりますよね。
AppleのAPIでこんな感じの関数があったかなと思いますが、Appleが全てではないので他のネイティブな人がどう感じるかです。確かに読み難いですよね。この関数では、makeSandwich
関数は綺麗なお皿が存在しないときにエラーを返すように見えます。正確には、綺麗なお皿がないか材料が不足しているときにエラーを返すようになっています。
この例が完全に、こういったことを大切にするノリで作られている感じでしょうか。いろいろ突っ込みどころがありますよね。makeSandwich
関数がサンドウィッチを返していないとか、サンドウィッチがグローバル変数に入っているとかね。あまりやりたくない作りだなとは思いますが、こういう例題って面白いですよね。
なぜリターンしなかったんだろう、サンドウィッチ型をね。多分これはSwiftガイドラインやAPIガイドラインの例じゃなくて、あくまで日常の手続きを再現するための例なのかもしれません。
Swiftプレイグラウンドのゲームも同じ感覚で作られていると思います。だから、サンドウィッチを作って食べるというプログラムっぽくないコードになっているんでしょうね。不思議な名前付けですが、こういったスタイルのときには有りなのかもしれません。
また、プログラミングの学習者向けに、こういう手続きを使ってますよというイメージを示すための例かもしれませんね。英語さえ読めれば理解できますが、サンドウィッチを作って食べるための一連の流れを表現している感じが出ていますよね。 この関数は、サンドウィッチを作る際に全ての材料が揃っている場合には正常に動作しますが、もし材料が揃っていなければエラーが発生します。このため、関数はトライブロックで囲むことによって、どんなエラーが発生しても対応できるようにしています。具体的には、サンドウィッチが正常に作れればそれを食べますし、材料が足りなければその材料を買いに行きます。皿がない場合には皿を洗うというふうに、エラーに対応するコードになっています。
エラーハンドリングについてですが、このコードを見てわかるように、単に材料を買って終了するわけではありません。皿を洗って終わるわけでもありません。また作り直すのです。サンドウィッチが完成すれば食べるのはもちろん、リカバリーを意識したコードになっているべきだと思います。このリカバリーのコードを書くのは面白そうですが、時間が足りないです。したがって、これは次回に回そうかと思います。
リカバリーを実際に書いてみると、いくつかの方法が考えられます。設定される状況次第でリカバリーの方法も変わるでしょうし、もう一度作り直すのかどうかも考えなくてはなりません。エラー処理の対応の仕方はさまざまな発想があり、複数の想定が可能だと思います。そのため、じっくりと時間をかけて見ていきたいところです。
次に、JavaScriptの変数宣言に関する話題ですが、変数の巻き上げ、いわゆる「ホイスティング」について言及しました。変数の巻き上げは、変数宣言がスコープのトップに移動される動きを指します。これは、コンパイラーやメモリ管理の観点からメリットがあると言えるでしょう。
ホイスティングという用語についてですが、確かフォートランもそうだったかと思いましたが、正確には覚えていません。また、ベーシックは全てグローバル変数だったため、巻き上げはありませんでした。このように、ホイスティングはコンパイラーの動作に関連した用語であり、知っておくと便利です。
最後に、今日の勉強会はこれで終了です。次回はエラーハンドリングとリカバリーについてさらに深掘りしていきますので、また参加してください。今日もお疲れ様でした。ありがとうございました。