定番の技術書を読了したため、
個人的に見返したい内容を記録しつつ、復習します。
(*本記事でまとめる内容は改訂新版の前のものです。)
副作用は無くすこと
関数(メソッド)の話。
副作用とは
関数が引数を受けとり、戻り値を戻す以外に、外部の状態を変更すること
「外部の状態」はいくつか対象となる。引数、インスタンス変数、グローバル変数、ファイルIOなど。関数内でこれらを容易に変更しないこと。
なおローカル変数は変更してもok。関数内に留まり影響しないため。
-
問題点
- 影響範囲を広げてしまう。副作用を起こしているコードは意外にも特定しづらく、原因特定の手間もかかる
-
対策
- 引数を不変 / 読み取り専用にする
- アクセス制限を決める
悪い例
ゲームプレイヤーを想定しKnightクラス
を作成する。
致命的に攻撃(attckメソッド)するたびに強くなってしまう。こういう混入は避けたい。
public class Knight {
int power = 10;
void attack() {
System.out.println("attack! : " + power);
power += 10;
}
}
呼び出す側
public class App {
public static void main(String[] args) throws Exception {
Knight knight = new Knight();
knight.attack();
knight.attack();
}
}
// 出力結果
// attack! : 10
// attack! : 20
関数は命じる形にする
これも関数の話。
クラスやオブジェクトなど、使う人(呼び出す側、Aとする)が使う部品(呼び出される側、Bとする)の内部状態を把握したり、それに応じて制御や判断しないこと。
尋ねるな、命じろ(Tell, Don't Ask)
という格言があるほど意識したいこと。
-
問題点
- B(部品)を変更した時、A(使う人)が影響を受けてしまう。影響範囲が広くなってしまう。
- AがBの内部処理を少し知る必要があり、やや依存している。Aが大変≒責務が増えている。
-
対策
- Bが状態の変更、制御、判断を行う = 命じるメソッドにする
- Aが知らなくても、Bが自律的に動けるようにする
悪い例
呼び出す側Appクラス
が
呼び出される側CoffeeMachineクラス
の状態(水と豆があるか)をチェックしている。
public class App {
public static void main(String[] args) throws Exception {
CoffeeMachine machine = new CoffeeMachine();
if (machine.isWaterFilled()) {
if (machine.isBeanFilled()) {
machine.makeCoffee();
}
return;
}
}
}
public class CoffeeMachine {
boolean isWaterFilled = true;
boolean isBeanFilled = true;
boolean isWaterFilled() {
return isWaterFilled;
}
boolean isBeanFilled() {
return isBeanFilled;
}
void makeCoffee() {
System.out.println("Making Coffee");
}
}
良い例
状態のチェックはCoffeeMachineクラス
が担う。
これでボタン1つ押せばコーヒーが作れる。
public class App {
public static void main(String[] args) throws Exception {
CoffeeMachine machine = new CoffeeMachine();
machine.makeCoffee();
}
}
public class CoffeeMachine {
boolean isWaterFilled = true;
boolean isBeanFilled = true;
void makeCoffee() {
if (!isWaterFilled) {
System.out.println("Fill water tank");
return;
}
if (!isBeanFilled) {
System.out.println("Fill bean tank");
return;
}
System.out.println("Making Coffee");
}
}
コマンドとクエリを分離する
これも関数の話。
関数は、次の2つのどちらかにしましょうという考え方。
- コマンド:状態を変更する
- クエリ:状態を返す、問い合わせ
ちなみにどちらも同時に行うと「モディファイア」と呼ぶ。
悪い例
変数pointに加算し返している
int AddAndGetPoint() {
point += 10;
return point;
}
良い例
pointへの加算と、pointの取得を分離する
void AddPoint() {
point += 10;
}
int GetPoint() {
return point;
}
命名を「名前設計」と呼んでみる
クラス名やメソッド名を検討することを「命名」という。
これを「名前設計」と呼ぶ、あるいは捉える。
ここでの設計は、
「ある課題を解決するためのしくみや構造を考えたり、作り上げたりすること」
と定義している。
命名を設計行為の一貫と考えることで、単純に名前をつける作業よりも、
そのクラスやメソッドの作り方まで意識が広がる。
その他
- コメント
- 仕様変更の注意点を書くのもgood
- マズローのハンマー
- 新しい知識や技術をついつい使ってみたくなる現象
- 「ハンマーを持っているとなんでも釘に見えちゃう」ということから、わりとわかる
- 凝集度と結合度
- まず、モジュールはクラス、パッケージ、レイヤーなど様々な粒度に解釈できる
-
凝集度
は、モジュール内におけるデータとロジックの関連性の強さ- クラス粒度だと、インスタンス変数と、それを変更するメソッドがクラス内に定義されているか
-
結合度
は、モジュール間の依存の度合い- クラス粒度だと、呼び出すクラスと呼び出されるクラスの数など
感想
- 悪い例に引数を書き換えるコードを示せそうとしましたが、javaでは値渡しがデフォルトでした
- 他にもメモしたいことは別の記事にまとめたいと考えております