実践Flutter

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

実践Dart:制御文

はじめに

ここでは条件分岐など、Dartのコードの実行フローの制御についてまとめていきます。

if文やfor文など、歴史と伝統の大定番の構文がほとんどで、switch文のみ少し変わった制約があります。

複雑な制御文を組み合わせると、あっというまにコードの可読性が下がります。最小限の分岐で処理を進めるように工夫することが重要になります。




if文

if文は他のプログラミング言語と同様です。

if(条件式){
  //条件式が真のときに実行
}else{
  //条件式が偽のときに実行
}

大定番の分岐構文です。ほぼ不可欠な構文ですが、バグの温床になるのでなるべく分岐しないよう工夫したいところです。

分岐するにしてもif文のなかで処理する内容は最小限にしてifブロック、elseブロックを小さくすると可読性が上がります。

ifからはるか下にスクロールしたところにelseがあるような形になると一気に理解し難いコードになっていきます。この場合は途中の処理を関数で切り出して、少なくとも1画面に収まるようにしておきたいところです。

?演算子

「条件式 ? a : b」条件式が真であればa、偽であればbとなる式です。C言語をはじめ様々なプログラミング言語で導入されている便利な構文です。

前節のif文が文であるのに対し、「条件式 ? a : b」はこれ自体が式ですので関数のパラメタなどの場所で使うことができる点が強力です。

String enter = (age >= 18) ? "OK" : "NG";

上記ではageが18以上であれば"OK"、そうでなければ"NG"がenterに代入されます。

switch文

switch文も他のプログラミング言語同様に使えます。

switch(変数){
  case1:
    do1();
    break;
  case2:
    do2();
    break;
  default:
    do3();
}

switchの冒頭で与えた変数の値が値1だった場合、値2だった場合、その他の場合で分岐させる例です。

すこし変わっているのは、各caseで何らかの処理を行う場合、breakを入れることが必須になっていることです。breakを入れずにcaseをフォールスルー場合は、caseの本文は空でなければならないというルールがあります。

下記はコンパイルが通りません。値1のケースの時、do1()を実行していますがbreakしないのは違反になります。

switch(変数){
  case1:
    do1();
  case2:
    do2();
    break;
  default:
    do3();
}

下記は値1のときに値2と同様のことをする処理で、この場合はbreakを入れなくてもコンパイルが通ります。

switch(変数){
  case1:
  case2:
    do2();
    break;
  default:
    do3();
}

こういうルールがあることを頭の隅に置いておくくらいで良いかと思います。

そもそもswitch文が必要になる場面は少ないですし、if文以上にバグの温床になりがちです。条件は単純ですが、分岐は複雑ですからね。switch文を使うのは年に一回程度に留めておくレベルですね。いやいや毎日使うよという場合、根底から考え方を変えたほうがよいです。本当に。

for文

forループの基本形は決まった回数を繰り返す、歴史と伝統ある以下の形です:

for(var i = 0; i < 10; i++){
  do();
}

制御変数をカウントアップなりダウンしてループを行うfor文はC言語のような低レベル言語ではよく使いますが、リッチなコレクションを多用するモダンな言語体系ではそれほど使わなくなってきました。

コレクションに含まれる要素の全てについて繰り返す下記の形の方が多いかと思います:

final List<String> cars = ["Yaris","Aqua","Prius","Crown"];

for(var c in cars){
  print(c);
}

インデックスの数値を扱うのはバグの原因になりがちですので、なるべく避けたいところです。for-inループが使える状況であればこちらを使います。

forループの中にbreakがあると、繰り返し実行を中止します。またcontinueがあると、その回のループを完了して次の回のループを開始します。breakやcontinueを多用すると制御構造が複雑になりがちですので、これもなるべく使わない形を考えることをおすすめします。

while文、do-while文

while、do-while文もおなじみの制御文で、特に変わったところはありません:

while(条件){
  do();
}

do{
  do();
}while(条件)

上のwhile文は条件が真になっている間中、繰り返し実行されるものです。ここで条件が最初にいきなり成立していないと1回も本文が実行されません

一方で下のdo-while文も条件が真になっている間中、繰り返し実行するというところは同様ですが、最初の1回は必ず実行されるという点がwhile文と異なります。

assert文

ブール型の式の真偽を確認し、偽であれば例外をスローします。これも他の言語でもおなじみの仕組みです。リリースビルドではすべてのassertは無視されます。完全にデバッグ用です。

assert(真になるべき条件式);

基本的に、絶対に真になると想定しているもの、前提としているものを念の為記載しておき、万一周辺の実装が変化してその前提が崩れたときに、これを早期発見するためのものです。

ほとんど真だが偽になる可能性もある、という場合はきちんとエラー対応の処理を用意します。

おわりに

分岐になるものは基本的にバグの温床であると言い続けました。

実際のところ処理の本質はどの分岐でも変わらず、変数値や関数の値のもたせ方次第では同じ処理をするだけで分岐させずにすむということもかなりのケースであるはずです。

これを発見して1本道のわかりやすいコードが書けたときは爽快ですよね。

バージョンアップしたり、きめ細かい処理を追加していくと、どんどん分岐が継ぎ接ぎのようになっていきます。最後はかさぶたの上にかさぶたができるような脆く複雑な構造になっていったりします。

そうなりがちなので、せめて最初のバージョンは真に本質的でストレートなコードを作っていきたいものですね。


f:id:linkedsort:20211113131132j:plain