Javaのtry-catch文とは?エラー処理に対応するためのtry-catch文の使い方
Javaのtry-catch文は、プログラム中で例外が発生するか試して(try)、例外が発生したら捕まえて(catch)、何かしらの処理を行いたい場合に使います。
この記事ではtryの用途の一つ「例外処理のtry-catch」を「Javaのエラー処理は良くわからないなぁ…」という人向けに解説します。tryが関係するtry-finally文やtry-with-resources文にも少しだけ触れます。
この記事をお読みになる方としては、Javaの例外について一応の知識があることを想定しています。もし自信がない場合は、お持ちのテキストやWEBの記事であらかじめ予習をしておいてください。
※この記事のサンプルは、Java 10の環境で動作確認しています。
目次
1.try-catch文は例外にその場で対処する時に使う
一般的なプログラミング言語で言うエラーは、Javaでは「例外(Exception)」で表現されます。Javaでは、例外が発生しうる箇所で、何かしらの対処を必ず行わなければコンパイルエラーとなってしまいます。
そんな例外に対処するには、以下の二つの方法があります。
- その場でエラーへ対処する→try-catch文を使う
- その場では例外へは対処せず、処理を中断して呼び出し元に任せる→メソッドのthrows節を使う
でも、throws節を使って例外への対処を呼び出し元のメソッドに任せても、結局そちらで同じ二択を迫られます。ですから、Javaではどこかで誰かが必ず例外へ対処するようになっている、ということです。
このように、Javaのエラー処理の特徴は、発生しうる例外への対処を構文上で強制されることです。C言語などでは関数の戻り値や広域変数でエラーの発生有無をチェックしますが、チェックはあくまで任意なので記述し忘れることがあります。例外は、そんな忘れがちなエラー処理を構文上で必須にしたものです。
また、メソッドの戻り値はメソッドが本来戻すべきモノだけに単純化し、エラーは例外として別のオブジェクトで表現する、というプログラミングスタイルでもあります。戻り値に処理結果とエラー有無が組み合わさった形式だと、呼び出し側で結果を解釈する処理が別に必要となってしまうのです。
2.try-catch文の書き方・使い方
2-1.try-catch文の基本構文
try-catch文はtryブロックとcatchブロックを繋げたものです。ブロックとは{}で囲まれた範囲を指すプログラミングでの用語です。
tryブロックでは例外が発生しうる処理を書きます。もしtryブロック内で例外が発生したなら、Javaがその例外を捕まえて、catchブロックに処理を移します。catchブロック内には補足した例外への処理を書いておきます。
catchブロックの変数宣言部分では、ThrowableあるいはThrowableを継承したクラス・インターフェイスだけを型に指定できます。普通のクラスを指定してもコンパイルエラーになります。catchブロックは例外を処理するためにある、例外専用のブロックだからです。
try { // 例外が発生しうる処理 } catch (捕まえる例外の型 例外の変数名) { // 例外発生時の処理 }
例として、ファイルの読み込み時に発生する例外へ対処してみます。java.io.FileReaderのコンストラクタでは例外FileNotFoundExceptionが発生しうると、コンストラクタのthrows節で示されています。ですので、これにtry-catch文で対処するなら以下となります。
// FileReader.readのコンストラクタ宣言(Stringを引数にとるもの) public FileReader(String fileName) throws FileNotFoundException
try { FileReader fr = new FileReader("/A/B/C/D.txt"); // ここで発生しうるFileNotFoundExceptionを… } catch (FileNotFoundException e) { // ここでcatchして… System.out.println("ファイルがありません!!"); // 何かのフォロー処理を行う!! }
ちなみに、例外の変数名はプログラマーの好きにできますが、単に“e”とするのが広く見られるスタイルで、JDKのソースコードでも同じです。eの他にはexも良く見ますが、exceptionのような長い変数名はほとんど見かけません。明確なものへは単純なものにするのがプログラマーの共通見解です。
2-2.例外が発生すると処理は中断される
例外が発生した時点で、tryブロック内の処理は即座に中断されてcatchブロックに処理が移ります。
つまり、以下のプログラムでは、処理の実行順序は①→②→④→⑤となり、③は実行されません。例外が途中で発生しなければ①→②→③→⑤となり、catchブロックの内容(④)は実行されません。
System.out.println("①"); try { System.out.println("②"); if (true) { // ※if文はコンパイルエラーを回避するためで、実際には不要 throw new Exception(); // 例外が発生!! } System.out.println("③"); } catch (Exception e) { System.out.println("④"); } System.out.println("⑤");
このように本来行うはずだった処理の途中で中断されます。ですから、例えばtryブロック内で開いたファイルを閉じるなどの必要な後処理を忘れないようにしなければなりません。そうしないと、いわゆるリソースリークやメモリリークが発生しえます。
一般的にはcatchブロックの中ではリソースの解放処理は行いません。リソースの解放は正常終了した時にも行う必要があるものですが、catchブロックの中で書いてしまっては正常終了時のリソース解放を書き忘れる恐れがあるからです。
そのために、finallyブロックやtry-with-resources文を用いて、処理の正常終了・例外発生に関わらず、リソース解放をします。以下に簡単にですが例を示します。try-with-resources文では、java.lang.AutoCloseableを実装したクラスなら、tryが終わった際に必ずAutoCloseable.close()を実行してくれます。
// finally句を用いる例 FileReader fr = null; try { fr = new FileReader("XXXX.txt"); } catch (Exception e) { // 何かの例外対処処理 } finally { if (fr != null) { // newできていない場合があるのでnullかどうか確認 fr.close(); // closeを実行(※実際にはcloseで発生するIOExceptionもcatchする必要あり) } }
// try-with-resources文を用いる例 // FileReaderはAutoClosableなので、try終了後に必ずcloseされる try (FileReader fr = new FileReader("XXXX.txt")) { // 何かの処理 } catch (Exception e) { // 何かの例外対処処理 }
2-3.複数の例外をcatchしたい時は
try-catch文のcatchブロックへは、catchしたい例外をいくらでも繋げられます。ただし後述する特定の条件を満たすことが必要で、さらに同じ例外は異なるcatchブロックで複数繋げられません。
try { // 例外が発生しうる処理 } catch (捕まえる例外のクラス1 例外の変数名) { // 例外発生時の処理1 } catch (捕まえる例外のクラス2 例外の変数名) { // 例外発生時の処理2 } catch (捕まえる例外のクラス3 例外の変数名) { // 例外発生時の処理3 }
例として、FileReaderのメソッドread()でやってみます。read()は、IOExceptionをthrowします。ですので、FileReaderをnewしてreadする際は、FileNotFoundExceptionとIOExceptionの2つの例外を捕捉しなければなりません。
// FileReaderのスーパークラスInputStreamでの、メソッドreadの宣言 public int read() throws IOException
try { FileReader fr = new FileReader("/A/B/C/D.txt"); // FileNotFoundExceptionが発生しうる int c = fr.read(); // IOExceptionが発生しうる } catch (FileNotFoundException e) { System.out.println("ファイルがありません!!"); } catch (IOException e) { System.out.println("ファイル読み込みに失敗しました!!"); }
ここで、tryブロック中で発生しうるけれどもcatchされない例外があるなら、そのtry-catch文の外で、どこかで誰かに対処してもらわなければなりません。そうしないとコンパイルエラーを解消できません。
// tryブロック内で発生しうるIOExceptionがcatchされていないので、このままではコンパイルエラー!! try { FileReader fr = new FileReader("/A/B/C/D.txt"); // FileNotFoundExceptionが発生しうる int c = fr.read(); // IOExceptionが発生しうる } catch (FileNotFoundException e) { System.out.println("ファイルがありません!!"); }
さて、複数の例外を対象としたtry-catch文は、tryブロック内で発生した例外へのswitch文のようなものだと考えると分かりやすいでしょう。具体的には、以下の例でお分かり頂けるでしょうか。
try { throw new IllegalStateException(); // tryブロック内でIllegalStateExceptionが発生!! } catch (NullPointerException e) { // ここには来ない System.out.println("NullPonterException"); } catch (IllegalStateException e) { // ここに直接来る!! System.out.println("IllegalStateException"); } catch (IllegalArgumentException e) { // ここには来ない System.out.println("IllegalArgumentException"); }
2-3-1.例外の継承関係にはご注意を
ちなみに、前節冒頭の「特定の条件を満たす限り」は、catchしたい複数の例外が継承関係にある場合に現れます。
例外の継承関係で上位にあるものが先にcatchに出てくると、その後のcatchでは下位のクラスを指定できません。そうした場合はコンパイルエラーになってしまいます。
例えば、NullPointerExceptionはRuntimeExceptionのサブクラスですので、RuntimeExceptionを先にcatchしてしまうと、その後ろではもうNullPointerExceptionをcatchできないのです。これがswtichとは違うところですね。
// NG例:スーパークラスを先にcatchしてしまったケース try { throw new NullPointerException(); // NullPointerExceptionがthrowされた時は… } catch (RuntimeException e) { // ここに来る。NullPointerExceptionはRuntimeExceptionのサブクラスであるため System.out.println("RuntimeException"); } catch (NullPointerException e) { // コンパイルエラー!! ここには絶対に来られないため System.out.println("NullPonterException"); }
// OK例:サブクラスを先にcatchするケース try { throw new NullPointerException(); // NullPointerExceptionがthrowされた時は… } catch (NullPointerException e) { // NullPointerExceptionを先に書けば、このcatchに来る。 System.out.println("NullPonterException"); } catch (Exception e) { // NullPointerException以外の例外はここに来る System.out.println("Exception"); }
例外は何かしらの継承関係にあることが多いので、特定のサブクラスを選んでcatchしてその例外だけの処理を行い、それ以外の例外は同じ処理をしたい、という場合は、catchする例外の順序に気を付けなければなりません。
同じことは、ExceptionやThrowableでも言えます。ですから、例外の継承関係を知るためにも、APIドキュメントには目を通しておいた方が良いでしょう。
try { // 何かの処理 } catch (Throwable t) { // これは良いが… } catch (Error e) { // コンパイルエラー!! ErrorはThrowableのサブクラス } catch (Exception e) { // コンパイルエラー!! ExceptionはThrowableのサブクラス } catch (RuntimeException e) { // コンパイルエラー!! RuntimeExceptionはExceptionのサブクラス } catch (NullPointerException e) { // コンパイルエラー!! NullPointerExceptionはRuntimeExceptionのサブクラス }
2-3-2.複数の例外を1つのcatchで捕捉する「マルチキャッチ」
なお、Java 7からマルチキャッチ(multi-catch)という構文が追加されました。複数の例外で同じ処理をすればいいなら、以下のように記述ができます。つまり、catchしたい例外へ「または」と指定するわけです。catchブロックをたくさん縦に並べなくてもいいので、プログラムがすっきりします。
try { // 例外が発生しうる処理 } catch (捕捉する例外1 | 捕捉する例外2 | 捕捉する例外3 例外の変数名) { // 例外は任意の数だけ書けます! // 例外発生時の処理 }
ただし、マルチキャッチを使うと、例外の変数の具体的な型(どのクラスとして扱われるか)は、「または」で繋げた例外の最も共通的な例外クラスとなります。言葉だけでは少しわかりづらいので、例を示します。
以下の例で言うと、NullPonterException、IllegalStateException、IllegalArgumentExceptionで共通なのはRuntimeExceptionのサブクラスだということなので、RuntimeException扱いとなります。ですが、FileNotFoundException と EOFException は両方とも IOException のサブクラスですから、IOException扱いです。
import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; class MultiCatchSample1 { public static void main(String[] args) { try { throwTooManyException(); } catch (NullPointerException | IllegalStateException | IllegalArgumentException e) { // eはRuntimeException扱い System.out.println(e instanceof Exception); // → 常にtrue System.out.println(e instanceof RuntimeException); // → 常にtrue System.out.println(e instanceof NullPointerException); // → trueになるかは状況による } catch (FileNotFoundException | EOFException e) { // eはIOException扱い } } static void throwTooManyException() throws NullPointerException, IllegalStateException, IllegalArgumentException, FileNotFoundException, EOFException { throw new NullPointerException(); } }
この例だと、それぞれの例外には固有のメソッドはないので違いは見えませんが、以下のように固有のメソッドがあった場合は共通となる例外のものしか扱えないということです。
class MultiCatchSample2 { public static void main(String[] args) { try { throwTooManyException(); } catch (SampleException1 | SampleException2 e) { // このケースでは、eはException扱いとなる e.exceptionMethod1(); // コンパイルエラー!! どちらの例外かはコンパイル時には確定できないため e.exceptionMethod2(); // 同上 // こうすることは一応できるが、本末転倒かもしれない… if (e instanceof SampleException1) { ((SampleException1) e).exceptionMethod1(); } else if (e instanceof SampleException2) { ((SampleException2) e).exceptionMethod2(); } } } static void throwTooManyException() throws SampleException1, SampleException2 { } static class SampleException1 extends Exception { void exceptionMethod1() { } } static class SampleException2 extends Exception { void exceptionMethod2() { } } }
2-4.try-catch文をネストさせた時は
try-catch文はネストできます。つまり、try-catch文の内側にもtry-catch文を書けるのです。
内側のtry-catch文でcatchされなかった例外は、外側のtry-catch文でcatchできます。もしネストしているすべてのtry-catch文で対象の例外がcatchされないのなら、メソッドからthrowされることになります(throws節への記述が必要)。
// tryブロック内でネストする場合 try { try { throw new IOException(); } catch (NullPointerException e) { System.out.println("NullPointerException!!"); // ここでIOExceptionをcatchしていなくても… } } catch (IOException e) { System.out.println("IOException!!"); // ネスト元でcatchできる!! }
ただし、外側のcatchブロックやfinallyブロックの中で発生した例外は、そのブロック内で処理するか、メソッドからthrowされなくてはなりません。その時点では、もう外側のtry-catchブロックでは捕捉できないのです。
try { throw new IOException(); } catch (IOException e) { // ↓のcatchブロックやfinallyブロック内で発生した例外は、ここで捕捉されない } catch (Exception e) { // catchブロック内でネストする場合 try { throw new IOException(); } catch (IOException e2) { System.out.println("IOException in catch block!!"); } } finally { // finallyブロック内でネストする場合 try { throw new IOException(); } catch (IOException e) { System.out.println("IOException in finally block!!"); } }
外側と内側のtry-catch文で同じ例外をcatchするようにした場合は、内側のものが優先されます。一つのtry-catch文で処理が完結していれば、外側へは波及しないということです。
try { try { throw new NullPointerException(); } catch (NullPointerException e) { System.out.println("NullPointerException in inner try-catch!!"); // ここでcatchされる } } catch (NullPointerException e) { System.out.println("NullPointerException in outer try-catch!!"); // ここでcatchされる }
なお、メソッドのthrows節を使うということは、メソッドを超えてtry-catch文をネストさせているのだと考えると分かりやすいです。例えば以下だとmethod2→method1の順番に未処理の例外が伝わり、どこかの誰かがcatchするまで続くのです。
// このソース断片はコンパイルには通りません try { void method1() throws IOException { // ⑤結局、method1でもIOExceptionは処理せずthrowする try { void method2() throws IOException { // ③method2ではIOExceptionは処理せずthrowする try { throw new IOException(); // ①IOExceptionが発生!! } catch (NullPointerException e) { // ②ここのtry-catch文ではIOExceptionはcatchされず、呼び出し元へthrowされる } } } catch (IllegalArgumentException e) { // ④ここでもIOExceptionはcatchされず、さらに呼び出し元へthrowされる } } } catch (IOException e) { // ⑥ここのtry-catch文でようやくcatchされた!! }
2-5.catchした例外をthrowしたい場合
一度catchした例外をthrowして、呼び出し元に処理を任せたい場合があります。その場では例外が発生したというログだけ出力したい場合などです。その場合は、catch内で例外をthrowできます。
try { throw new IOException(); } catch (IOException e) { System.out.println("IOException!!"); // 何かの処理をやって… throw e; // 例外の処理自体は呼び出し元か他のtry-catch文に任せる }
当然ながら、throwされた例外へは誰かが対処しなければならないので、さらにこの外側にtry-catch文を書くか、メソッドのthrows節にthrowした例外の型を記述しなければコンパイルエラーになってしまいます。
なお、catchした例外と違う例外をthrowすることもできます。ただし、発生した例外を捨ててしまうと本当に何が発生したかが呼び出し元側で分からなくなってしまうので、新しくthrowする例外には発生した例外の情報を持たせることが普通です。
try { throw new IOException(); } catch (IOException e) { System.out.println("IOException!!"); // 何かの処理をやって… throw new UncheckedIOException(e); // 異なる例外を、発生した例外の情報を持たせて生成し、throwする }
3.try-catch文を上手に活用するために
3-1.例外をcatchして何もしないのは厳禁!
例えば、以下のようなcatchした例外に対して実質的に何も行っていないソースコードを結構見かけます。仕事で作られたソースコードでも残念ながら見かけます。これは「例外を握り潰す」と言われることがあるパターンで、大変危険なプログラムです。
try { // 例外が発生しうる処理1 } catch (Exception e) { // catchはしているが、何もしていない!! }
try { // 例外が発生しうる処理1 } catch (Exception e) { e.printStackTrace(); // catchした例外でprintStackTraceしているだけ!! }
なぜ危険なのかと言うと、例外が発生していることが誰にも分からないからです。例外が発生した後も、プログラムは何事もなかったかのごとく平然と動き続けてしまうのです。後者も、標準エラー出力をログに出力していればまだいいのですが、捨ててしまっていたなら前者と同じです。
こういうプログラムがまかり通っているプロジェクトでは、エラー対応が後手に回ったり、問題が致命的なレベルになって初めて、別の経路で問題が発覚したりします。そして「何が起きているのかわからない」という状況になるのです。
プログラミングの経験が浅かったり、仕事の納期が近かったりすると、とにかくコンパイルエラーをなくして早く動かしたい一心でこういうことをする人がいます。ですが、急がば回れと言う言葉もあるように、例外へはきちんと対処すべきなのです。そうしないと後から苦労することになります。
3-2.【中級者向け】RuntimeExceptionやErrorのcatchは必要な時だけ!
例外の一種であるRuntimeExceptionやErrorは、必要性がない限りはcatchしないのが普通です。
JavaでのThrowable関連のクラス階層は以下のようになっています。最上位にインターフェイスThrowableがあり、そのサブクラスにExceptionとError、ExceptionのサブクラスとしてRuntimeExceptionがあります。実際の例外は、これらのどれかから継承して作られたものです。
Throwable → 全ての例外・エラーのスーパークラス
┗Exception → catchされるべき例外のスーパークラス
┗RuntimeException → 非チェック例外のスーパークラス
┗Error → 通常のプログラムではcatchすべきでない重大なエラー
この中で、メソッドのthrows節にある場合に対処が必要なのはThrowableとExceptionの二種類です。逆に言うと、RuntimeExceptionとErrorはthrows節に明示的に書いてあっても、try-catchの必要はありません。RuntimeExceptionやErrorはどんなものが実際にthrowされるかは分からない、ということでもあります。
public class TryCatchThrowsSample { // このメソッドにはthrows節はないので、この中で例外へ対処しなければならない void throwsTest() { throwsThrowable(); // コンパイルエラー!! Throwableへは何かしら対処が必要 throwsException(); // コンパイルエラー!! Exceptionへは何かしら対処が必要 throwsRuntimeException(); // RuntimeExceptionは対処しなくてもいい throwsError(); // Errorは対処しなくてもいい } void throwsThrowable() throws Throwable { throw new Throwable() {}; } void throwsException() throws Exception { throw new Exception(); } void throwsRuntimeException() throws RuntimeException { throw new RuntimeException(); } void throwsError() throws Error { throw new Error(); } }
Javaでの決め事として、RuntimeExceptionやErrorは対処不能なエラーを表現します。つまり、環境不備やプログラムミスなどが原因であり、発生しても明示的なリカバリ処理をするべきではないエラー、という扱いです。きちんと環境構築やテストを行っていれば、実運用上では発生しないはずのものだからです。
ですので、RuntimeExceptionやErrorに対して、try-catch文で明示的に補足して処理をすべきケースは少ないと言えるでしょう。ただし、いかなるエラーが発生してもずっと動き続けなければならない種類のプログラムならcatchが必要です(WEBサーバなど、コンピュータに常駐する種類のものです)。
一方、Exceptionはプログラムミス等ではなく、通常の処理上で発生しえて、何かしらのリカバリ処理を呼び出し側が行うべきエラーを表現するのに使います。ですから、Exceptionは対処が構文上も必須になっているのです。これにより、発生しうる例外への対応を呼び出し側へ強制できるのです。
ただ、メソッドのJavadocやthrows節でRuntimeExceptionやErrorが発生すると明示されている場合は、そういうことが起こり得るということは理解しておきましょう。実際に発生した場合の対処がやりやすくなるからですし、そういうエラーが発生しないような予防措置もできるからです。
3-3.【中級者向け】tryブロックの範囲はなるべく狭くしよう!
3-3-1.例外が発生する・しない処理が混在する場合
例外が発生しうる処理がプログラム中に複数あり、例外が発生しない処理と混在している場合は、どちらが良いtry-catch文の書き方でしょうか。理由も含めて、少し考えてみてください。
try { // 例外が発生しうる処理1 // 例外は発生しない処理 // 例外が発生しうる処理2 // 例外は発生しない処理 // 例外が発生しうる処理3 } catch (SomeException1 e) { // 例外発生時のフォロー処理1 } catch (SomeException2 e) { // 例外発生時のフォロー処理2 } catch (SomeException3 e) { // 例外発生時のフォロー処理3 }
try { // 例外が発生しうる処理1 } catch (SomeException1 e) { // 例外発生時のフォロー処理 } // 例外は発生しない処理 try { // 例外が発生しうる処理2 } catch (SomeException2 e) { // 例外発生時のフォロー処理 } // 例外は発生しない処理 try { // 例外が発生しうる処理3 } catch (SomeException3 e) { // 例外発生時のフォロー処理 }
どうすべきかはプログラマーにより意見は分かれますが、一般的には「tryブロックのスコープはなるべく小さくすべき」と言われます。実際は前者と後者の折衷案的な広さとなることが多く、その上で後者に近いスタイルが良いとされているでしょうか。
tryブロックの範囲が広すぎると、例外が発生しないところも含まれてしまい、どの処理に対する例外処理なのかが不明瞭になります。ただ、一つ一つの処理にいちいち個別のtry-catch文を使っては、ソースコードがとても長くなりますし、見通しも非常に悪くなります。何事も極端すぎてはいけないのですね。
ですので、あいまいな言い方になりますが、現実的には処理のまとまりが良い単位だったり、これらの処理は一塊にして処理結果を保証しなければならない…というような単位とすると、見た目上も処理上も、良いバランスのプログラムになるでしょう。
3-3-2.ループの中で例外が発生する場合
また、以下のループ処理内で例外が発生しうる場合への対応は、どちらがより良いtry-catch文の書き方だと思われますか?
try { for (int = 0; i < XXXX; i++) { // 例外が発生しうる何かの処理 } } catch (Exception e) { // 例外発生時のフォロー処理 }
for (int = 0; i < XXXX; i++) { try { // 例外が発生しうる何かの処理 } catch (Exception e) { // 例外発生時のフォロー処理 } }
この場合はプログラムの仕様によります。例外発生時にfor文自体を中断させるのか、それとも処理を継続すべきなのかの仕様が明確になっていなければ、どちらが良いかの判断はできないからです。
for文を中断させるなら前者かもしれません。プログラム上も、例外が発生した時にはfor文が終了することが明確、かつ構文レベルで確実だからです。後者のスタイルでやるならcatchブロック内でbreakするのですが、意図が少々伝わりづらいでしょうか。それにbreakを書き忘れたらそのまま動いてしまいます。
後者は、例外発生時にはフォローして、処理自体は継続しなければならないケースに適しているでしょうか。前者のスタイルでやるなら、再度for文を続きから実行しなければなりませんが、さすがにそのようにプログラムを作りたいとは思えませんし、意味の分かりづらいプログラムになってしまいます。
なお、for文などの中でtry-catchを繰り返すのは、実行速度に全く影響が出ないわけではありません。でも、それが目に見える形で現れることはほぼないでしょう。ですから、そのプログラム中で例外発生時にはどう動くべきか…という仕様をまず明確にして、それに応じて使い分けましょう。
4.まとめ
この記事ではtry-catch分の基本をお伝えしました。tryブロックで例外が発生しうる部分を指定し、catchブロックで発生した例外への処理を行うのが基本パターンです。
例外のcatchの仕方は、過去の構文では1つのcatchブロックに1つの例外しか指定できませんでしたが、今では複数の例外をcatchできるマルチキャッチがありますので、クラスの継承関係に気を付けた上で、積極的に活用しましょう。
プログラムを作る上では、エラー処理は絶対に欠かせません。そして、プログラムは正常系の処理よりもエラー系の処理の分量の方が多いものです。この記事でお伝えしたことを活用して、エラー処理にも気が配られた良いプログラムを作れるようになっていただければ幸いです。
コメント