はじめまして、楽楽販売新卒エンジニアのomegumiです。
少し前に、社内で「脳に収まるコードの書き方」の輪読会が開催されました。 (輪読会とは、複数の人で同じ本を読み、その内容について意見を交わす読書会です)
初学者視点でもたくさんの学びがあったので、コーディング経験に関係なく大事そうだと感じた学びと個人的に業務で取り入れたことについて書いていきたいと思います。この記事を通して、私同様に「プログラマって何から勉強していけばいいんだ」「技術書とか設計って怖い…」と思っている方を少しでも後押しできれば幸いです。
読んだ書籍「脳に収まるコードの書き方」
今回、社内の輪読会で読んだ書籍はこちらです。
プログラマに求められる仕事と経験
本書を読んだことで得られた学びに、「プログラマの本当の仕事とは何か」「何をどう勉強していけばいいのか」があります。 結論から言うと「どういう状況で、どんな手法を選択すべきか判断する/できるようになること」だと私は思ったのですが、なぜ本書からそういう考えに至ったかを説明します。
「脳に収まるコードの書き方」から気づけた、プログラマとして目指すべき姿
私はラクスに入社するまで、チーム開発やビジネス目的の開発経験が希薄でした。そのため「プログラマとして求められる振る舞いはどんな内容か、ビジネス組織におけるプログラマの職責とは何か」を入社後によく考えるようになりました。 この書籍を読み始めるまでは、以下のような漠然とした目標で止まっていたと感じます。
- 可読性の高いコードを書く
- 変更しやすいコード設計を考える
- バグや想定外の挙動がない機能を作る
- 技術面のボトルネックや問題解決を行う
もちろん、良いコードを知り、ソフトウェアに求められる機能を早く実装できるようになることは重要だと思います。むしろ、当初の私はその手法や理想系を知るために勉強していました。 が、途中からこのような課題に直面し始めました。
======================
学んだ内容を業務で活かそうと考えていても、実装後や先輩に指摘をもらうまで「本にあった手法がハマるケース」だったことに気づけない
======================
この原因こそが、次で説明する「ソフトウェアエンジニアリングの”技”」が身に付いていないことであり、本書で「プログラマの職責」とされていた組織的な開発の鍵でした。
まず"ソフトウェアエンジニアリング"って何?
みなさん、コードを書くときにソフトウェアエンジニアリングを意識したことはあるでしょうか?私は学んだことすら(さわりしか)ありません。書籍に出てきた説明をまとめると、次のように言えそうです。
難しそうに聞こえますが、著者は 「決定論的なプロセス(=この状況なら絶対にこの手法!と断言できる方法)」は、現実の開発現場には存在しない と述べていました。つまり、ソフトウェアエンジニアリングだけを極めても、実際のプログラマの仕事として十分とは言えない、ということです。
ではプログラマとして何が必要なのか、本書からの学びを私なりにまとめたものが以下になります。
- 決定論的なプロセスを見つける(完璧なソフトウェアエンジニアリングを体現する)以上に、さまざまな技法を知り、ビジネスや状況に応じた適切な選択ができるようになることが重要。
- だからこそ、「ソフトウェアエンジニアリングの”技”」の部分で悩み、力を発揮することがプログラマの職責。
ではソフトウェアエンジニアリングの”技”とは何か?
結論から言うと、ソフトウェアエンジニアリングの”技”とは「状況を見て判断、経験で判断」することです。個人の経験や判断頼りは組織的な開発に良くないという話だったのに、一体どういうことなのか。
キーとなる著者の考えを以下に抜粋します。
エンジニアリングが完全に決定論的プロセスなら、人間は必要なくなります。コンピューターと産業ロボットがあれば十分でしょう。
(出典:脳に収まるコードの書き方p252 [原書:Code That Fits in Your Head])
適用できる山ほどの方法論があります。それでも、自分の脳を使う責任は無くなりません。スキルと適切なプロセス、経験則、技術を組み合わせることが仕事です。
(出典:脳に収まるコードの書き方p253 [原書:Code That Fits in Your Head])
上記も踏まえて、自分なりの解釈を含めてまとめるとこんな感じです。
- 現状、状況ごとに「固定の理想/ルール」となる手法はない
- でも適用できる手法(方法論)は、山ほどある
- だからこそ、「どういう論理に基づいて、その手法を選択するのか?」という思考プロセスを学ぶことが重要で、プログラマとして得るべき経験とは「状況判断と手法選択のための判断経験」
- プログラマの職責とは、「どういう技術課題に、何の手法が最適かを判断する」こと(それにより、プロダクトの品質を担保する)
つまり、「自身の脳を使い判断する」、「その判断を責任もってコード及びプロダクトに反映させる」ことが、プログラマの職責であるということです。
ここから考えると、課題だった「学んだ内容を実際のコードに活かせない状態」は、手法は学べている(=知識はある)が、それを採択するに至るために必要な思考プロセスが備わっていない(=経験が伴っていない)状態と捉えることができます。一度、「こういう時に使うのか!」という経験をしたり、「どういった理由・状況で採択される手法なのか?」という視点・思考プロセスを学ぶ必要があるということです。
小まとめ(結局何をやったらいい?)
方法論はどんどん勉強していい
(例:カプセル化,関心ごとの分離手法,〇〇駆動開発など。)
定量的視点での評価手法を学ぶ
(例:テストによる0/1,チェックリスト使用,複雑度計測など。書籍に詳しい紹介がありますので、ぜひ読んでみてほしいです)
成果物をビジネスや目的に沿ったものにする開発手法も学ぶ
(例:自動化,Gitの使い方など。こちらも書籍に詳しい紹介があります。ぜひ読んでみてください)
書籍では「組織として開発」のスタンスで話が進んだため、「ステークホルダーの納得を得やすくする」ことの重要性・方法についても各所で説かれていました。コードや技術課題に対する判断力だけでなく、ステークホルダーやビジネスサイドを踏まえた「職業プログラマとしての判断力」も身に着けていくということです。以下は、特に「プログラマの職責」とされそうな部分を、自分なりにまとめたものになります。
- ビジネスサイドの状況も鑑みて「技術的に譲れない・実装すべき部分はどこか」を判断する
- 技術的に譲れない=技術者視点でのMUSTについて、説明・納得・合意のための道筋を考える
- クリティカルな操作/機能を最速で動くようにする ⇒成果物の形を最速で業務目的に近づける(動く大枠から作る)
- ビジネスとコード双方のMUSTを担保できるラインの見極め
「脳に収まるコード」とは?
書籍「脳に収まるコードの書き方」では、組織的な開発の鍵として「人間の脳に優しい、読みやすいコード」すなわち「脳に収まるコード」を書く手法が主題となっていました。そこで、ここからは「脳に収まるコード」とはどうすれば書けるのかについて、自分のような初学者でも実践できた内容をまとめます。
関心事をどうまとめるか
読んですぐに実践できた内容として、コードを書く場所を決める際の判断手法があります。
コーディングにおける関心事とは、関心を払わなければならない変数/オブジェクトの詳細や、コードが目的とする処理タスクや機能が当てはまります。つまり、「脳の関心を惹くことと」と言い換えられます。人間の脳に優しいコードを書くためには、役割ごとに整理する手法を知ることが重要ということです。
私が書籍から学び取った、重要な判断基準は以下2つです。
- どんなものも、与える役割は「専用の機能1つ」だけ
人間の脳は7つまでしか覚えられない
※著者によると、人間が一度に覚えておける容量の限界が「7」までであり、それ以上に処理や情報が増えた場合は、詳細すぎるとして切り分けを考えた方が良いとしていました。
まとめると、コードの一塊についてそれぞれシンプルな日本語で説明できる状態を保ち、一塊に含まれる詳細は7つまでに抑えることが重要ということです。これは、メソッドやクラス、API、プルリクエストの文章などの様々な関心事に適用できるため、業務で実践できたことをデフォルメ化してお伝えしたいと思います。
例えば以下のように、チョコレート生成についてのクラスがあった場合、どのように修正すべきなのか考えてみました。
Before(関心事がまとまっていない例)
class ChocolateFactory {
public function makeChocolate($cacaoBeans) {
// 準備:カカオ豆の種類ごとに担当パティシエを決定
// 加工:カカオ豆を焙煎、チョコに成型する処理
// 確認:できたチョコの品質検査を行う処理
return $chocolates;
}
}
// チョコレートの作成・出力
$maker = new ChocolateFactory();
$chocolates = $maker->makeChocolate();
このコードには、以下のような問題があります。
1つのことを理解したい場合でも、全てを読むことになる
これは、1つのメソッド内で複数の工程について、関心事の詳細が全部入っているためです。例えばチョコ作りで通る工程をざっくり理解したい場合でも、「ここは準備の処理で、ここは確認の処理っぽい…」と順に全てを読むことになり、非常に大変です。ここから、すでに複数の役割を持たせてしまっていることがわかります。
各処理の再利用・変更がしにくい
例えば、焙煎処理だけコーヒー生成でも使いたい場合や、特殊な成型処理を追加したい場合です。
makeChocolate()
内ですべてくっついているため、新しい処理もここに書くしか無くなり、どんどん本筋の「チョコ作り」とは関係ない処理が入り込むことになります。コードの役割がどんどん増えていく原因になるということです。何をしているのか理解しにくい
現在は単純なコメントのみのため、少ない事柄を扱っているように見えます。しかし実際、DBからカカオ豆を取得したりもみ殻と分けたりといったチョコ生成処理を書いていくと、理解すべき変数・分岐条件・使用する関数などの数はかなり多くなることが予想できます。これは、書籍の「7つまで」に反しています。
そこで、今回書籍から学べた手法を使って、関心事の観点でリファクタリングしてみたコード例が以下です。
After(関心事を分離し、整理した例)
class PrepareMakeChocolate { // 準備:カカオ豆の種類ごとに、担当パティシエへ振り分けるメソッド } class Patissier { // 加工:カカオ豆を焙煎、チョコに成型するメソッド } class ChocolateFactory { // チョコ作りの工程(準備・加工)書いたメソッド public function makeChocolate(): Chocolates{} // 確認:在庫に入ったチョコレートの品質を参照するメソッド public function checkChocolateQuality($id): boolean {} } // チョコを作る $chocolateFactory = new ChocolateFactory(); $chocolates = $chocolateFactory->makeChocolate(); // 品質次第でチョコレートを提供 $chocolateFactory->checkChocolateQuality($chocolates['id']);
この変更は、以下のように可読性・再利用性の向上に貢献できます。
- 各クラスが1つの役割に集中している(準備,加工,確認)
- 各工程の影響範囲が独立している
ChocolateFactory
では工場としての役割「チョコを作って提供する」を考えて「チョコ作りの工程」「作ったチョコの確認」の2つをメソッドとして用意しました。管理や品質検証の工程は、厳密には「チョコを作る」工程ではないので、「作る」から分離しました。
もし焙煎や成型の処理に特殊な工程を組み込むパターンが発生したら、インタフェース化を検討しても良さそうです。上記コード例を書くにあたり、役割以外の判断基準として書籍から得た他の手法も実践しています。以下は、書籍でも紹介されていたかなり有名なコード設計の理論について、自分なりの解釈でまとめたものです。
関心事を分けるための要点2つ:
コマンドとクエリを分離する
システムの挙動を以下2つに大別するという考え方です。
- コマンド:他のオブジェクトの状態を変更する副作用がある
- クエリ:状態を照会する(ので、返り値がある)
上記の例で言うと、
checkChocolateQuality
メソッドは作ったチョコの状態を確認して、結果を取得するだけの「クエリ」です。 一方、PrepareMakeChocolate
内の担当パティシエへ振り分けを行うメソッドは、他のオブジェクトに担当パティシエの情報を当てがう副作用があるため「コマンド」と考えられそうです。この2つを分ける意識は、各クラス・メソッドの役割をより明確化することに役立ちます。やりとりする型と状態を保証すべき範囲を明確にし、 保証されたオブジェクト同士で付き合う
各クラスやメソッドに出入りするデータ・オブジェクトについて、「どのオブジェクトが、どんな状態を、どこまで保証するべきか」を決めておく必要があります。例えば
PrepareMakeChocolate
クラスを経由してエラーが出なかった(=PrepareMakeChocolate
内の検証に引っかからなかった)場合は、「担当パティシエが確定している状態」が保証されるべきです。
このように、関心事によってコードを書く場所の場所を判断する手法は、実際の業務でもメソッド抽出や変数の配置場所を決める時に役立ちました。
小まとめ(脳に収まるコードを書くには?)
- 関心事という観点で、クラスやメソッドを分ける
詳細すぎる情報は、カプセル化で「安全に」切り分ける
コードの一部を切り出すときには、関心事単位でまとめるだけでなく以下3点を保証することが重要です。
- 外部から送られた「データの状態」が内部ルールと沿うか検証する
- 外部に返す「データの有無・状態」を明確にする
- 外部と内部を分けるため、公開範囲の制限を行う
覚えておきたい考え方:カプセル化で実装の詳細を隠蔽する
(出典:脳に収まるコードの書き方_p128 [原書:Code That Fits in Your Head])
最後に
今回の輪読会参加で自分にとって最もプラスだったのは、下の2つです。
- 今後の業務/勉強から、どんな学びを得ていくべきかが具体化されたこと
- コード設計を取り巻く、様々な手法まで興味が広がったこと
「ソフトウェアエンジニアリングとかテスト駆動開発って、よく聞くけど専門的すぎて自分にはまだ難しそう…」「設計や上流の知識が全然ない!そもそも何から勉強していけば良いの?」と感じていた私にとって、コーディング面のテクニックだけでなく「何をどう勉強すべきか」を考え直すきっかけとなった当書籍による輪読会への参加は、非常に大きな意義があったと感じています。
書籍に「初学者向けではない」とあるだけに、輪読会への参加前はためらいもありました(実際難しかったです)が、なんとか読み切った時には達成感もひとしおでした。今後、難しい書籍への挑戦や勉強会に参加する心理的ハードルも下がったと感じます。
ちなみに、私は本書がきっかけで「テスト駆動開発」に興味を持つに至りました。理由としては以下です。
- 切り分けたクラス/メソッドの検証に深いつながりがある
- プログラマが担うべき品質担保へ直結する
- 書籍で、開発の進め方における”あるべき”とされていた「小さく書き進め、常に動く状態を保つ」に大きな効力を発揮する
プログラマの職責である「判断」での引き出しを増やしていくためにも、輪読会で下げてもらったハードルを飛び越え「テスト駆動開発Kent Beck (著), 和田 卓人 (翻訳)」を読むなどしていきたいと思います。
また今回は盛り込めませんでしたが、「脳に収まるコードの書き方」には他にも多くのコーディングテクニックや開発改善の手法も書かれています。もちろんベテランの方にもおすすめの書籍だと思われますが、私のような初学者の方でも大量の学びがあると思いますので、ぜひ本書に挑戦してみていただきたいです。
本記事が、少しでもなんらかの興味・学びのきっかけとして届けば幸いです。
*1:出典:脳に収まるコードの書き方p37 [原書:Code That Fits in Your Head]