プログラミングの「副作用」とは何か、そしてその対策
副作用とは?
Wikipediaによると、次のように説明されています。
プログラミングにおける副作用(side effect)とは、式の評価による主たる作用とは別に起こるそれ以外の作用である。
難しいですね🫠
僕は「関数やメソッドが、戻り値以外に外界に及ぼす影響」を副作用と呼ぶ、と解釈しています。副作用の例は次のとおりです。
- ファイルに何かを書き出す
- 別のシステムとネットワーク越しに通信する
- データベースの内容を更新する
- 関数やメソッドの外で定義された変数(グローバル変数やクラスのフィールドなど)の値を変更する
副作用はバグのもと
副作用はバグの原因になりやすいです。
何故なら、関数やメソッドを利用する側の人にとって副作用は基本的にサプライズだからです。「そんなことが起こるなんて気づかなかった!」と利用者は思ってしまいます。
どうすれば副作用に気をつけつつ、バグを防ぐことができるのでしょうか?
対応策1: 副作用があることを分かりやすくする
まずは「この関数・メソッドには副作用がありますよ」ということを、利用者に対してアピールすることが大事です。
アピールの方法は次のとおりです。
戻り値無しにする
関数・メソッドに戻り値が無いということは、計算してその結果を返すことが目的ではありません。なのでこの関数・メソッドには副作用があるとすぐに分かります。
逆に言うと、副作用がある関数・メソッドはなるべく戻り値を返さないようにしましょう。
副作用がありそうな名前にする
次のようなキーワードを関数・メソッドの名前に使って、どんな副作用が起こるメソッドなのかを分かりやすくしましょう。
- write
- upload
- update
- insert
- delete
- change
- flush
- set
- add
- ...他にもいろいろ
対応策2: 副作用を分離する
もう1つ大事なのは、副作用となる処理を単独の関数・メソッドに切り出すことです。
1つの関数・メソッドに副作用のある処理・ない処理を混ぜるのではなく、別の関数・メソッドとしましょう。
次のコードは悪い例です。1つのメソッドの中で、計算処理とファイル書き出し(副作用)の両方を行っています。
public void () {
// 計算処理
計算結果 = ...;
// 上記の計算結果をファイルに書き出し
}
次のコードは良い例です。ファイル書き出し以外の処理は1つ目のメソッドで行い、2つ目のメソッドは副作用であるファイル書き出しのみを行っています。
public 計算結果 計算() {
// 計算処理
return 計算結果;
}
public void (計算結果) {
// 上記の計算結果をファイルに書き出し
}
副作用を分離すると、実は単体テストがやりやすくなります。ファイル書き出しなどの副作用があるメソッドのテストは、副作用なしのメソッドに比べて手間がかかります(書き出したファイルの内容を検証したりする、副作用の結果も検証する必要があるため)。
副作用を分離することで、手間をかけずにテストできる副作用なしのメソッドの割合が増えるため、単体テストがやりやすいのです。
おすすめの本
次の本を読むと、この記事の内容がより詳しく解説されていますよ。
Discussion