これは何?
『レガシーコードからの脱却』第 9 章で紹介された CLEAN コード の学びやかんがえたことを書きます。
📚 コードの品質指標 CLEAN とは
ロバート・マーチンが『Clean Code: アジャイルソフトウェア達人の技』で提唱した、コードの品質の指標についてのアクロニム(頭文字)
それぞれ、
- 凝集性 :Cohesive
- 疎結合 :Loosely Coupled
- カプセル化:Encapsulated
- 断定的 :Assertive
- 非冗長 :Nonredundant
簡単にいうと、
- クラスは 1 つのことだけをする(凝集性)
- はっきりした責務を担う(疎結合)
- 実装は隠蔽されているべき(カプセル化)。
- オブジェクトの状態は自分自身が管理する(断定的)
- オブジェクトの定義は一度だけにすべきだ(非冗長)
という原則。
高品質のコードは 凝集性 が高い
それぞれの部品(関数、クラス…)は、一つのものだけを扱う。 それぞれの部品が自分の責任に集中し、自分の仕事をうまくやるように設計する。
こうすれば、何か 1 つの変更が必要が生じた時に、対応するクラスだけ変更すればよくなる。 変更による影響調査とテストが簡単になる。
そのようなクラスには、その責任を表す端的な命名が可能である。もし命名できないなら、色々やらせすぎている可能性がある。
高品質のコードは 疎結合 である
オブジェクト間の関係を明確な意図を持った状態に保つことを「疎結合」という。
疎結合なコードは、それを利用しているコードに対して 間接的にしか依存しない 。よって、分離、検証、再利用、拡張が楽になる。
疎結合は通常、間接的な呼び出しによって実現される。サービスを直接呼び出すのではなく、中間層を通じて呼び出す。 これにより、サービスに変更があっても呼び出し元の変更の影響を減らすことができる。
参考 👉 「コードの疎結合性」と「依存性逆転の原則」について書いた雑記
高品質のコードはカプセル化されている
実装の詳細は、外部の世界からは見えなくなっている。
カプセル化 とは、以下を切り離すことである。
- インタフェース(自分がやろうとしていること)
- 実装(どうやってやるか)
中で何やっているかを隠蔽し、入出力だけを公開するので、
- 他のコードに影響を与えずに自由にコードを変えられるようになる
- コードがモジュール化されるので扱いやすくなる
などのメリットがある。
良いカプセル化のためには、以下のの 2 つの視点が役に立つ:
アウトサイドインプログラミング
コンシューマの観点で機能を設計する。サービスはクライアントのニーズに基づいて設計される。 サービスが何をやっているかを示す名前をつけ、それがどう動くかは隠す。 これによって、サービスの高レベルの結合を意識できるようになる。
インサイドアウトプログラミング
問題を小さなかたまりに分解し、それらを縫い合わせて 1 つのソリューションを作るというもの。 実際にコーディングにいきつくまでに必要になる。ただし、全体像への配慮が欠けると責任が明確でない壊れやすいコードが生まれがち。
コンポーネントは、何なのか、何のためにあるのか から始める。
こうした高レベルの視座があれば、エンジニアリングのバックグラウンドがないドメインエキスパートとのコミュニケーションも可能になる。 詳細を切り離しておけば、チーム全体でシステム化対象の理解がしやすくなり、コミュニケーションコストも下がるし、柔軟性、一貫性の向上に役立つ。
ソフトウェアがどう作られているかではなく、ソフトウェアを使うことによって得られる体験から始める。
カプセル化の方法
- 概念を抽象化して隠す
- メソッド化する
- インタフェースを導入する
ソフトウェア開発においていつでも通用する「知らぬが仏」パターンがある。依存が少なければ少ないほど、コードの変更は簡単になるため。
しかし、カプセル化したものを公開する必要がある時もある。その時は、必要に応じて小出しに公開する。
とりあえずクラスのデータを private
で宣言しておいて、必要があれば getter
, setter
などでアクセスできるようにする。
カプセル化を習慣化することで、呼び出し元を意識して設計するようになる。入出力が明確になり、システムの相互作用における副作用が減り、 コードを説明する明確なドキュメントにもなる。
メソッドシグネチャなどは、カプセル化の良い方法である。これは「実装ではなくインタフェースをプログラムする」というパターンである。
高品質のコードは断定的である。
⭐️ 断定的とは? 自分が何をやるコードなのか、自分の責任はどこなのかはっきりしていること。
他のオブジェクトとの依存性が多い「好奇心旺盛なオブジェクト」は、常に何らかのオブジェクトを参照する必要があり、いずれそれは人で追えなくなる。
これは先ほど述べた カプセル化 の原則とも反する。 なので、オブジェクト自身で状態を管理できる方が良い。
こうした問題はなかなかわかりづらいので、普段から適切な場所に移すようにした方が良い。
高品質なコードは冗長でない
ミッションクリティカルなアプリケーションは冗長でも良い。人の命がかかっているから。そしてこれは、意図的な冗長である。
CLEAN の文脈では、意図しない冗長 について考える。c.f. DRY 原則 (Don’t Repeat Yourself)
長ったらしいコードがあって、それを別の場所で使いたい時、コピペした方が良いだろうか?いや、関数名をつくって使いまわした方が良い。
📚 余談
『リーダブル・コード』では、
/util
(ユーティリティ用のコード置き場)を用意すると良いって言ってた。
コードを書いた当事者なら、「ああ、これはこのパターンをコピペしたんだな」とすぐわかるが、時間が経てば一目ではわからなくなる。
ちゃんと一つの関数にまとめておけば、そんな労力は必要なかったのに〜ということになりかねない。
1 つで済むもの冗長になっていると、テストもビルドも読む時間も冗長になってつらくなる。できるだけまとめて抽象化しよう。
コード品質が私たちを導いてくれる
CLEAN コード の原則は、ちまちましたものかもしれない。でも、これらは、
- オブジェクトははっきりと定義された特徴を持つ
- オブジェクトは自分の責任に注力する
- オブジェクトの実装の詳細は隠されている
- オブジェクトの自身の状態は自分で管理する
- オブジェクトは一度だけ定義される
という恩恵を与えてくれる。
長ったらしいが、
- コードに凝集性があれば、理解もバグを見つけるのも簡単になる。それぞれのエンティティは 1 つのことしか扱っていないから。
- コードが疎結合であれば、エンティティ間の副作用が起こることも少なくなる。テスト・再利用・拡張がより簡単になる。
- コードがカプセル化されていれば、複雑さを管理し、呼び出し元がその先の詳細を知らなくても良いように維持できる。詳細を変更しても、呼び出し元に変更は必要ない。
- コードが断定的であれば、振る舞いを配置する場所が、依存データがある場所であることを示す。
- コードが冗長でないなら、バグ修正や変更を 1 箇所で 1 回だけやればよい。
これらの原則は、相互に独立しているわけではないので、どれかを達成すると他のも達成できるような感じになっている。 コードをチェックするときの観点として 1 個 2 個妥当そうなものについて着目すると良さそうである。
筆者が好きな原則は Cohesive(凝集性) だそうだ。これは簡単に見つけて直せる。コードがやっていることに名前をつけることを 手助けしてくれる。
明日のベロシティのために今日品質を上げる。
技術的負債
技術的負債とは、開発中に学習したことをコードに反映しなかったときにおこる「負債」を説明するものだ。
その時に対応しておけばすぐ終わるものを、先延ばしにすることで、同じリファクタリングをするのに数倍もの時間がかかってしまう。 コードの知識が失われてしまうためだ。
「確かに、カプセル化のために新しいクラスを作って波カッコを余分に入力しなければいけないかもしれないが、これまでソフトウェア開発において タイピングがボトルネックになったことなどあるだろうか?」
コード品質を上げる 7 つの戦略
人によって品質の定義は様々だ。やはり、開発の前に、品質について合意が取れておくのが良い。
1 品質の定義を明確にする
高品質のコードとはどういう特徴を持つべきか、確認する。
2 品質のためのプラクティスを共有する
どうやったら、高品質を維持できるか。そのプラクティスについて考えて、共有する。
3 完璧主義を手放す
- 完璧に作るのは不可能である。
- 限りある、必要最小限の受入基準をもっておけば、作り込みすぎる(金メッキ)を避けることができる。
4 トレードオフを理解する
開発者だけでなく、ステークホルダーを把握するなどプロジェクトの全体像を把握し、どこにどういうトレードオフが存在するか理解する。
5 「やり方」を隠す
実装の詳細をカプセル化し、インタフェースを公開する。呼び出し元は、どうやって実現されるかを気にせず、欲しいものが得られるようにする。
これよって、開発者としては、あとから実装の詳細を変更する自由が得られる。 もし、呼び出す人が実装の内部に依存したものをすでに作ってしまった場合、彼らに配慮する必要が出てくる。
6 良い名前をつける
プロジェクトで最も重要なドキュメントは、ソフトウェア自身だ。
- エンティティやふるまいには、「何をやっているのか」を示す名前をつける。
- 命名には一貫性を持たせ、略語や一部のメンバーしかわからない造語は避ける。
- 名前は長くても良い。ソフトウェア開発でタイピングはボトルネックにならない。
- 「説明的」「能動的」「肯定的」な命名をする
7 コードをテスト可能に保つ
テストコードが動くだけでなく、すぐいつでも動かせること(いろんな前提条件がないと動かないとかはダメ)。
保守しやすいコードを書く 7 つの戦略
保守しやすいコード = 理解しやすく扱いやすいコード
コードの共同所有を取り入れる
- チームメンバーの誰もがコードベースに変更を加えられるようにする。
- コーディング規約を共有し、一貫性を持たせる。属人性を排除する。
- 共通のドメインモデルを使って作業し、共通の開発プラクティス、設計を表す共通の用語を使う。
リファクタリングを熱心に行う
- リファクタリングは絶対常に行う。
- コードについて学んだことを今反映させなければ、「負債」となり、今後の時間の大多数を「新規開発」ではなく「負債の返済」にあてることになる。
常時ペアで行う
- ペアプロが、知識の伝達・維持に有効である。
- 毎日でもいいからペアを変える。
- 少なくとも「設計」「コーディング」「リファクタリング」「デバッグ」「テスト」の際にはペアでやるべき
頻繁にコードレビューする
- ペア以外でもいいからレビューを頼む
- なぜその方法を選んだのかの意思決定の理由に着目し、設計の選択肢やトレードオフについて議論する
他の開発者のやり方を学ぶ
他人のコードを読んで、他人の書き方を学ぶ。これはスキル向上の良いプラクティスである。
ソフトウェア開発を学ぶ
- 20 年前はソフトウェアの本なんてほとんどなかったが、今はたくさんある。
- 医師は週に 8~10 時間勉強している。開発者も同じだ。
コードを読み書きして、コーディングの練習をする
スティーブン・キングはこう言っている
「作家になりたいなら、絶対にしなければいけないことが 2 つある。たくさん読み、たくさん書くことだ」
本章の振り返り
コードを読む時間は書く時間の 10 倍と言われている。「CLEAN」なコードを書けば、その時間を節約できる。
本章のまとめ〜CLEAN コード〜
- 凝集性:似たコードを一つにすることで副作用を減らせる
- 疎結合:テストや影響範囲の特定が容易になる
- カプセル化:実装の詳細を隠し、拡張がしやすくなる
- 断定的:責任を明らかにし、ソフトウェアがモジュール化される
- 非冗長:保守の問題を減らす。