Kyoji Osada

IT Engineering Semantic & Episodic Memory by Kyoji Osada at AiR&D Inc. from Tokyo, Japan.

一流エンジニアになるための Off-JT #5. 先行排他処理

先行排他処理:はじめに

みなさんは、とても行数の長い if 文やとても階層の深い if 文は目にされたことはあるでしょうか?
初学者の方は、これから目にされる機会があるかもしれません。

このポストでは C 系、Java 系、PHP 系の構文で例示します。

if (評価式) {
    例:100 行 if 文ブロックなど
}

または

if (評価式) {
    if (評価式) {
        if (評価式) {
            if (評価式) {
                if (評価式) {
                    if (評価式) {
                        処理
                    }
                }
            }
        }
    }
}

コードを書いていると、どうしても“正常系処理”から書きたくなるエンジニアさんも多いかもしれません。
これは、とある機能実装にあたって、その目的に早く到達するためにはごく自然な行動かもしれません。
「とりあえずいったん動いた。」
これを目指すために、最短距離で実装する事自体は決して悪いことではありません。
その代わり、そのような実装手順を踏むならば、“異常系処理”のための大幅なリファクタリングが絶対条件になるでしょう。
その機能が複雑、または大きなロジックであるほどそのコストはかかるでしょう。

しかし、この“異常系処理”のアプローチを一歩誤ると、冒頭のような if 文になる可能性を多いに高めるでしょう。
このようなソースコードのことを我々は “if 文地獄” と読び、“アンチパターン”として啓蒙しています。

一流エンジニア”は、このようなコードをほぼ書かないでしょう。
なぜなら、一流と呼ばれるエンジニアは、このようなコードは“生産性”が低下することを十分に理解しているからです。

では、このようなコードを書かないようにするためには、どうすれば良いでしょうか?
このポストでは、コードの“生産性”を高める方法の一つとして、“先行排他処理”について解説します。

先行排他処理とは?

先行排他処理”とは、“正常系処理”よりも先に、“異常系処理”をすることを言います。
そして、我々はそれらのコーディングを“先行排他処理コーディング”と呼びます。

例えば
「ファイルの中身を取得する」
という処理の場合、
「ファイルが存在しなければ排他する」
というように、先行して“異常系処理”をする事をいいます。

ここで気をつけなければならないのは、
「ファイルが存在すれば
という、正常な状態を期待した if 文で“正常系処理”を先行させない事です。

先行排他処理の有用性

先行排他処理”の有用性を解説する前に、“正常系処理”を先行させるとどんなデメリットが生じやすいかを解説します。

前述のように、“正常系処理”を先行させ、“異常系処理”を後方に書くと、どうしても if 文ブロックの行数が長く、あるいは階層が深くなっていきます。
なぜならば、if 文ブロックを閉じることができない状態になりやすいからです。

if (正常系評価) {
    100 行の if 文ブロック
} else {
    異常系 else 文ブロック
}

または

if (正常系評価) {
    if (正常系評価) {
        if (正常系評価) {
            if (正常系評価) {
                if (正常系評価) {
                    正常系処理
                } else {
                    異常系処理
                } 
            } else {
                異常系処理
            }
        } else {
            異常系処理
        }
    } else {
        異常系処理
    }
} else {
    異常系処理
}

このような状態になると、以下のようなデメリットが生まれます。

  1. 脳内の“ワーキングメモリー”の浪費
    まず一つ目のデメリットは、脳内の“ワーキングメモリー”の浪費です。
    if 文ブロックの行数が長く、または階層が深いということは、そのコードをキャッチアップするにあたり、そのロジックを脳内の“ワーキングメモリー”に記憶させていかなければなりません。
    これは、作業のためにとても長い文章を一時的に記憶していく状態に非常によく似ています。
    では、その脳内のワーキングメモリーを使い果たしてしまったら、どうなるでしょうか?
    もう一度、同じ文章を読み直すことになります。
    これにより、ロジックのキャッチアップに余計な時間がかかり、“生産性”を低める事になります。

  2. 視認性”の低下
    二つ目のデメリットは、“視認性”の低下です。
    これは、if 文の階層や if 文のブロッックを単に見間違える可能性を高めるという事です。

  3. ロジックの矛盾
    三つ目のデメリットは、“ロジックの矛盾”が生じやすいということです。
    これは、一つの文章を接続詞で延々と繋げていく作業に非常に似ています。
    その結果、次第に論点がずれていくという状態を生じやすくしてしまいます。

  4. スパゲティコード”の誘発
    四つ目のデメリットは、ロジックが複雑になりやくす、“スパゲティコード”を誘発しやすいということです。
    これは、接続詞で延々と繋げた一つの文章に対して、強引にその整合性をとろうとすると、どうしても複雑な文章構造になり、ロジックの I/O も分かりづらくなってきます。
    書く方も、読む方も、要点が掴みにくい“スパゲティコード”になりやすいということです。

  5. 未知のバグ
    五つ目のデメリットは、“未知のバグ”を生みやすくなるということです。
    巨大なロジックブロックになるほど、評価の重複や抜けも生じやすくなることから、未知のバグを生みやすくしてしまいます。

しかし、“先行排他処理”では、このような“正常系処理”を先行させた場合のデメリットが、そのままメリットになります。
なぜなら、異常系を先行して排他処理すると、その処理はその時点で完了し、if 文ブロックを閉じる事が可能となるからです。
そして、残る正常系の処理には、if 文を使わなくて済むようになります。
したがって、冒頭のような “if 文地獄”が起きにくくなります。

もちろん、知っていながら“異常系処理”を面倒がって省く行為は議論の対象外となります。

例)正常系処理が先行した場合

// ファイルが存在すれば
if (評価式) {
    // ファイル読込権限があれば
    if (評価式) {
        // ファイル形式であれば
        if (評価式) {
            // ファイルの中身が取得できれば
            if (評価式) {
                正常系処理
            // ファイルの中身が取得できなければ
            } else {
                排他処理
            }
        // ファイル形式じゃなければ
        } else {
            排他処理
        }
    // ファイル読込権限がなければ
    } else {
        排他処理
    }
// ファイルが存在しなければ
} else {
    排他処理
}

例)先行排他処理の場合

// ファイルが存在しなければ
if (評価式) {
    排他処理
}
// ファイル読込権限がなければ
if (評価式) {
    排他処理
}
// ファイル形式じゃなければ
if (評価式) {
    排他処理
}
// ファイルの中身を取得できなければ
if (評価式) {
    排他処理
}
// 最後に正常系処理:if 文は必要ない
正常系処理

これは、“正常系処理”を先行させた場合が、接続詞で延々と繋げた一つの長い複雑な文章のようなものであるのに対し、“先行排他処理”は箇条書き処理のようなものです。
どちらが、“生産性”が向上するかは自明でしょう。

例えば、「東京大学大学院 人文社会系研究科」の研究でも明らかになったように、これに似た状態だといえるしょう。
newswitch.jp

開発速度が落ちる?

「“先行排他処理コーディング”では、開発速度を遅くなる?」
という意見が出た事があります。
開発速度と品質”の両立については、別のポストで言及する予定ですが、これは正しくも誤りでもあります。

まず、正しい側面で解説すると、個人開発においては、エンジニアさんによっては確かにその通りかもしれません。
個人開発においては、どのコードがどの意図によって書かれたものかは、既に本人が一番わかっているわけですから、可読性やロジックのキャッチアップ速度という話はほぼ出てこないでしょう。

次に、誤りの側面で解説すると、趣味ではなくビジネスやオープンソースコミュニティの現場においては、ほとんどの場合が複数人によるチーム開発となるでしょう。
いくら一人の開発速度が早くても、複数人のキャッチアップ速度や開発速度が遅くなれば、チーム全体の開発速度は遅くなります。

したがって、この開発速度が遅くなるという主張は、個人開発の域の話であり、チーム開発の話としては誤りであるのは明白です。

パフォーマンスが落ちる?

「“正常系処理”のパフォーマンスが下がる?」
という意見もありましたのでついでに解説します。
大丈夫です。そんなことはありません。
もし、仮に影響したとしても、今の時代その影響はマイクロ秒単位でしょう。
逆に複雑な if 文の方が処理パフォーマンスが下がるのは自明でしょう。

否定形表現を使うな?

先行排他処理”では、if 文の評価式が否定形であったり、戻り値が false のメソッドや関数などを多用するケースが増えます。

例)

if (! 否定形評価式) {
    処理
}

あるいは

if (a != b) {
    処理
}

「if 文で否定形表現をあまり使うな?」
という情報を、たまにお見かけすることがあります。
彼らのこれらの主張には、きとんと意図があるはずです。
語弊を覚悟でこれを解説するとするならば、
「単に肯定系表現ができるところを、わざわざ分かりづらい否定形表現をすることもだいだろう。」
というものです。
きとんと、目的と意図がある“先行排他処理”については、これらをあてはめなくても大丈夫です。

先行排他処理:まとめ

このポストでは、“一流エンジニアになるための Off-JT” シリーズのテーマとして、“先行排他処理”について解説しました。

このスキルは、“一流エンジニア”になるためのみなさんの“強力で有用な武器”になりえます。
このポストが、みなさんの“一流エンジニア”になるための一助になれば幸いです。

一流エンジニアになるための Off-JT” シリーズ次回のテーマは “#6. エンジニアの生産性” の予定です。