実践Flutter

Flutterでスマホを中心にアプリ制作していきます。

実践Dart:例外

はじめに

本記事ではDartの例外の扱いについて述べていきます。

例外は不測の事態が発生したとき、その自体を処理を呼び出した元を誰かが捕捉するまでたどっていく仕組みです。

だれも例外を捕捉して対処しない場合、アプリが落ちて終了ということになります。

こうはならないように、誰かしら責任をもって対処する必要があります。ウェブページを探しに行ったけれども見つからなかった、であるとか重たい処理が時間までに終わらなかった、など様々なケースがあります。基本的にはこうした処理を頼んだ場所で、考えられる例外には対処するようにすれば万全です。





例外の基本

例外とはなにかの処理で不測の事態が起こった場合に、通常の処理の制御フローを超越して呼び出し元に制御を投げ返す仕組みです。

不測の事態とは:

  • 想定した処理が終わらない
  • 不正な値が入ってきた
  • 不正な計算結果になった

などであり、その場でその異常事態を解決できない場合に、その場の仕事を放棄して投げ返します。指定されたファイルが見つからない、サイトにアクセスできない、必須のオブジェクトがnull、など色々な状況が考えられます。

処理を依頼する側では、考えうる例外について対処するために投げられた例外をキャッチして、例外の種類に応じて何らかの対処をします。

例外をキャッチしなければ、更に上の処理に例外が順次投げられます。トップレベルの処理でも投げられた例外がキャッチされない場合は、プログラムが停止します。最終的には全ての例外についてい何らかの対処を入れて強制終了を避けるのが普通です。

異常事態が起こった場合の例外のスローはthrowキーワードを用います。

void main(){
  var a = 1;
  
  if(a != 0){
    throw Exception();
  }
}

throwのあとExceptionクラスのインスタンスを指定してこれを投げています。だれもキャッチしていないので「Uncaught Error: Exception」というメッセージと共にこのプログラムは強制終了されます。

throwされるのはExceptionやErrorの子クラスが一般的です。

  • ArgumentError
  • FormatException
  • IndexError
  • IntegerDivisionByZeroException

など様々予め用意されています。

自分の投げたい例外にぴったりくるものがあればこれらを使っても良いですし、自分で作ったクラスを投げてもOKです。

基本的にnull以外のどんなクラスのオブジェクトでも投げられますので、適切な名前と異常事態を解析するために十分な情報をもたせられる設計を表したい異常事態に合わせて行います。


例外の捕捉

例外の捕捉は下記例のような構文で行います:

void main(){
  try{
    func(0);
    func(1);  
  } on FormatException {
    print("フォーマットが違います");
  }
  
}

void func(int a){
  if(a != 0){
    throw FormatException();
  }
  print("OK");
}

ここでは「try ~ on FormatException」でFormatExceptionを捕捉しています。

上記コードの実行結果は:

OK
フォーマットが違います

となります。func(0)の方は例外をスローしませんのでOKと出ます。

on節は連続して書くことができ、複数種類の例外に対応できます:

void main(){
  try{
    func(0);
    func(1);  
  } on FormatException {
    print("フォーマットが違います");
    
  } on IntegerDivisionByZeroException catch (ex){
    //onは連続で記載可能
    //catchキーワードで投げられた例外を捕捉して利用可能
    print(ex.toString());
    
  } catch (ex) {
    //onを書かずにcatchすれば、throwされたものを全て捕捉
    print("例外中の例外:"+ex.toString());
  }
}

try節の最後にfinallyキーワードから始まるブロックを追加すると、最終的に例外があってもなくても必ずこのfinallyで指定された処理を実行することになります:

void main(){
  try{
    func(0);
    func(1);  
  } on FormatException {
    print("フォーマットが違います");
    
  } on IntegerDivisionByZeroException catch (ex){
    //onは連続で記載可能
    //catchキーワードで投げられた例外を捕捉して利用可能
    print(ex.toString());
    
  } catch (ex) {
    //onを書かずにcatchすれば、throwされたものを全て捕捉
    print("例外中の例外:"+ex.toString());
    
  } finally {
    print("最終的には必ずここの処理は通る");
  }
}

catchの意味合いが他の言語とは少しことなるので注意です。他の言語のcatchはDartではonの方が近いですね。ただ最終手段でなんでも投げられたものを捕捉して穏便にアプリを終了させるなどのときはcatchが有用です。

いかに不測の事態が起こったとしても、クラッシュさせてしまうよりはエラーログを適切に記録しながら穏便にアプリを終了させに行くのがよいでしょう。




おわりに

例外的な処理は必ず発生するものですので、これに対する対処は必須になります。

ユーザになんらかの不具合を伝えて、一旦別の処理をしたり、もう一度トライしたりなどを選択してもらうのが一般的な対処になります。

不測の事態が起こったときにすぐにアプリがクラッシュしてしまっては、使えないアプリだという烙印を押されてしまいますからね。なるべくそうならないように、起こりうる事態にはしっかり備えておきたいものです。


f:id:linkedsort:20211107023905j:plain