良い関数ばかりではない悪い関数も普通にあるし使われる
プログラムの関数で良い悪いってあるの?
一度は考えたことはないでしょうか。
この記事では良い関数は期待した結果になること。
すごく当たり前の話なのですが良い関数というのは
引数を指定して期待した結果をリターンしてくれることだと思います。
・・・当たり前すぎですね。
期待した結果にならない関数をだれが使うんですか?
実行するごとに違う値を返す関数をだれが使うんですか?
と言いたいのですが、残念ながら結構なプログラムで使われてます。
私も使います。
この記事を読まれている人の周りでも
「同じ結果にならない関数」がエラーや障害を発生させているかもしれません。
「同じ結果にならない関数」はこんな特徴があります。
- 関数の中に引数・ローカル変数以外が使用されている
- 関数の中にクエリ操作が記載されている
- 関数の中にファイル操作が記載されている
- 関数の中にAPI操作が記載されている
目次
この記事でいいたいこと
- 悪い関数は引数以外に変化する要素が紛れ込んでいる
- 良い関数と悪い関数は分けておく
この記事はプログラムの初心者から一年ほど経験の人向けに書いています。
それでは本題です。
関数にフィールド・プロパティが使われているなら要注意!それがprivateであっても
まずは
- 関数の中に引数・ローカル変数以外が使用されている
- 関数の中にクエリ操作が記載されている
- 関数の中にファイル操作が記載されている
- 関数の中にAPI操作が記載されている
です。
関数とプロパティが同居しているクラスで発生する可能性があり、
「外部」から関数の動きを変えてくるかに注意します。
class PublicSideEffect
{
public int five = 5;
public int AddFive(int target)
{
return target + five;
}
}
最初は「AddFive()」は5を足してくれるシンプルな関数です。
では動きを変えてみましょう。
publicSideEffect.AddFive(1);//6
publicSideEffect.five=2;
publicSideEffect.AddFive(1);//3
簡単に動きを変えれましたね。
プロパティを書き換えてしまうと「5を加算する」機能なのに
5を加算してくれない悲しき関数になりますが
実際のプログラムではプロパティが書き変わることは十分ありえます。
また、privateはアクセスが制限されているから大丈夫と考えがちですが
コード次第では同じことが起きます。
class PrivateSideEffect
{
private int five = 5;
public void CangePrivteFive(int target)
{
five = target;
}
public int AddFive(int target)
{
return target + five;
}
}
privateSideEffect.AddFive(1);//6
privateSideEffect.CangePrivateFive(2);
privateSideEffect.AddFive(1);//3
やはり動きが変わります。
内部のフィールドやプロパティに影響を与える関数(CangePrivateFive)が原因ですね
このように関数の動きが変わることを関数に副作用があるといいます。
public、privateな値で変化するのですから
globalな値が関数内に紛れているのはもちろん要注意です。
引数・ローカル変数以外含まれている関数対策
フィールド・プロパティがとにかく書き変わらないようにします。
読み取り専用プロパティにしたり、
プロパティが設定されるのはコンストラクタだけに限定することで、
関数の動きを変化をさせないよう工夫をします。
サンプルみたいなポンコツなコード書きますかね?と思われるでしょうが、
仕組上できるなら何かの拍子に誰かがやらかします。
誰かが自分にならないために心の片隅に留めてくれるとうれしいです。
知らないうちに外部から影響を受ける関数

- 関数の中に引数・ローカル変数以外が使用されている
- 関数の中にクエリ操作が記載されている
- 関数の中にファイル操作が記載されている
- 関数の中にAPI操作が記載されている
クエリ操作を例にとって、後半3つを同時に説明したいと思います。
物販(EC)サイトで考えてみます。
ある日入った注文の一覧が欲しいときのイメージコードです。
データベースからデータを取得して
インスタンスにするという単純なものです。
注文[] 注文取得(注文日)
{
Selectクエリ = Selectクエリ作成(注文日);
発行結果[] = クエリ発行(Selectクエリ);
foreach(var 注文データ in 発行結果[])
注文データから注文インスタンス作成して返値に追加;
}
return 注文[];
}
ここで注目すべきはクエリの「発行結果」が変わるということです。
引数で変わるならクエリの実行結果が変わるのは当然と
考えている方もいらっしゃるかもしれません。
しかし重要なのは引数以外の「外部」の要素が
結果を変えているということを把握できているかです。
ここでいう「外部」はデータベースそのものです。
どういう事かというとクエリを実行すると関数内の「発行結果[]」の値を「変える」のです。
例えば2020/01/01の注文情報を取得したとします。
これを2019/12/31に実行したらどうでしょう。
未来の注文になるため「注文取得(2020/01/01);」は当然0件です。
では2020/01/01に実行したらどうでしょう。
当日の注文は「注文取得(2020/01/01);」を実行した時点で
注文されているデータが取得できます。
早朝であれば1、2件入っているかもしれませんね。
では2020/01/02に実行したらどうでしょう。
過去の日付ですのでこれ以上注文が入ることはないですが一日分の注文です。
「注文取得(2020/01/01);」はそれなりの数になっているでしょう。
100件?それとも10000件以上?
このように、実行する時間帯によっていくつかの可能性が考えられます。
今回の例では実行する日時が違えば、
同じ関数同じ引数でも結果が異なりました。
引数以外に結果に影響を与えるということ、
関数が「外部」の影響を受けているというのはこういうことです。
これが「同じ結果にならない関数」の身近な例です。
とにかく関数の中で「値」を生み出している奴がいるとき「外部」の存在に要注意です。
ファイル操作とAPI操作も外部からの影響を受けやすい
クエリ操作同様にこれらも外部から影響を受けやすいです。
例えばCSVファイルのデータを解析する際など
ファイルを読み取る場合はファイルの読み取りデータが
関数の中に入ってきます。
これもデータがデータベースにあるかファイルにあるかの違いでしかないです。
API操作も同じですね。
これは通信先がDBサーバなのかWEBサーバなのかで
サーバの違いはあれど結局やっていることは
リクエスト結果のレスポンスデータを使用するということです。
通信の結果が入力値になります。
結局どっちも「外部」からデータをもらってます。
クエリ操作、ファイル操作、API操作は例外処理を意識しよう
インプットがそうならアウトプットもではないかと気づかれた方は正解です。
クエリならばupdate,insertでデータベースに書き込みを行います。
その結果が必ず関数の中で処理されます。
よくよく「書き込みに失敗しました」とか「通信に失敗しました」
なんてことありますよね。
そういう「例外」な値もいきなり関数の中で入ってきます。
ですので、大体のファイル操作、クエリ操作、API操作では
例外処理が記載されるのです。
「同じ結果にならない関数」は分けてあげて影響を小さくする

クエリ操作など必須なコードも「引数以外の影響を受けやすい関数」であるため
プログラムから排除するのは現実的ではありません。
ですので軽減策をお伝えします。
プログラミングをするとき「引数以外の影響を受けやすい関数」と
「引数だけで完結している関数」に分けられるときは分けてあげればよいです。
そして関数を呼び出して使う場合も呼び出す箇所をまとめてあげると
影響が小さくなります。
例えばクエリでデータを取得する処理を最初のほうで実行するとかですね。
すでにコードがある場合は少しずつ移動させることをお勧めします。
スパゲッティコードになっていたりしますので。
まとめ
今回の例では4つの例で紹介しましたが
引数以外の「外部」から影響を受けていないかを考えるのはとても重要です。
現在時刻取得なんかもそうです。
現在時刻を取得する機能をほとんどの言語が提供しますが、
端末の現在時刻が関数の外から入ってくるため、
実行するごとに結果が変わるかもしれません。
関数を作る時は「外部」を意識していきましょう!