読者です 読者をやめる 読者になる 読者になる

関数分けの効用

通常、プログラムを書くときは、処理をただ順番に書き下すのではなく、いくつかの「関数」に分割します。 これによって各ステップの処理が部品化され、再利用が可能になるわけですが、関数分けの効用はそれだけに留まりません。 例え、たった一度しか実行されない処理でも、これらを上手く関数化し、本体から分離することで、プログラムの可読性・保守性を向上させることができます。

例えば、以下のようなフォームからデータを受け取るプログラムを考えてみましょう。

名称
所在地
Tel
Fax

▼ソースを表示 ▲ソースを隠す
<form action="./" method=post">
    <div>
        <input type="hidden" name="id" value="<%=office.id%>" />
    </div>
    <table>
    <tr>
        <th>名称</th>
        <td><input type="text" name="name" value="<%=escape_html(office.name)%>" /></td>
        </tr>
    <tr>
        <th>所在地</th>
        <td>
            <input type="text" name="postcode-a" value="<%=sprintf('%03d', postcode_a)%>" size="3" /> -
            <input type="text" name="postcode-b" value="<%=sprintf('%04d', postcode_b)%>" size="4" /><br />
            <textarea name="address" rows="2"><%=escape_html(office.address)%></textarea></td>
        </tr>
    <tr>
        <th>Tel</th>
        <td><input type="text" name="tel" value="<%=escape_html(office.tel)%>" /></td>
        </tr>
    <tr>
        <th>Fax</th>
        <td><input type="text" name="fax" value="<%=escape_html(office.fax)%>" /></td>
        </tr>
    </table>
    <div>
        <input type="submit" value="<%=escape_html((office.id != 0) ? ' 更新 ' : ' 登録 ')%>" />
    </div>
</form>

まずは、何も考えずに実装してみましょう。 CGIパラメタ値の取得から、オブジェクト生成, データベース処理までを、ひとつの関数の中にフラットに書き下します。

# 更新 (/登録)
def exec_modify()

    id         =param_int('id')
    name       =param_str('name')
    postcode_a =param_int('postcode-a')
    postcode_b =param_int('postcode-b')
    address    =param_str('address')
    tel        =param_tel('tel')
    fax        =param_tel('fax')

    postcode =sprintf('%03d-%04d', postcode_a, postcode_b)

    company =Company::new(id, name, postcode, address, tel, fax)

    id_mod  =nil

    if (company.id != 0)
        id_mod =Company::db_update(@dbh, company)
    else
        id_mod =Company::db_insert(@dbh, company)
    end # company.id

    (中略)
end

データベースへの登録 (insert) と、更新 (update) のどちらの処理を行うかは、Copmany オブジェクトのIDによって判断します。 値 0 は特別なID値で、これをもつオブジェクトは、まだデータベースに登録されていないことを示します。 (データベース内には、ID = 0 に該当する列は存在しない仕様。)

Company::db_insert は、ID = 0 のオブジェクトを渡されると、既にデータベースに登録されている項目と重複しないIDを発行してそのオブジェクトに割り当て、戻り値の値として、これを返します。 IDが 0 でないオブジェクトを渡した場合は、db_insert, db_update ともに、オブジェクトのIDをそのまま返します。

さて、このプログラムでも動作に問題はないのですが、私はかなり「とっちらかった」印象を受けます。 その原因は、id, name, postcode_a, ... といった変数が、company にオブジェクトが割り当てられた時点で不要となるにも関わらず、メソッド exec_modify の終了までそのスコープを持続させている点にあります。 そのせいで、プログラマは必要以上に広い範囲わたって、これらの変数を気にしなければなりません。

そこで、CGIパラメタを受け取って、オブジェクトを生成する処理を、関数として分離 してみることにします。

# 更新 (/登録)
def exec_modify()

    company =param_company()

    id_mod  =nil

    if (company.id != 0)
        id_mod =Company::db_update(@dbh, company)
    else
        id_mod =Company::db_insert(@dbh, company)
    end # company.id

    (中略)
end

# CGIパラメタ (法人情報) の取得
def param_company()

    id         =param_int('id')
    name       =param_str('name')
    postcode_a =param_int('postcode-a')
    postcode_b =param_int('postcode-b')
    address    =param_str('address')
    tel        =param_tel('tel')
    fax        =param_tel('fax')

    postcode =sprintf('%03d-%04d', postcode_a, postcode_b)

    return Company::new(id, name, postcode, address, tel, fax)
end

ちょっといい感じになったのではないでしょうか。 id, name, postcode_a, ... といった一時的な変数のスコープは、param_company の中に納まり、exec_modify を読む人がこれを気にする必要はなくなりました。 また、データ読み込みと、データベース操作が別々の場所にまとめられたため、プログラムの見通しも良くなっています。

こうした手法を、私は「相 (phase) の分離」と (勝手に) 呼んでいます。 しかし、これだけではまだ分離が完全ではありません。

郵便番号の取得に使用されている一時変数 postcode_a, postcode_b, がじゃまっけです。 なので、この相も分離してしまいましょう。

( 前略 )

# CGIパラメタ (法人情報) の取得
def param_company()

    id       =param_int('id')
    name     =param_str('name')
    postcode =param_postcode()
    address  =param_str('address')
    tel      =param_tel('tel')
    fax      =param_tel('fax')

    return Company::new(id, name, postcode, address, tel, fax)
end

# CGIパラメタ (郵便番号) の取得
def param_postcode()

    a =param_int('postcode-a')
    b =param_int('postcode-b')

    return sprintf('%03d-%04d', a, b)
end

ここで注目して欲しいのは、変数の名前が postcode_a, postcode_b から、a, b と簡略化されたことです。 param_company あるいは exec_modify などのメソッドの中では、a, b のような短い名前は適切ではありません。 しかし、param_postcode は「郵便番号を取得する」メソッドであり、そのコンテキストの内部においては、postcode_ というプレフィクスはむしろ冗長になってしまうのです。

このように、プログラムを機能のまとまりごとに関数化すると、それらの中により局所化されたコンテキストを持ち込むことができるようになり、結果として変数名などを簡略 化できるというメリットがあります。 変数名が短くなったからといって、その役割が曖昧になることはありません。 (むしろ可読性は向上すると言っても差し支えないでしょう。)

こうした関数分けをせずに、一つの関数を延々と数百行にも渡って書き綴る人をしばしば見かけます。 私の観測の範囲では特に学生が多いのですが、「この部分を関数に分けなさい」というと、「プログラムが長くなってしまう」とか「面倒くさい」なんて答が返ってきたりします。 あの……。あとからそのプログラムの修正する俺の方がずっと「面倒くさい」んですけど……。

そうした長~い関数は、プロのプログラマの間では、「最長不倒関数 (never ending function)」と呼ばれ、とても嫌われています。

Cプログラミング診断室 / 最長不倒関数 / UNIXへの招待
http://www.pro.or.jp/~fuji/mybooks/cdiag/cdiag.10.1.html

一度書いて、あとは知らない!というのならともかく、長期に渡って保守・運用をすることを考えると、多少長くても、適切な関数分け (相分離) がなされているプログラムの方が扱いやすいのです。

担当: 成田 (the chopper)