書籍: 「良いコード/悪いコード設計入門」📕
書籍 「良いコード/悪いコード設計入門」についてまとめる。
この書籍はジュニアレベルエンジニアにおすすめである。本書の著者 ミノワ駆動さんもおっしゃていたが新卒入社しこれからエンジニアとして業務を遂行する人やジュニアレベルのエンジニアにおすすめの書籍であると述べている。
これを私なりに具現化すればプログラミングの基礎 「順序、条件、繰り返しのアルゴリズム理解」やほとんどのプログラミング言語で「共通の書き方 for文や switch文、if文、while文」などを理解し使える人は次のステップとして読んだ方がいい。これが活用できれば、効率の良いコードを書ける一歩になると思う。
- レビュー数/PRがめちゃくちゃ多く、設計についての考え方を理解せず、コードを書きがちな人
- 設計について学びたい人
本書を読み終えた私が思うに、この内容は基本的なことを理解し、どんなコードや設計が技術的負債、可読性が落ちるなど、エンジニア、開発にとってデメリット生じるかを詳しく書かれており、解決策として上記の基礎を組み合わせて解決してく流れや基本的な書き方が基礎となっているので上記のようなターゲット層になると判断した。
※ 前提: こちらの書籍はオブジェクト指向型言語を扱っている書籍なので、オブジェクト指向型言語を学習、業務で実装している人が望ましい。
ところどころの例はPHP言語で例を書くようにする。
まとめきれていない部分もあるが、必ずこちらの記事はまとめ上げる。こちらの書籍は実装や設計の掟です!
2章 設計の初歩
ここでは大きく分けて2つの要点をわかりやすくする!
-
変数の定義
-
Class
をうまく活用するコツである -
変数
-
変数は大規模な開発であればあるほど無水に存在するが、変数名をみただけで**何の変数であるか 理解できない変数は可読性が低く、無意味になってしまう!**そのため、きちんとした変数名を定義しよう!
-
また再代入について
再代入することはPJTにあると思う。実際に大規模なECサイトでは存在していた!
商品の合計金額を条件に応じて変更する!
例えば、あるセール品は定価の何%引きの価格などという形で再代入していた!あまりよろしく無いという!再代入すれば、どのような計算がなされて再代入されてるからパッと見極めにくいからである。
- CLassの使い方
- Classの使い方である。関係し合うデータと処理はclass単位でまとめるとよりわかりやすい、
似てる処理なのに、乱雑するとそれだけ呼び出し先に一定のルールが存在せず、可読性が落ちる。
故に、関係し合うデータとロジックは同じクラスにまとめよう!
3章 クラス設計
クラス設計については要になるので、よりシンプルに書こうと思う。そこで先に要点を述べると重要なポイントは4つである。
- クラス設計は、オブジェクト指向と密接の関わり
- オブジェクト指向はソフトウェアの品質向上を目的とする考え方の一つ。
- 保守や運用がしやすいコードを書くには関心の分離が重要。
- クラスが単体で正常動作するよう設計する必要がある!
詳細は以下である。
-
クラス設計とオブジェクト指向の関係
クラス設計は、プログラムの中でどのようにデータや機能を整理して使うかを決める方法です。この設計は、「オブジェクト指向」と呼ばれる考え方と深くつながっています。オブジェクト指向は、プログラムを作るときに、現実世界の物や人をプログラムの中で「オブジェクト」として表現し、それらのオブジェクトが協力して働くように作る考え方です。クラスはそのオブジェクトを作るための「設計図」みたいなものです。 -
オブジェクト指向の目的
オブジェクト指向の目的は、プログラムをわかりやすくして、後から修正したり、機能を追加しやすくすることです。つまり、最初にしっかりと設計することで、プログラムを「より良い品質」に保つことができます。たとえば、ゲームのキャラクターを作るなら、キャラクターを「人間」としてクラスを設計すれば、どんなキャラクターもその設計図に従って作れるようになり、新しいキャラクターを追加するのも簡単になります。 -
保守や運用がしやすいコードを書くには関心の分離が重要
プログラムを長く使い続けたり、何度も修正していくためには、プログラムの中で「関心の分離」を意識することが大事です。これは、プログラムの機能を小さな役割ごとに分けて、他の部分に影響を与えずに変更できるようにする考え方です。たとえば、車のプログラムを作るときに、エンジンの動きとタイヤの動きを別々に設計しておけば、エンジンを修理する際にタイヤのプログラムをいじる必要がありません。 -
クラスは単体で正常に動作するように設計する必要がある
最後に、クラスは他のクラスや部分に頼りすぎずに、それ単体で正しく動くように設計する必要があります。これによって、ある部分に不具合があっても他の部分には影響が出にくくなります。たとえば、コンピューターのマウスをクラスとして設計した場合、そのマウスのクラスはキーボードや画面の動きに影響されずに単独で動けるように作るべきです。
「クラスが単体で正常に動作するように設計するための」クラス構成要素は2つ!
- インスタンス変数
- メソッド
どちらが欠けてもいけない。すべてのクラスがすべてのクラスの自己防衛責務を備える考え方が必要だからです。
クラスは単一の責務を常に持っていなければならない。そのクラスが存在する意義を明確にしなければ
クラスが破壊される。
Ex.
車の例を挙げるとエンジンのClass、タイヤのClass、ボーンネットのClassなど車は複数のパーツで出来上がり始めて成立する。
これらのClassが単一責務を補っていない場合、不具合が生じるだろう。例えば、エンジンが何らかの理由(故障など)で責務を全うしなければ車は正常に機能しない(動かない)ですね。
ないしは車の概念が無くなるということだ。
このイメージができれば、下記の質問についても答えられるのではないでしょうか?
Q. なぜ、コンストラクタで確実に正常値を設定しないといけない?
A. 私の答えはコンストラクタはClassの出入り口部分であり、そのClassが何をするのか、明確な要素を定義する場所だからである。
仮に、コンストラクタ内に異常値が設定された状態であれば異常のClass(オブジェクト≒車など)ができ上がってしまうので正常値を設定が必須である。詳細に理由は以下だ。
-
オブジェクトの状態を安定させるため
オブジェクト指向プログラミングでは、オブジェクトが持つデータ(プロパティや属性)は、そのオブジェクトの状態を表します。コンストラクタで正常な値を設定することで、オブジェクトが作成されたときに一貫した状態になります。これにより、プログラムが予測可能に動作します。 -
エラーの防止
もしコンストラクタで不正な値(例えば、年齢にマイナスの数値など)を設定できると、その後の処理でエラーが発生する可能性があります。例えば、年齢がマイナスだと、年齢を使った計算や表示で意味不明な結果になることがあります。正常な値を保証することで、こうした問題を防げます。 -
コードのメンテナンスがしやすくなる
正常値を設定することによって、他の部分のコードがそのオブジェクトを使いやすくなります。例えば、他の部分で「このオブジェクトは必ず年齢が0以上である」という前提でコードを書くことができます。これにより、後からコードを修正する際にも安心です。 -
デバッグが容易になる
オブジェクトの状態が一貫していると、バグを見つけやすくなります。例えば、何か不具合が発生した場合、正常な値が使われているかどうかを簡単に確認できるため、問題の特定がスムーズになります。
~ 不変で思わぬ動作を防ぐ ~
**1. 変数を変えないとどう良いか(イミュータブルの考え方)**
プログラムで変数というのは、数字や文字などの値を一時的に保存するための「箱」のようなものです。この箱の中身(値)は、普通は必要に応じて変えることができます。でも、変わるたびに「いつ変わったのか」「今の値が何か」などを常に確認する必要があって、間違えやすくなります。たとえば、ゲームでスコアが勝手にリセットされてしまったら困りますよね?
そんなトラブルを防ぐために、**「イミュータブル」**という仕組みを使います。「イミュータブル」とは「変わらない」という意味で、一度決めた値を変えられなくする方法です。たとえば、「final」というキーワードを使うことで、変数の値が変更されないようにロックできるんです。これにより、予期しないエラーやバグを防げます。
**例**:
ゲームのキャラクターの名前は一度決めたら変わらないものと考えて、`final`を使って名前の変数を固定すれば、間違って名前が変わることがなくなります。
~ メソッド引数やローカル変数にもfinalを使うと良い! ~
**1. メソッドの引数やローカル変数にも「final」を使う**
プログラムの中では、関数(メソッド)にデータを渡す「引数」と呼ばれるものがあります。これも、値が途中で勝手に変わってしまうと、どう変わったかを追いかけるのが大変です。引数は基本的に変えないことが良いとされているので、「final」を使うことで引数が変わるのを防ぎます。こうすることで、バグの発生を防ぎ、プログラムが正しく動くようになります。
**例**:
学校の出席者リストを作るプログラムで、渡された生徒の名前が途中で変わったら困りますよね?名前が途中で変わらないようにするために「final」を使うと安心です。
~ 現実味のないメソッドはむやみに追加しないこと! ~
3. 不要なメソッドは追加しないこと
プログラムを書いていると、あまり使わない機能(メソッド)をつい追加してしまいがちですが、それが混乱の元になることがあります。不要な機能が多いと、プログラムの見た目がごちゃごちゃしてわかりづらくなり、バグを引き起こしやすくなります。だから、本当に必要なメソッドだけを書くのが大切です。
**例**:
例えば、自転車のプログラムを作るなら、「タイヤを回す」という機能が必要です。でも、「タイヤを光らせる」という機能は自転車には現実的じゃないので、むやみに追加しない方が良いということです。
~ クラス設計のまとめ クラス要素の扱い方 ~
4. クラス設計のポイント
クラスの設計を考えるときは、「高凝集化」や「不正状態の防止」などが大事なポイントです。高凝集化とは、クラスの中の機能がしっかりとまとまっていて、余計なものが混じらない状態のことを指します。こうすることで、プログラムがわかりやすくなり、不具合を減らすことができます。また、クラスが不正な状態にならないようにすることも重要です。たとえば、プレイヤーのHPがマイナスになるようなことが起きないようにします。
**完全コンストラクタと値オブジェクト**もオブジェクト指向の基本的な構造の一つです。完全コンストラクタとは、オブジェクトを作るときに必要なデータをすべて指定し、その後はデータを変更できなくする方法です。これにより、プログラムが安全に動作します。
**例**:
たとえば、キャラクターを作るときに名前や体力など、必要な情報を最初に全部決めておけば、後で勝手に名前が変わったり、体力が不正な値になることがなくなります。これが「完全コンストラクタ」の考え方です。
高凝集化したり、不正状態から防護したりする
設計パターンについて
ポイントは、完全コンストラクタと値オブジェクト
この要素はオブジェクト指向設計の最も基本形を体現した構造の一つ。
完全コンストラクタと値オブジェクトについてわかりやすく教えて
4章 不変の活用
変数の値を変更するなど状態を変更する
ミュータブル
一方状態変更できないことをイミュータブル
再代入は返答の意味が異なり、推測を困難にする
また、いつ変更されたのか追うのが難しくなる!
前回言ったように、再代入は誤解してバグを生む可能性もある!そのため、再代入は避けるべき。
不変にして再代入を防ぐべき?
再代入を機械的に防ぐ、良い方がある。ローカル変数にfinal修飾子をつけるfainalを付与すると不変になり、変更できなくなる。引数も不変にしたい場合
は、fainalをつけよう!
引数も不変にする
5章 低凝集 ―バラバラになったモノたち―
データとロジックの関係性の強さを表す指標
staticメソッドはインスタンス変数を使えない
staticメソッドはインスタンス変数を使えない
staticメソッドを持ち出した時点で、メソッドとデータが乖離する。どうしても、低凝集にならざるを得ない。
しかし、コンストラクタにクラスと関連するデータを定義すると高凝集になる!これが基本。
~ インスタンス変数を使う構造に作り変える ~
クラスと関連するメソッド(処理)をまとめて書くと高凝集な構造になる!
~ どうしてstaticメソッドが使われるのか? ~
-
手続き型言語 C言語などの考え方が影響している!手続き型言語ではオブジェクト指向言語で設計すると、データとロジックが別々になるように設計される。staticメソッドはclass
オブジェクトをインスタンスせずにstaticメソッドとして定義できる。このように、気軽に使えるが注意が必要! -
~ どういうときにstaticメソッドを使えば良い? ~
-
凝集度に影響がない場合に、staticメソッドを使う!簡単に言えば、ログ出力用メソッドやフォーマット変換用メソッドなど、凝集性に無関係のものにstaticめそっどがつかわれる!
-
初期化ロジックの分散
十分にクラス設計しても、初期化ロジックなあちこちに分散して低凝集になることがある!
共通処理クラスの置き場所によって低凝集に陥る可能性について、税込金額の処理を例に説明します。
-
凝集性とは?
凝集性は、クラスやモジュールがどれだけ一貫した役割を持っているかを示します。高い凝集性のクラスは、特定の目的に集中していて、わかりやすいです。低い凝集性は、さまざまな目的の処理が混在している状態です。 -
共通処理クラスとは?
共通処理クラスは、複数の場所で使う処理をまとめたものです。例えば、税込金額の計算を行うクラスです。 -
税込金額の計算
税込金額を計算するには、税抜価格に税金を加えます。計算式は次の通りです。
税込金額 = 税抜価格 × (1 + 税率)
- 置き場所の問題
-
適切な場所: 税込金額を計算するためのクラスを、経済や会計に関連するモジュールに置くと、役割が明確で高い凝集性を持つことができます。
-
不適切な場所: しかし、もしこのクラスを、全く関係のない処理(例えば、ユーザー管理や画像処理)のクラスに置くとどうなるでしょう?その場合、税込金額の計算だけでなく、他の無関係な処理も含まれてしまいます。これが低凝集の状態です。
- 低凝集の問題
低凝集になると、以下のような問題が発生します。
- 理解が難しい: 税込金額を計算するために無関係なコードを探さなければならず、時間がかかります。
- メンテナンスが大変: 他の処理が混ざっているため、変更や修正が難しくなります。
- バグのリスク: 無関係な部分を変更した際に、予期しない影響を与えることがあります。
~ 多すぎる引数がなぜ問題なのか? ~
- 結論、引数がある分だけ処理が存在するからである。処理があればあるほど、そのクラスは膨張する。ロジックが複雑化したり、重複コードが増えたりして可読性が落ちる。
~ 意味ある単位ごとにクラス化する ~
概念的に意味のあるクラスをつくることが肝要。
クラスに関係のある変数はインスタンス変数として定義しておく必要がある!
工夫ポイントとして、privateをつけて外部からアクセスされないように定義することも重要!
~ メソッドチェイン ~
メソッドチェインはその名の通り、メソッドを数珠つなぎのようにつながる実装方法である!数珠つなぎのようにしているメソッド、処理の仕様が変わると、その度にバグが生じる!
影響範囲がいたずらに拡大可能な構造なので、グローバル変数と同様の性質を持っている。あらゆる箇所からアクセス可能な点であるため、単一のグローバル変数より悪手。デメテル法則と呼ぶ。
~ 尋ねるな、命じろ ~
「尋ねるな、命じろ」という言葉は、ソフトウェア設計やプログラミングにおいて、オブジェクト間のやり取りのスタイルを示すものです。この考え方は、特にオブジェクト指向プログラミングに関連しています。
「尋ねるな、命じろ」の意味
1. **尋ねる**: あるオブジェクトが他のオブジェクトの状態を問い合わせること(例: `getSpeed()`)。
2. **命じる**: あるオブジェクトに対して具体的な命令を出すこと(例: `accelerate(10)`)。
この考え方は、次のような理由から重要です。
- 理由
1. **カプセル化の強化**: オブジェクトの内部状態を直接操作するのではなく、メソッドを通じて行動を命じることで、オブジェクトの内部実装を隠蔽できます。これにより、オブジェクトの設計変更が他の部分に影響を与えにくくなります。
2. **責任の明確化**: オブジェクト自身が自身の状態を管理し、必要な操作を自分に対して命じることで、各オブジェクトの責任が明確になります。これにより、コードの可読性や保守性が向上します。
3. **結合度の低減**: オブジェクト同士が互いに詳細を知る必要がないため、システム全体の柔軟性が増します。変更が必要な場合も、影響を受けるオブジェクトだけを修正すれば済むことが多くなります。
具体例
以下は簡単な例です(Pythonを使用)。
class Car:
def __init__(self):
self.speed = 0
def accelerate(self, amount):
self.speed += amount
def get_speed(self):
return self.speed
class Driver:
def drive(self, car):
car.accelerate(10) # 命じる
# car.get_speed() # 知る必要はない
my_car = Car()
driver = Driver()
driver.drive(my_car)
print(my_car.get_speed())
ここでは、Driver
はCar
の速度を尋ねることなく、accelerate
メソッドを呼び出すことで、車の速度を増加させています。このアプローチにより、Car
クラスの内部の実装に依存することなく、ドライバーの行動を定義できます。
- 結論
「尋ねるな、命じろ」は、オブジェクト指向設計において、オブジェクトの内部状態に依存せず、オブジェクトが自分自身の行動を管理することを促す重要な原則。この原則を守ることで、ソフトウェアの可読性、保守性、柔軟性を高めることができる。
6章 条件式
- 条件分岐の書き方について
条件分岐を書く際に、無闇にif文を使ってはいけない!
なぜならネスト化し、可読性が落ちるからだ。従って、条件分岐を書く際はシンプルに書く!
if(is_null($product) {
} else if ($product) {
if($this->product->isSall() ) {
}
}
これだと、名前がシンプルなのでわかりやすいが、価格の計算だったり、独自のビジネスモデルがわからないと、瞬時に理解できないネスト日された条件分岐は可読性が落ち、よろしくない。
ネスト化された構造を解決する方法としてswitchがある!switch文は
caseごと、つまり条件分岐に応じてまとめて処理が書ける!
switch($product) {
case : sale
.........
case : public
............
case : default
................
}
のようにかける!!先ほどと比べれば、シンプルでわかりやすい。可読性が上がる。
なので、解消法は単一責任選択の原則の考え方が重要。
『ソフトウエアシステムが選択肢を提供しなければならないとき、そのシステムの中の一つのモジュールだけがその選択肢のすべてを把握すべき。』
端的に説明すれば、同じ条件式の条件分岐を複数書かず、一箇所にまとめよう!という原則。
だが、switch文もデメリットがある!
それはcaseごとに当てはめる処理内容が多ければ多いほど膨張する!そこで、巨大化したクラスは関心ごとの小さなクラスへ分割することが肝要。
switch($product) {
case : sale
// 価格の計算
.........
// セール品なので購入制限数/人
//決まったセール品数が売り切れ時は元の価格にもどす
case : limited(限定数)
// 価格の計算
............
// 限定品なので購入制限数/人
// 売り切れたら、sold outを表示する処理など
case : default
................
}
のように複数の処理を書かないといけない。これもよろしくない。
- 方法は、interfaceである!
interfaceはJavaなどのオブジェクト指向言語特有の仕組みで、昨日の切り替えや差し替えを容易にできる!
interfaceを使うと、分岐ロジックを書かずに分岐と同じことが実現可能。
7章 コレクション ―ネストを解消する構造化技法―
自前でロジックを作成しなければいけない場合があるがこの場合、ロジックの実装の可読性が落ちた!
例として、for文の中にif文があるコードである。
それぞれの分岐で複雑になりがち!これを解決するのは、早期 continue
を適用する!
~ 低凝集なコレクション処理 ~
ECサイトでamazonのようなたくさんのジャンルが買えるサイトがあったとする!
この合計算出計算ロジックがあるclassで計算されているが、特別なセールの場合は別のクラスで同じ内容のロジックがかかれていたとする! これがコレクションによる低凝集にあたる!
つまり、同じ括りにできそうなメソッドが複数のCLassに分散されており、低凝集に値する。
この場合、どうしたらよいか??
- コレクション処理をカプセル化
- コレクションの低凝集性を解決する一つにファーストクラスコレクションがある!これはコレクションに関するロジックをカプセル化する設計パターン。
- クラスには以下の2つが備わっている
- インスタンス変数
- インスタンス変数を不正状態から防御し正常に操作するメソッド
この考え方の応用で次の要素を備える
- コレクション型インスタンス変数
- コレクション型インスタンス変数を不正状態から防御し正常に操作するメソッド
このようにコレクションに関するロジックをカプセル化することで
コレクションとコレクションを操作するロジックが一つのクラスにギュッと凝集する構造になる!
8章 密結合 ―絡まって解きほぐせない構造―
..... Later .....
9章 設計の健全性をそこなうさまざまな悪魔たち
~ デッドコード ~
-
デッドコード。デッドコードは使われていないのに、実装されているコード。これは可読性がおちたり、バグが陥りやすい。可読性はなぜこれがそんざいしてるのか、ずっと考えなければいけなくなり、可読性おちる!!
-
使われていないコードを参照してロジックを書くなどが起こる可能性があるからだ!仕様が決まってる部分だけ書こう!
~ YAGNI原則 ~
- 必要のないコードがあると、可読性が低下し、読み手を混乱させる!
~ マジックナンバー ~
- マジックナンバーも意味不明なマジックナンバーがあれば可読性がおちる!
マジックナンバーをかかないようにするには、定数として定義する!
~ 文字列型執着 ~
文字列型執着(string obsession)とは、プログラミングにおいて、文字列データを過剰に使用したり、他のデータ型に変換することを避けて文字列のまま扱うことを指します。これは、特にPHPのように動的型付けの言語ではよく見られる現象です。
具体例
例えば、数値を文字列として扱った結果、意図しない動作を引き起こすことがあります。
<?php
// 文字列としての数値
$price = "1500"; // 文字列型
$taxRate = "0.1"; // 文字列型
// 消費税を計算する関数
function calculateTax($price, $taxRate) {
// 文字列のままで計算
return $price * $taxRate; // ここで文字列同士の演算が発生
}
// 税込み価格を計算
$tax = calculateTax($price, $taxRate);
$totalPrice = $price + $tax; // 文字列同士の加算
echo "税込み価格: {$totalPrice}円\n"; // 結果が意図しないものになる可能性
?>
- データ型の誤用:
taxRateは、数値として扱うべきなのに、文字列型として定義されています。priceと
PHPは動的型付けのため、文字列としての演算が行われますが、これにより意図しない結果が生じる可能性があります。- 意図しない動作:
calculateTax関数内で$price * $taxRateが計算されると、PHPは自動的に文字列を数値に変換しようとしますが、明示的な型変換がないため、結果が予期しないものになることがあります。
最終的な出力である$totalPriceも、想定通りの計算結果にならない可能性があります。
- 意図しない動作:
- 改善策
文字列型執着を避けるために、数値として扱うべきデータは明示的に数値型に変換することが重要です。以下のように修正できます。
<?php
// 数値としての価格
$price = 1500; // 数値型
$taxRate = 0.1; // 数値型
// 消費税を計算する関数
function calculateTax($price, $taxRate) {
return $price * $taxRate; // 数値として計算
}
// 税込み価格を計算
$tax = calculateTax($price, $taxRate);
$totalPrice = $price + $tax; // 正しい加算
echo "税込み価格: {$totalPrice}円\n"; // 期待通りの結果
?>
~ グローバル変数 ~
- 多くのロジックでグローバル変数を参照し、値を変更していると、どこで、どのタイミングで値が変わっていたか把握が非常に困難。
グローバル変数を参照しているロジックに変更が起こりそうであれば、バグが生じないか慎重に検討しないといけない。
検討の結果、排他制御が必要な場合も生じる!
排他制御は慎重に設計しないと、デッドロックに陥る可能性がある!
さらに排他制御に関しては巨大データクラスはグローバル変数よりも悪質。
排他制御したい変数が一個でも他のインスタンス変数までデッドロックしてしまう恐れがある。グローバル変数をつかってなくても、グローバル変数と同質のものを知らず知らずのうちに使っているので注意が必要!!
グローバル変数を使う際の注意点
- 影響範囲が大きいので、グローバル変数は注意が必要!いかに影響範囲を小さくすることによるメリット記載
- 無関係なロジックからはアクセスできないように設計
- 呼び出し箇所が少なく、局所化されているほど、ロジックの理解が容易になる
- 正しく動作するロジックを実装しやすくなる
~ null問題 ~
例えばnullが入り込む前提でロジックを書いてしまう実装!
例の処理については以下である
<?php
// 商品データの配列
$products = [
1 => ['name' => 'Tシャツ', 'price' => 1500],
2 => ['name' => 'ジーンズ', 'price' => 3000],
3 => ['name' => 'スニーカー', 'price' => 5000],
];
// カートの初期化
$cart = [];
// カートに商品を追加する関数
function addToCart($productId, $quantity) {
global $cart, $products;
// 商品が存在するか確認せずに直接アクセス
$cart[$productId] = $cart[$productId] + $quantity; // ここで存在しない場合はnullに加算される
}
// カートの内容を表示する関数
function displayCart() {
global $cart, $products;
if (empty($cart)) {
echo "カートは空です。\n";
return;
}
echo "カートの内容:\n";
foreach ($cart as $productId => $quantity) {
// 商品が存在しない場合、nullが返る
$productName = $products[$productId]['name']; // 存在しないIDの場合、ここでエラー
$productPrice = $products[$productId]['price']; // 存在しないIDの場合、ここでエラー
echo "{$productName} x {$quantity} - " . ($productPrice * $quantity) . "円\n";
}
}
// 商品をカートに追加する例
addToCart(1, 2); // Tシャツを2つ追加
addToCart(2, 1); // ジーンズを1つ追加
addToCart(4, 1); // 存在しない商品を追加しようとする(ここが問題)
// カートの内容を表示
displayCart();
?>
- そもそも、nullとは?
- nullは、未初期化状態の守り療育なアクセスは制御上トラブルの原因となる!こうしたことを避けるためにnullが発明された。
~ nullを取り扱わない設計が大事! ~
- nullを返さない return null
- nulを渡さない nullを変数に代入しない
<p>nullを使わない処理にすればnullの場合の例外で落ちる心配がなく実装が可能!</p>
~ null安全 ~
- null安全とは?
- null安全は、nullが原因のエラーを発生させない。
- null安全を実現する機能の一つにnull非許容型がある!null非許容型はnullを保持できない型。
~ 例外の握りつぶし ~
例外の握りつぶし
try {
reservations.add(product);
} catch (Exception $e){
}
これが例外の握りつぶしである!
この実装は例外をcatchしても何の処理もしていない!!よくないロジックである。なぜなら、いつどこでエラーが起こったかわからない/わかりにくいからである。
→例外握りつぶすもんだいはエラーが起こっても外から検知する術がない!
10章 名前設計 ―あるべき構造を見破る名前―
適切な責務を考、密結合を防止するにはクラスやメソッドへの名付けがポイント!
命名が曖昧や抽象化されたものだと責務が曖昧や巨大化され、よくない設計になる!
解決策をまとめる
-
目的駆動名前設計は名前から目的や意図が読み込まれることを象徴。
名の通り、オブジェクト設計や単一責任原則を守る設計のように課題解決を意図した作りとなっている
例として、ecサイト
商品クラスと定義した場合
商品クラス自体も、結びついたクラスに関係するロジックを持ち始め、どんどん巨大化し複雑化してきます。密結合状態!
このようなクラスに仕様変更した場合、どうなるか??
** ~ 関心の分離 ~**
上記のように商品クラスが予約、注文、発送など、商品にまつわる関心ごとと密接に関わることをかいしょうするには、関心の分離である!
つまり、商品クラスは関心ごとに中から橋への分割が必要!
その第一歩として行うのが命名。
関心ごとに相応しい命名にすること。
その後、各関心ごとにふさわしいロジックをカプセル化すること!関心の分離により、疎結合高凝集が果たされ、仕様変更が生じても、開発生産性が向上する!
~ ⚠️命名における注意 ~
抽象化した命名をしない!
商品は抽象的すぎる!目的不明オブジェクトは避ける!
名前を設計 目的工藤名前設計
設計をする意義は、課題を解決するための仕組みや構造を考えたり、作り上げたりすること。
目的駆動名前設計にすることで、なぜそのメソッドやクラスが存在するのかわかりやすくなる!
目的駆動設計における重要なポイントをピックアップ
・可能な限り具体的で、意味範囲が狭い、特化した名前を選ぶ
・存在ベースではなく、目的ベースで名前を考える
・どんな関心ごとがあるかを分析する
・声に出して話してみる
・利用規約を読んでみる
・違う名前に置き換えられないか検討する
・疎結合高凝集になっているか点検
・名前とは無関係なロジックを排除しやすくなる
・クラスがちいさくなる
・関係するクラスの個数が少なくなる
・関係するクラスが少ないので仕様変更しまに考慮を要する影響範囲ガス小さく済む
・目的に特化した名前な名で、どこをやる変更すれば良いかすぐ探し出せる
・開発生算性が向上
~ 目的別の命名にする ~
Ex. ユーザー→なんのユーザー?
「ユーザーだと」アカウント、個人プロフィールなど多岐に渡りイメージできてしまう!
その後はどんなビジネス目的があるかを考える
登場人物や事柄を列挙したり、関係性を整理したり、分析してみよう!
- 違う名前に置き換えられないか?
- 違う名前に置き換えてみて意味をもっと狭くできないか、違和感がないかなど検討してみる!
- 目的以外のロジックが混入しそうであれば命名を考え直す!
- 他のクラスと幾つ関連づけられるか、考えてみよう!
- 商品クラスのように幾つものクラスと関連付けができると、密結合の危険がある!
関連個数が少なければ少ないほど、影響範囲が低減することも視野に入れること。
11章 退化コメント
..... Later.....
12章 メソッド 良きクラスには良きメソッドあり
メソッドの設計方法を取り扱う。
- 必ず自身のクラスのインスタンス変数を扱う
インスタンス変数を安全に操作するようなメソッドを設計することで、クラスないの正常性を担保できる仕組み!
メソッドは、必ず自身のクラスのインスタンス変数を使うように設計する。例外もあるがこれが原則。
インスタンス変数を生成する際、コンストラクタを使うが、完全コンストラクタパターンを用いてコンストラクタにガード節を用意することも、インスタンス変数の安全な操作につながる!
他のクラスのインスタンス変数を変更するメソッド構造にしてはいけない!
つまり、インスタンス変数を変更したい場合はそのクラス内にメソッドをかくということ。
コマンドクエリ分離
状態変更と取得を同時に行うメソッドは混乱しやすい上に、利用者にとっても使いにくいもの。取得だけしたい、変更だけしたいケースに対応できず、良いことがない。そこで、コマンドクエリ分離と呼ばれる考え方がある!
メソッドはコマンドまたはクエリのどちらか一方だけを行うように設計する、コマンドクエリを分離する考え方。
引数について
引数は不変にすること
フラグ引数は使わない
nullを渡さない
出力引数は使わない
引数は可能な限り少なく
戻り値について
型を使って、戻り値の意図を表明すること
13章 モデリング クラス設計の土台
モデルは、特定の目的達成のために最低限考慮が必要な要素を備えたものがモデル。
例に挙げると、商品モデルがあったとします。商品は配達や購入される状況ごとに必要な要素は異なります。購入時には商品の価格や個数が必要だけど、
配達時には必要ない!
つまり、シチュエーションごとに商品モデルが異なる。
モデルが肥大化すると、一貫性がないモデルとなり、弊害を生んでしまう。モデルは一貫性を持てるように、モデリングし、モデリングには対象とする社会的活動や目的の理解が必要である!
具体例
Userとシステムの関係
Userはどのようにモデリングすれば良い?
Userとは何か?を考える。直訳すると、利用者、使用者である。Userはシステムの利用者。
Userと言われただけで様々なユーザーが想い浮かべれると思います。法人ユーザーや個人ユーザーなど。Userのモデル名が抽象的すぎて、適切な表現ではない。モデル名を正しく表現することが重要であり、『目的駆動名前設計』がポイント。
※目的駆動名前設計は具体的で意味の狭く、目的を表現した名前へ設計し直すこと!。
つまり、モデル設計は目的駆動で名前設計することが適切に目的達成するモデル設計につながる。
単一責任とは単一目的
モデルの見直し方
- そのモデルが獺祭しようとしている目的を全て洗い出す
- モクタカそれぞれ特化したモデリングをし直す
- 目的駆動名前設計にもとづき、モデルに命名する
- モデルに目的外の要素が入り込んでいる場合、さらに見直す
14章 リファクタリング 既存コードを成長に導く技
..... Later .....
15章 設計の意義と設計への向き合い方
本書のノウハウを用いると、コードを楽に素早く正確に変更できるようになる!素早く変更できれば、ソフトウェアの価値が素早く高まる。
テーブル
ソフトウェアにおける設計とは?ーソフトウェア品質特性の向上を促進するための仕組みをつくること
性能効率性は、パフォーマンス性能を表す品質特性であり、性能効率性を上げるにはパフォーマンス設計をする!
ここでは、保守性に関係する設計。つまり、保守性の中でも特に変更用意性の向上を目的にした設計手法。
変更容易性の低いコード(レガシーは開発生産性が落ちる要因は2つ。
バグを埋め込みやすくなる
可読性が低下する
これらの要因が起こり、生産性が下がる!つまり、変更容易性は極めて重要な品質特性であるということ。
ソフトウェア他エンジニアの成長性
ソフトウェアの価値や魅力をより高めるために仕様が追加、変更され、コードが変更される。
課題を解決する
課題とは、現状と理想のギャップを把握できて、課題が見つかる!課題を見つけるには、現状と理想を知らなければならない。これらを知るために、
見える/見えないとプラスの価値とマイナスの価値の2軸、4しょうげんで表現する。
見える 見えない
プラスの価値 新機能 アーキテクチャ
マイナスの価値 バグ 技術的負債
カードの複雑さを推しはかるには?
コードの行数
循環的複雑度
凝集度
結合度
チャンク
コード分析サポートツール
code climate quality
understand
16章 設計を妨げる開発プロセスとの戦い
~ 設計を妨げる一つにコミュニケーションがある! ~
コミュニケーションが円滑でないと同じ実装を複数人で行ったり、情報の目線が合わなかったりが生じ、バグが増大する!
その際は心理的安全性を高めることとコンウェイの法則がポイントとしてある。
設計
<h5>早く終わらせたい心理や早く実装が完了することの正しさは悪?</h5>
筆者が言うには悪だという!結果的に品質が悪ければ、実装は意味をなしにくい実装やコードとなり
あまりよろしくないことだという!
<h5>クラス設計と実装のFBサイクルを回す</h5>
仕様変更なさい、最低でもメモ書き程度のクラス図は書いておこう!責務や凝集性の観点から問題ないか、チームでチェック!
実装後にわかることや新しいバグや修正箇所の発見に出くわすこともあるので、クラス図は書いておこう!
- 設計する際は多数決できてるのではなく
設計に詳しい人やベテランエンジニアがリーダーシップをとって設計していくこと!
多数決はナンセンス!!
設計ルールづくりは以下を参考に
- 実装ではボーイズスカウト論を取り入れよう!
- 実装する前よりも綺麗にすること!
- 既存Classを信用せず、冷静に正体を見破る
- アンカリング法とジョシュアツリーの法則
これらの法則を見破り、正しく設計していくこと!
命名やコーディング規約の重要性も忘れずに!
<h5>レビュー</h5>
-
コードを設計してレビュー。
レビューする際はお互い、敬意と礼儀を持つこと以下にルールはまとめる -
定期的に改善タスクを棚卸し
定期的に改善タスクを棚卸しし、対処できるようにすること
タスク管理はgit issueでするとよい -
チームの設計力
チームの設計力が足りない場合は改善する必要あり!-
どのようにすればOK?
チームメンバーの力を借りてボトムアップ!日頃からの信頼関係を構築し、仲間を集めることが重 要!
キーワード : ランチェスターの法則 -
日本はスモールステップ
焦りは禁物。毎日少しずつ、設計の知識を共有していこう!実感が大事、手を動かす
実際に手を動かしてもらい、理解してもらう! -
フォローアップ勉強会
- 1.本書に書いてあるノウハウを12個読み合わせる
-
- ノウハウを適用できそうな箇所をプロダクションコードの中から探す!
-
- ノウハウを使って改善。
-
- どう改善したかについてbefore, afterがわかるように発表
-
- 発表内容について質疑応答や議論
-
Discussion