0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【リーダブルコード】制御フローを読みやすくする

Posted at

リーダブルコードの7章「制御フローを読みやすくする」を読んだので、内容をまとめています。
条件やループなどの制御フローがないコードは読みやすい。
他の場所に飛んだり枝分かれするのは複雑でコードが分かりにくくなってしまう


忙しい人向けに、まとめから

鍵となる考え
条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。

具体的なポイント

  • 条件式の引数の並び順
  • if/elseブロックの並び順
  • 三項演算子
  • do/whileループを避ける
  • 関数から早く返す
  • 悪名高きgoto
  • ネストを浅くする
  • 実行が流れを追えるように

7章の内容

条件式の引数の並び順

if(lenght >= 10)の方が、if(10 <= length)よりも読みやすい。
なぜか?
以下のような指針が使える。

左側 右側
「調査対象」の式 「比較対象」の式
変化する あまり変化しない

この指針は英語の用法と似ていて、「もし君が20歳以上ならば」というの方が自然な言い回しになる。
「もし20年が君の年齢以下ならば」は不自然。

if/elseブロックの並び順

if/else文のブロックは、並び順を自由に変えることができる。
だが、この並び順には優劣がある。

  • 条件は否定系よりも肯定系を使う。例えば、if(!debug)ではなく、if(debug)を使う
  • 単純な条件を先に書く。ifとelseが同じ画面に表示されて見やすい
  • 関心を引く条件や目立つ条件は先に書く

具体例 URLにクエリパラメータexpand_allが含まれているかどうか判断して、responseを構築する
「ピンクの象のことを考えないように」と言われてに、ピンクの象を考えてしまう

改善前
if(!url.HasQueryParameter("expand_all"){
    response.Render(items);
}else{
    xxxxxxxx;
}

改善後
if(url.HasQueryParameter("expand_all"){
    xxxxxxxx;
}else{
    response.Render(items);
}

三項演算子

簡潔に書けるが、読みやすさの点で議論の余地がある。

鍵となる考え
行数を短くするよりも、他の人が理解するのにかかる時間を短くする

基本的にはif/elseを使い、三項演算子はそれによって簡潔になるときにだけ使う。

具体例

改善前
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / ( 1 << -exponent );

改善後
if (exponent >= 0 {
    return mantisa * ( 1 << exponent );
}else{
    return mantissa / ( 1 << -exponent);
}

do/whileループを避ける

関数から早く返す

「ガード節」を使わずに実装するとすごく不自然な実装になるので、関数から早く返すことは望ましい。
参照:ガード節
具体例

public boolean Contains(String str, String substr){
    if (str == null || substr == nul) return false;
    if (substr.equals("")) return true;
    省略
}

ネストを浅くする

ネストの深いコードは理解しにくい。ネストが深くなると、読み手は「精神的スタック」に条件をプッシュしないといけない。

ネストが増える仕組み

新しいコードを追加する人にとっては、挿入したコードは新鮮で「関心を引く」コードになる。
一方で後からそのコードを読む人にとっては、こうした文脈や意図は失われてしまう。

鍵となる考え
変更する時にはコードを新鮮な目で見る。一歩下がって全体を見る。

早めに返してネストを削除する

ネストを削除するには、「失敗ケース」をできるだけ早めに関数から返す。

改善前
if(user_result==SUCCESS){
    if(permission_result != SUCCESS){
        reply.WriteErrors("error reading permission");
        reply.Done();
        return;
    }
     reply.WriteErrors("");
}else{
    reply.WriteErrors(user_result)
}
reply.Done();

改善後
if(user_result != SUCCESS){
    reply.WriteErrors(user_result)
    reply.Done();
    return;
}

if(permission_result != SUCCESS){
    reply.WriteErrors(permission_result);
    reply.Done();
    return;
}
reply.WriteErrors("");
reply.Done();

実行が流れを追えるように

ここまではループや条件などのジャンプを簡単に読める方法など、低レベルの制御フローについて説明されてきた。
でもプログラムの高レベルの「流れ」についても考える必要がある。
main()から出発して、心の中でコードを追っていく、関数を次々に呼び出し、プログラムが終了するまで続けていく。プログラミング言語やライブラリには、コードを「舞台裏」で実行する構成要素も存在する。

構成要素 高レベルの流れが不明瞭になる理由
スレッド どのコードがいつ実行されるのかよく分からない
シグナル/割り込みハンドラ 他のコードが実行される可能性がある
例外 いろんな関数世に出しが終了しようとする
関数ポインタと無名関数 コンパイル実行時に判別できないので、どのコードが実行されるのか分からない
仮想メソッド object.vrtualMethod()は未知のサブクラスのコードを呼び出す可能性がある

これらの構成要素を使うことで、コードが読みやすくなったり、冗長性が低くなる。
一方で使いすぎるとコードの行方を見失うことがあるので、コード全体に占める割合を大きくしないことが大事。


引用
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?