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

latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

関数の引数を増やすことでバグを生み出してしまう事がある。 という話

(ε・◇・)з いつも JavaScript のローレベルな話題をお届けしています~
(ε・◇・)з このブログは今日も平常運転です~


さて、引数を1つしか持たない単純な関数は不便なのでしょうか? 便利なのでしょうか?

このエントリでは、

  • 引数を1つしか持たない単機能な関数は、Array#map などのイテレータと相性が良い
  • イテレータと組み合わせて使える関数に、うかつに引数を追加するとバグを生み出してしまう事がある

という例をご紹介します。



引数を1つしか取らない関数にできること

引数を1つしか持たず、与えられた文字列の先頭を大文字化する関数(例: toUpper)は、Array#map などのイテレータと組み合わせる事でスッキリと記述する事ができます。

function toUpper(str) { // @param String:
                        // @return String:
    return str.charAt(0).toUpperCase() + str.slice(1);
}

["abc", "def"].map(toUpper); // -> ["Abc", "Def"]

(ε・◇・)з 単純ってすばらしいね

既存の関数を拡張し引数を増やすとバグを生み出してしまう事がある

あるとき、

(ε・◇・)з toUpper に引数(pos)を追加して場所を指定できると便利じゃない!?
(ε・◇・)з よーし、拡張しちゃうぞー

と、あまり深く考えずにノリで動作を変更してしまうと…

function toUpper(str,   // @param String:
                 pos) { // @param Number(= 0):
                        // @return String:
    pos = pos || 0;
//  return str.charAt(0).toUpperCase() + str.slice(1);
    return str.slice(0, pos) + str[pos].toUpperCase() +
           str.slice(pos + 1);
}

たしかに toUpper の汎用性と機能性は向上するのですが、
その代償として、イテレータと組み合わせて記述する事ができなくなってしまいます。

["abc", "def"].map(toUpper); // -> ["Abc", "dEf"]
                             // 既存のコードの結果が変わってしまう

Array#map + toUpper を既に利用しているユーザにしてみればこれはバグであり、ハタ迷惑な話です。

引数を一個しか取らない限定された関数は意外と便利

イテレータ用に引数を1つしか持たない単純な関数を用意しておくと、それらを組み合わせてスッキリと記述できたりします。

複雑な機能を持った関数を生み出さず、引数の数と型はライブラリ全体のバランスを見て、設計段階から練り込んでおくべき

必須の引数が3つ以上あり、かつ引数の数が可変で、引数の順番と型の組み合わせで関数の挙動が大きく変化するような I/F の設計は控えるべきです。
そのような、全てを飲み込むファサードパータンのような I/F は、好ましくありません。

おそらくそのような関数は、関数本来の動作とは無関係な(引数の型の検査や引数の並べ替え)処理に、毎回コストを支払うような構造になってしまいます。

ぱっと見の分かりやすさや実行速度を犠牲にして得られるのが、ソースコードよりはるかに長い説明文と自己満足ぐらいだとしたら、そのような I/F の設計は今一度考えなおすべきかと思われます。

機能の多重化と分かりやすさのバランスを間違えると、リファレンスを見なければコードが書けないライブラリになってしまいます。


(ε・◇・)з ボクタチはこれを高機能化の罠と呼んでいるんだ
(ε・◇・)з タチコマタチコマ