はじめに
エンジニアになって1年ほど経ちました。
日々良いコードってどういうコードなんだろうと悩んでいた時、良いコード/悪いコードで学ぶ設計入門に出会いました。
まだ3章までしか読んでいませんが、書かれている内容がとてもわかりやすく
またコードもあるのでとても自然に内容が入ってくる書籍でした。
今の段階で学んだ事を記事にしています。
対象者
この記事は下記のような人を対象にしています。
- 駆け出しエンジニア
- プログラミング初学者
- オブジェクト指向での良いコードの書き方を知りたい方
第3章までの内容
- 第1章 悪しき構造の弊害を知覚する
- 第2章 設計の初歩
- 第3章 クラス設計ーすべてにつながる設計の基盤ー
第1章〜第2章は基本的な内容
- 変数や関数名の命名は意図や目的を表現したものにしよう
- ベタ書きはやめてメソットに使用
などといった小さい単位での基本的な書き方、考え方が記載されていました。
結構ここまではネット上で情報としてあって自分もこれらは気をつけながら書けていると
思っていました。
しかし第3章を読んで自分の考えていた良いコードの浅さを痛感しました。
第3章はクラスで書くとどういうコードになるのか
そもそも私自身classにアレルギー反応があり避けてきた人間だったので
今回この章を通してclassが好きになりつつあります笑
classの基本的な考え方
本書に書かれていた内容で
「クラスは単体で正常動作するように作成する」事が重要で
イメージは「キーボードをパソコンに接続するとすぐ使える状態」を実現できるように
実装する。
コードを書く人が誰でもすぐに操作を間違える事なく、使える状態でclassを作成する事が重要だと思いました。
頑強なクラスの構成要素
- インスタンス変数
- メソッド
インスタンス変数を設定する時は、不正状態から防御できる状態で設定する
正常に操作する事ができるメソッドを設定する
上記の2つが重要
それに加えてclassは自己防衛責務を負う事でソフトウェア全体の品質が向上する。
これらの構成要素を踏まえて本書で書かれていたコードが下記になります。
※書籍はjavaだったのでtypescriptに書き換えています。
実際に本書通りに実装してみた結果
enum Currency {
USD = "USD",
EUR = "EUR",
JPY = "JPY",
}
class Money {
// ①
public readonly amount: number;
public readonly currency: Currency;
constructor(amount: number, currency: Currency) {
// ②
if (amount < 0) {
throw new Error("金額には0以上を指定してください。");
}
if (currency === null) {
throw new Error("通貨単位を指定してください");
}
this.amount = amount;
this.currency = currency;
}
// ③
add(other: Readonly<Money>) {
if (this.currency !== other.currency) {
throw new Error("通貨単位が違います");
}
const added = this.amount + other.amount;
return new Money(added, this.currency);
}
}
①について
function HomePage() {
const money = new Money(1000, Currency.JPY);
console.log(money.amount);
money.amount = 2000
console.log(money.amount);
return <div>home</div>;
}
実装で使用した場合上記のようになりreadonlyが無いと
実装の中でいつの間にか値が変えられて、インスタンス生成時と
値が変わっていて予期しない動作をしてしまう恐れがある。
そのためreadonlyを使用してインスタンス変数を不変にしていました。
②について
if (amount < 0) {
throw new Error("金額には0以上を指定してください。");
}
if (currency === null) {
throw new Error("通貨単位を指定してください");
}
金額や通貨単位などが不正なものを受け取る事ができるのでここで状態を検知して
不正状態から防御を実施する。
こうする事で自己防衛責務を果たす事が可能になる。
③について
add(other: Readonly<Money>) {
if (this.currency !== other.currency) {
throw new Error("通貨単位が違います");
}
const added = this.amount + other.amount;
return new Money(added, this.currency);
}
実装内でamountに指定の数値を足せるように実装
otherをreadonlyに設定する事で設計時のadd機能を損なわないように
メソッド内で上書きできないようにしている。
引数otherをnumberではなくMoneyにする事で
classを使用する方が誤った引数を渡さないように実装
add(other: Readonly<number>) {
const added = this.amount + other;
return new Money(added, this.currency);
}
もし上記のようにnumberを渡すように実装していると初めて見る方からしたら
なんの数値か分からないのでclassを渡すことで明示的にamountと分かるようにしている。
if (this.currency !== other.currency) {
throw new Error("通貨単位が違います");
}
不正状態から防御する観点から誤った通貨単位でaddをしようとした時にエラーが発生するように実装している。
結論
実際に本書に書かれているjavaのコードを見ながら自分が普段使っている
TypeScriptに置き換えて書くことでかなり理解が進みました。
使用設定パターン(デザインパターン)としては
- 完全コンストラクタ
- 値オブジェクト
以前、デザインパターンを勉強したことはあったのですが、言葉の羅列でなーんとなくしか理解ができませんでしたが、本書を読むことで理解を深める事ができたと思います。
おわりに
また第4章から学んだ事があったら記事にさせて頂きます。
最後まで読んで頂きありがとうございました。
少しでも参考になったと思ったらいいね!お願いします。
記事を書く励みになります