関数型プログラミングをやってみたい

はじめに

こんにちは、竹田です。現在ネットオンの開発Sec.にて決済系のバッチ処理を主に担当しています。

最近採用係長の開発においてテストコードの品質を良くしようという風潮があり、テストを書くことが好きな私としてはうれしいです。

テストを書いたおかげで気づくことができたバグというのが相当数あります。より強気にリファクタリングを行うための武器になります。意図しない副作用が出た時にテストがキャッチしてくれる安心感があるため初めてコードを触る人に対しても引継ぎが楽になります

今回そんなテスト以外にもバグを減らしたり、より安全なリファクタリングを行ったり、引継ぎを行いやすくできる何かがないかと思っていた所、関数型プログラミングという概念を知り調査と実践を行いました。軽くオブジェクト指向との対比についても述べようと思います。

オブジェクト指向の辛い点

関数型プログラミングに入る前にオブジェクト指向についての感想を述べさせてください。

オブジェクト指向は簡単に言うとデータの集まり(プロパティ)とデータの処理(メソッド)を持つオブジェクトを上手に組み合わせて設計をすることです。オブジェクト指向で開発するとバグの混入を防げたりコードの可読容易性が高まると言われています。ですが私は以下の点が辛いなと実感しました。

  • そもそも理解が難しく可読容易性が低い
  • 目の前のコードに集中できない
  • 引継ぎの容易性が低い
  • テストコードを書くのが面倒
  • エディターが参照してくれない場合がある。

そもそも理解が難しく可読容易性が低い

オブジェクト指向に関する本や記事を読み、デザインパターン、カプセル化、ポリモーフィズムについて知ることはできましたが、私の感想は「とても難しい」です。採用係長にもオブジェクト指向的に書かれた部分がありますが可読容易性が高くなっているとは思えませんでした。

目の前のコードに集中できない

コードを読む上で頭の中に入れておくべき情報が多すぎて目の前のコードに集中することができないのが一番厄介でした。またリファクタリングを行いたい時に影響範囲の特定が難しいとも感じました。

引継ぎの容易性が低い

当然クラスに分けられていますが、このクラスがどういう機能を持つべきクラスなのかのコメントがないことが多く、各クラスの役割を理解する負荷が結構大きいです。つまり引き継ぎの容易性が低いと思いました。恐らく設計書に全体像が書き起こされていない場合、全てのコードを読み込まないと手が付けられないという状態になりそうです。

テストコードを書くのが面倒

またテストコードを書くのが非常にめんどくさいです。オブジェクトの性質上オブジェクトが状態を持つので入力に対して特定の出力を期待している時に、オブジェクトを特定の状態に遷移させておく必要があります。仮にテストしたいオブジェクトが他オブジェクトに依存している場合、テスト対象のオブジェクト以外の知識が必要になります。

エディターが参照してくれない場合がある。

VSCodeで参照先を追いたい時にオブジェクトを使っていると参照先に飛べない事があるのも結構痛いです

オブジェクト指向に精通していれば、これらのような問題点を感じないのでしょうか?

関数型プログラミングとは

ネットの記事や本を読むと主張が様々あるため、これこそが関数型プログラミングであるというような話ではなさそうです。

私が関数型プログラミングについて理解できた部分で惹かれた特徴は「副作用がない」「状態を保持しない」の2つでした。この2つに関しては「純粋関数」を意識することができれば達成できそうです。純粋関数の特徴は以下の3つであるそうです。

・関数の戻り値が一つだけ・その引数にのみ基づいて戻り値を計算する・既存の値を変更しない

なっとく!関数型プログラミング

以下のような関数は純粋関数と言えます。上記の条件をすべて満たしています。

function sum(a,b){
  return a + b;
}

しかし、実際扱っているソースコードではDBの変更があったり、スコープの大きな変数を途中で書き換えている実装があります。以下は簡単な例です

function getRandomInt(num) {
  return (Math.random() * num);
}

上記のコードでは引数にnumを渡しますが返値にランダム要素を含めて計算してしまっているため引数にのみ基づいて戻り値を計算できていません。

function doubleSentence(sentence){
  return sentence.repeat(2)
}

上記のコードは一件正しそうですが、sentenceにnullが渡されてしまった場合例外を投げるため関数の戻り値が一つだけというルールに反します。

function removeLastItem(list){
  return list.pop()
}

上記のコードはlistが破壊的変更を受け、既存の値を変更してしまっているためルールに反します。

この単純な3つのルールですが完全にルールを守った記述にするのは案外難しいと思いました。特にDBの副作用に関してはもう当たり前すぎて意識していない事が多いです。

しかし、この3つを守る事を意識するだけでもテストが書きやすいです。「必ず返値がある」「副作用が無い」というのはテストに恩恵が大きいことを実感しています。

またオブジェクト指向と異なりコードリーディング時も、少なくとも目の前のロジックに集中できるので効率が上がります。

オブジェクト指向と関数型プログラミングのどちらがより優れているという事ではないのですが、オブジェクト指向よりも関数型プログラミングの方が浅い知識であっても恩恵を受けやすいのかもといった印象を受けました。

オブジェクト指向は知識が半端な状態で取り組んだり、一度でもオブジェクト指向にそぐわない改良をしてしまうと取り返すのがとても難しそうです。

採用係長でオブジェクト指向と関数型プログラミングどちらを選ぶべきか

よく見かけるこの論争の中で私の中でしっくりくる回答がタックオーバーフローにある事を別記事より知りましたので共有します。

  • Object-oriented languages are good when you have a fixed set of operations on things, and as your code evolves, you primarily add new things. This can be accomplished by adding new classes which implement existing methods, and the existing classes are left alone.
  • Functional languages are good when you have a fixed set of things, and as your code evolves, you primarily add new operations on existing things. This can be accomplished by adding new functions which compute with existing data types, and the existing functions are left alone.
https://stackoverflow.com/questions/2078978/functional-programming-vs-object-oriented-programming/2079678#2079678

「オブジェクト指向言語が適しているのは、物事に対する固定された一連の操作があり、コードが進化するにつれて、主に新しいものを追加していく場合だ。これは、既存のメソッドを実装する新しいクラスを追加することで実現できる。

関数型言語が適しているのは、物事の決まったセットがあり、コードが進化するにつれて、主に既存の物事に新しい操作を追加していく場合だ。これは、既存のデータ型を使って計算する新しい関数を追加することで実現できる。」

この文章から考えると採用係長の仕組みに乗せるなら関数型プログラミングの方が適合しそうだなと思いました。基本的にDBに乗っているデータの形は決まっていてそれに対して操作を行い画面に表示したり金額を計算したりというような処理を追加することが多いのでオブジェクト指向を入れようとするとちぐはぐなロジックになりがちなのかもしれません。

終わりに

関数型プログラミングの触りだけを学んだ状態なのでメリット全てを享受できているわけではありませんが、少ない時間の学びでも業務に活かせることが多かったです。

今後さらに学んでより高品質なコードが書けることを目指していきたいと思います。あわよくばオブジェクト指向も関数型プログラミングも使いこなせるようになりたいです。