クリーンコードとは、読みやすく、理解しやすく、保守しやすいコードのこととする。 そのクリーンコードには様々な原則や手法がある。
その中にPure Functions(純粋関数)というのがある。
純粋関数
純粋関数は、次の基準を満たす特定のタイプの関数である。
- 引数を受け取り、値または出力を返す。
- システムの状態を変更したり、外部リソースとやり取りするなど、副作用は発生しない。
- 同じ入力が与えられると、常に同じ出力が返される。
- スコープ外の状態や変数には依存しません。
e.g.)
function add(x, y) { return x + y; } const result1 = add(3, 5); // 8 const result2 = add(3, 5); // 8
そのため、
- 純粋関数は、受け取った入力と内部ロジックによってのみ動作が決定されるため、予測しやすくテストがしやすいと考えられている。
- 純粋関数の出力は外部要因の影響を受けないため、プログラムの動作の推論も容易になる。
- 純粋関数は関数型プログラミングでよく使用される。
実装をするときに考えていること
実装をする際に、純粋関数であるかどうかは考えていない。 ただ、テスタビリティや副作用については気にしている。
先日あった実装例
func Foo() { var f Foo ... f.SetBar(a, b, c) } func Foo2() { var f Foo ... f.SetBar(a, b, c) }
というコードに対して、以下のようなリファクタリングがなされようとした
func Foo() { var f Foo ... setBar(f) } ... func setBar(f Foo) { var a = ... ... f.SetBar(a, b, c) }
この変更は引数に対して副作用が発生している。 そのため以下のようなコードを提案した。
func Foo() { var f Foo ... f.SetBar(buidBar()) } ... func buidBar() Bar { var a = ... ... return Bar(a, b, c) }
buidBar
の方が setBar
に比べて、テストもしやすく副作用がない。
setBar
の呼びだし側が f
の状態が変わることを予期していないと、勝手に状態が変わりバグの温床になりやすいと考えている。
パラメーターへの代入の除去
純粋関数とは少し違うが、リファクタリング 既存のコードを安全に改善する(第2版)の第1版に「パラメーターへの代入の除去(Remove Assignments to Parameters)」というのがある。(第2版にはこの記述はないかも?) ここでは、引数で渡されたオブジェクトに何かをすること自体は否定していないが、丸ごと変更することには反対している。
Bad
int discount(int inputVal, int quantity, int yerToDate) { if (inputVal > 50) intputVal -= 2; }
Good
int discount(int inputVal, int quantity, int yerToDate) { int result = inputVal; if (inputVal > 50) result -= 2; }
最後に
この記事も近いようなことを書いている。
純粋関数の考えかたには素晴らしいが、実際の現場では純粋関数で実装することが困難なケースも多々あると思う。(そう出来ないくらい既存の実装が厳しかったり、いずれは副作用が発生する実装が入るはず) 重要なのは副作用のリスクを認識して管理し、必要に応じて適用していくことだと思う。
最後に、関数型プログラミングのパラダイムは信頼性が高く保守しやすいコードを書くためのきっかけとなるので、時間があるときに勉強していきたい。