みなさんこんにちは。Voicyでエンジニアをやっている窪田です。
この記事はVoicy Advent Calendar 2022の12日目の記事です。
私は普段からみんながどうやってプログラムを書いてるんだろうかと気になってたので、今回は自分の書き方を少し紹介してみようと思います。
はじめに
例えばWebサーバーの処理で、以下のようにプログラムが分かれてたとします。
この場合、皆さんはどの層から書き始めますか?右の処理を呼ばれる側から書けば、呼び出す側を書く時には既にそのメソッドが存在していてコンパイルエラーにならないため、Repository層から書くと言う方もいるんじゃないでしょうか。もちろんこれは完全に好みの問題なので正解はないのですが。私は左の呼び出す側から右に向かって書いていくことが多いです。その理由は主に二つあります。
- データの流れに沿って処理を書くことができる
- インターフェースを決めてから処理の内容を実装できる
以下でそれぞれについて説明します。
データの流れに沿って処理を書くことができる
例えば本を読む場合、ビジネス書だと目次を見て気になった章から読み始めることもできますが、小説などの物語では話の流れがあるため、最初から順を追って読まないと訳がわからないことになります。
プログラムが動く際にもデータの流れというものがあり、先ほどの図で言うと、左から右に流れ、その後また左に戻ってくるという流れがあります。私の場合はこの流れに沿ってプログラムを書いた方が頭を整理されるのと、途中で処理が漏れてたりすると、その流れに違和感を感じて気付けることがある気がします。あくまで気がするだけで、ちゃんと検証したわけではないのですが。。。
先にRepository層で必要そうなメソッドを書いてからUseCase層を書くと、その用意されたメソッドを全部呼びだしたことで満足してしまい、実はその間に別の変換処理が必要だったみたいなことがあるんじゃないかと心配になってしまうのです。
インターフェースを決めてから処理の内容を実装できる
ここで言うインターフェースとはメソッドの名前・引数・戻り値のことです。
例えばデータ検索を行うユースケース層で以下のような実装をしたとします。(Goで書いてます)
func Search(params entity.SearchParams) entity.DataList {
// 検索条件に合うデータのIDを取得
dataIDList := repository.Search(params)
// 取得したIDをキーに詳細なデータを取得
dataList := repository.GetDataList(dataIDList)
// データをソートして返す
return sortDataList(dataList)
}
この時点では呼び出されている各メソッドがまだ作られてないのでコンパイルエラーにはなるのですが、それでもいいので私はまずこのユースケースで必要な処理の流れというのを一気に書き出してしまいます。各メソッド内でどんな処理をするかということも詳しいところまではあまり考えてません。
メソッドにしろAPIにしろ、個人的にはインターフェースというのは呼び出すが側がわかりやすいように設計されるべきだ思っているのですが、内部ロジックを作りながらそれを考えていくと、つい内部でどんな処理をやってるかをメソッド名で表したくなったり、内部処理に都合の良いパラメータを受け取るようにしてしまうケースが(私の場合は)あります。しかし、銀行や市役所の窓口に行った時に、その後ろにいる人たちがどんなことをしてるかを私たちが知る必要がないのと同じで、メソッドの呼び出す側からしたら内部で何をしてるかはどうでもよくて、値を渡したら必要な結果を返してくれればそれでいいわけです。なので呼び出し側から作ることで、使いやすいインターフェースになるんじゃないかと思ってます。
とはいえこの方法も欠点はあって、メソッドは他の場所からも呼ばれる可能性があるため、特定の呼び出し元に特化せず、汎用的に作られているのが理想なのですが、ついつい今作ってる呼び出し元だけに都合がいい設計にしてしまうことがあるので注意が必要です。
まとめ
すごく簡単にですが、私のプログラムの書き方を紹介してみました。そんなの当たり前じゃんとか、もっとこうやった方がいいじゃんとかあるかもしれません。プログラムの書き方は本当に人それぞれだと思うので、もしおすすめの書き方があれば教えてもらえると嬉しいです。
最後に
株式会社Voicyでは一緒に働く仲間を募集しています。興味を持っていただいた方は、こちらもぜひご覧ください!