はじめに
Twitter眺めたら、株式会社サイバーエージェントが『良いコードとは何か』というタイトルの記事を投稿している事が分かった。内容に興味があったのでざっと見てみたが、あまりイメージがつかなかった。
なので今回はこの記事を筆者にとって理解できる形にまとめたいと思う
記事の内容を筆者が理解できるようにまとめる
▼記事の進行
株式会社CyberZに所属する「森 篤史」という方が、開発プロジェクトのリーダーとして活動していく中で感じたことをベースに、「良いコード」を定義していく
▼「良いコード」とは
森さんの思う「良いコード」は、4つの視点で構成されている。具体的な内容を後述する
1. 品質とスピードはトレードオフか
【言葉の意味】
1)品質
製品に対する「性能(処理速度など)」・「セキュリティ」・「ユーザビリティ」・「保守性」を指す
「外部品質」「内部品質」に分けられる
▼外部品質
・ユーザに見える要素
例:仕様どおりに動く、バグがない、高速に動作する など
▼内部品質
・ユーザから見えない品質
例:保守性、柔軟性、可読性、一貫性 など
2)スピード
開発完了までの期間の事。「スピード重視」は「早く開発完了させる事」を指す。
3)トレードオフ
「両立できない関係性」を示す言葉
👆言い換えると「両方得る事は出来ない」
例1:「お金と時間」 ⇒ 自分の時間を犠牲にして対価を得る場合、両方得たとは言えない
例2:「高品質と低価格」 ⇒ 消費者と生産者が互いに利益を得たい場合、両方得る事はほぼ無い
【品質とスピードがトレードオフの関係性である場合】
納期までの期限が短いと、開発に割けられる時間も短くなるため、品質は下がる
逆に言うと、納期までの期限が長いと、その分開発に時間をさけられるため、品質は上がる
※特に犠牲になる部分は「内部品質」になる
⇒『スピードを犠牲にして品質を上げる』か『品質を犠牲にしてスピードを上げるか』の関係性
👆しかし実際はそうではない
【もし品質を犠牲にした場合、どうなるのか】
・始めは問題ないかもしれないが、長期的には崩壊を生み出す要因になりかねない
・「内部品質は後で修正できるものなのか」
・「後で」は来ない。理由は、「スピードが求められている」という状況にある
・「スピードが求められている」とは、以下のような状況
1. 市場から求められており、早く出さないといけない
2. 出した後も市場のプレッシャーは止まらない
・修正しきれない部分が仇となり、生産性は下がり始める
<ChatGPTが分かりやすい例え話を持ってきた>

⇒品質の犠牲は生産性を落とし、プロジェクトの成功への足かせになってしまう
【まとめ_品質とスピードはトレードオフか】
・トレードオフではない。むしろ正の相関がある。
『品質を上げる』=コードの変更速度の向上・手戻りが減る・学びのループが早くなる
=『スピードが上がる』
・この考えを持つと、あらゆる疑問にも答えが出る
問:「時間をかければ良いコードは書けるのか」
A:書けない。
ロジック:①品質とスピードはトレードオフではない
=②品質を下げてもスピードは上がらない
=③時間をかけても品質は上がらない
👆「相関関係」ではあるが「因果関係」では無いという事。その為品質を上げる為のコードを時間をかけて作った場合、それが長期的には重要な事なら結果的にスピードが上がったことになる。
2. 技術的負債の発生と解消
・「品質とスピードはトレードオフか」の話で品質とスピードは正の相関関係にあることが分かった。ここでは、具体的に「品質と技術的負債」という視点で考える
【言葉の意味】
技術的負債
品質や保守性を犠牲にして、一時的にプロジェクトを進めることを意味する
例1:不必要に複雑すぎるコードや規約に従わないコードが多すぎて別の人が理解できない
例2:十分なテストを行わずにコードをリリースした事で、バグやセキュリティの問題が見落とされる
【技術的負債の例を表で分ける】

👆後述するが、「学びによる負債」以外の項目は改修するべきものになる
技術知識の問題
使っている言語やフレームワーク、ライブラリ等で発生する問題
ドメイン知識の問題
プログラミングの対象の知識
例1:出前/宅配サービスを開発する為には、お店や配達員に関する知識が必要になる。
例2:送金サービスを開発する為には、お金に関する知識が必要になる。
【意図的な負債】
▼概要
「品質を犠牲にして速度を上げる」という考え方の元、発生する負債
例1:より良いコードの書き方を知っているのに異なる書き方をする
例2:ドメイン知識を無視して挙動だけを合わせた書き方をする
👆「品質とスピードはトレードオフ」だと考えている人が起こしやすいんだろうなぁ
▼避けられることか
間違いなく避けられる。
もしどうしても時間がない場合、品質を削るのではなく、ターゲットを削るかリリース日の延長をするかで考えるべき
【変化による負債】
▼概要
時代の流れや市場の流れにより、従来の技術が古くなった事で発生する負債
例1:使っていた技術が古くなった
例2:ツールやドメイン知識が仕様変更により変化した
▼避けられることか
場合によっては避けられるが、経験値が必要になる。
例1:Githubのスター数を見て、継続的にメンテナンスされるかどうかを見極める
例2:業界の専門家やコミュニティに参加し、知識を共有し合う
例3:技術分野や産業における最新のトレンドや市場動向を定期的に調査し、トレンドを把握する
発生後は影響範囲が広がりやすい特徴がある為、早期に改修することが望まれる
【学びによる負債】
▼概要
当初は知らずに進行していたが、新しく知識を学んだ事で、現実と理想のギャップによって発生した負債
例1:プログラミングを勉強したら、当初書いたコードよりも良いコードが思いついた
例2:ドメイン知識を勉強したら、対象分野の専門家とのコミュニケーションが効果的になった
▼避けられることか
避けられない。というか「学ぶ」ってそういうもの。
これ自体はポジティブな事であるため、むしろどんどん発生させていい。
※ただし、発生後放置していると多大な悪影響を及ぼすため、早期に改修させることが望まれる
【Ward Cunninghamによる説明】
技術的負債という概念の生みの親とも呼ばれる、「Ward Cunningham」は以下のように説明している
ーーーーーーーーーーーーーーーーーーーーーーーーーーー
「学びによる負債」を放置することは、「意図的な負債」を生み出すのと同義である。重要なのは、「現時点での理解を可能な限り反映させる事」である。
ーーーーーーーーーーーーーーーーーーーーーーーーーーー
【まとめ_技術的負債の発生と解消】
・プロジェクトを進めるにあたり、「品質」に着目すると、以下のような項目に分けられる
1. 意図的な負債
2. 変化による負債
3. 学びによる負債
・技術的負債を改修する上で重要な事は、「意図的な負債を可能な限り減らすこと」である。この意識を持つことで、他の変化による負債や学びによる負債を改修できたり予想が立てられたりする。
3. 凝集度と結合度
・「品質とスピードは正の相関がある」「意図的な負債を減らす事で品質はもっと良くなる」という話をまとめた
・ここでは「共通的な『良いコード』とは何か」に視点を置く
👇かなり長いし、内容がほぼ記事の要約なので飛ばしていいです
記事に乗っている「凝集度」と「結合度」を筆者の自己満で書く
【凝集度】
凝集度
パッケージ・モジュール・メソッドといった粒度を指す

堅牢性
ソフトウェアやシステムなどが、予期しないエラーや障害やセキュリティ上のリスクなどに柔軟に対応できる性質
単一責任の原則
クラスやモジュールなどのソフトウェアコンポーネントが一つの責任(役割)に焦点を当て、それを徹底的に実行すべきであるという考え方
例1:1つのコンポーネントが複数の役割を持たないように導入され、可用性を向上させる
例2:コンポーネントを修正しても複数の責任を持たない為、バグの発生を減少させられる
▼ある凝集性を持ったものの解説
・一番悪いものから説明する
偶発的凝集
適当(無作為)に集められたものがモジュールとなっている。
モジュール内の各部分には特に関連性はない(例えば、よく使われる関数を集めたモジュールなど)。
function main() {
const date = new Date();
console.log(date);
const arr = ["apple", "banana"];
console.log(arr.length)
}
論理的凝集
論理的に似たものを集めたモジュール
例:フラグを渡すことで動作を変えるもの
保守性が低く、好ましくないとされている
function sample(isA) {
if (isA) {
sampleA();
} else {
sampleB();
}
}
時間的凝集
コード内の異なる操作が同じタイミングや順序で一緒にまとまっているモジュール
実行順序を入れ替えても問題なく動作する
function processOrder(order) {
initConfig() // 設定の初期化
initLogger() // Loggerの初期化
initDB() // DBの初期化
}
手続き的凝集
時間的凝集とは違い、順番に実行する必要があるものを集めたモジュール
function processOrder(order) {
checkPermission(order); // 権限確認
writeFile(order); // ファイル出力
}
通信的凝集
同じデータを扱う部分を集めたモジュール
順番は重要ではない
function processOrder(order) {
saveToDatabase(order);
sendConfirmationEmail(order);
updateInventory(order);
notifyWarehouse(order);
}
逐次的凝集
ある部分の出力が別の部分の入力となるような部分を集めたモジュール
function sample() {
const file = getFile() // ファイルを取得
const transfromed = transForm(file) // ファイルを変換
saveFile(transfromed) // ファイルを保存
}
機能的凝集
単一の定義されたタスクを実現するモジュール
// 三角形の面積を計算する関数
function calculateTriangleArea(base, height) {
// 三角形の面積を計算する式:面積 = (底辺 * 高さ) / 2
const area = (base * height) / 2;
// 面積をコンソールに出力
console.log(`三角形の面積は ${area} です。`);
// 面積を戻り値として返す(必要に応じて使用)
return area;
}
【凝集度の使い分け】

▼凝集度の特徴と使い分けから改修できる点
・凝集度の悪いものは極力使わず、より良い凝集度を持った関数を作る
例:時間的凝集 ⇒ 逐次的凝集, 手続き的凝集に関数を分けてリファクタリングする
例:論理的凝集 ⇒ 共通化せず分ける
!注意点!
関数が分かれると多少なりとも認知負荷が上がるため、意味の分かる単位で区切ることが重要
【結合度】

結合度
モジュール間の相互依存性の程度
※「凝集度」はモジュール内の評価に使われる
▼結合度の種類の解説
結合度が低い=可読性が高い, 保守性が高い
・結合度が高い・悪いものから解説する
内部結合
あるモジュールが別のモジュールの内部動作によって変化したり依存したりする
例:別のモジュールの内部データを直接参照する
1つのモジュールを変更することで、依存するモジュールも変更する必要がでる
function UserModule() {
addUser(user) // ユーザーを追加
getUserInfo(userId) // ユーザー情報を取得
updateUserInfo(userId, newData) // ユーザー情報を更新
return {
addUser,
getUserInfo,
updateUserInfo
};
}
共通結合
複数のモジュールが同じグローバルデータにアクセスできる状態
共通のリソースを変更すると、それを使用したすべてのモジュールを変更される特徴を持つ
あるモジュールで変更を加えると、他のモジュールで予期せぬ動作をする可能性がある
class Data {
constructor() {
this.value = null;
}
}
// Dataクラスのインスタンスを作成
const data = new Data();
// updateA関数の定義
function updateA() {
data.value = "a";
}
// updateB関数の定義
function updateB() {
data.value = "b";
}
// Data クラスのコンストラクタでデータの初期化が行われているが、その初期化に関連する updateA と updateB 関数が同じクラス内に存在する。
// このような関連性の低い初期化と更新の操作が同じクラス内に存在することから、共通結合が高いといえる
外部結合
複数のモジュールが、外部から供給されたデータ·フォーマット、通信プロトコル、デバイスインターフェイスを共有している状態
function sample1() {
api.getData()
}
function sample2() {
api.update()
}
制御結合
あるモジュールに何をすべきかの情報を渡すことで、別のモジュール処理の流れを制御する
function sample() {
test(true)
}
function test(result) {
if (result) {
sample2()
} else {
sample3()
}
}
スタンプ結合
複数のモジュールが複合データ構造を共有し、その一部のみを使用する
function sample() {
sample2(User = "田中")
}
データ結合
プリミティブ型と呼ばれる単純な引数でやり取りを行うモジュール
必要最小限のデータを渡すことが出来るため、スタンプ結合よりは結合度が低くなる
function sample() {
function2(1, "ok")
}
メッセージ結合
引数のないやりとりで、タイミングのみ等を伝えるモジュール
function sample1() {
sample2()
}
【結合度の使い分け】

【まとめ_凝集度と結合度】
共通的な『良いコード』の判別は「凝集度」と「結合度」の種類別で考えることができる
・凝集度
・結合度
⇒表を基に使い分け、できる限り理想的な凝集性・結合性を持ったコードを書くことが『良いコード』と言える
4. Clean Architecture
・「品質とスピードは正の相関がある」「意図的な負債を減らす事で品質はもっと良くなる」「良いコードの定義は凝集度と結合度で説明できる」という話をまとめた
・ここでは「『アプリケーション全体のより良い状態』とは何か」に視点を置く
【Clean Architectureの考え方】
クリーンなアーキテクチャについて、Clean Architectureが主張するルール
1.レイヤーに分離することで、関心事の分離を行う
・関心事を細分化し、境界線を設ける
2.依存性は内側だけに向かっていなければならない
・内側
・抽象的なもの(ビジネスロジック・エンティティ)
・外側
・具体的なもの(UI・DB・外部システム)
⇒外側に依存しないため、保守性が高まる
内側/外側の境界線を越える場合
境界線を超える際のルール
1.外側から内側へのアクセスは常に可能である
・内側から外側へ依存することが無いようにする
2.内側から外側へ情報を伝える場合、ストリームや依存関係逆転の原則(DIP)を使う
・ストリーム
・データの生成、変換、処理、および出力を効率的かつ柔軟に行うための方
例(ストリーミング):データ全体を細切れにして連続的にダウンロードしながらデータの再生を行う方法
・依存関係逆転の法則
・「プログラムの重要な部分が、重要でない部分に依存しないよう設計すべきである」という考え方
3.単純なデータ構造の方が好ましい
・レイヤー間の結合度は低くする方がいい
4.内側が外側について知るようなデータを渡してはいけない
・常に内側の知識のみで構成されるようにする
【まとめ_Clean Architecture】
・アプリケーション全体の品質を上げるためには、「関心事でレイヤー分けし、内側/外側で依存関係を制御する」事が重要である。
記事のまとめ
1.品質とスピードは正の相関関係にあり、トレードオフではない
2.品質を上げる為には「意図的な負債」を可能な限り出さない習慣が大切
3.「良い品質」の定義は凝集度と結合度を基準にする
4.「良いアプリケーション」の定義は、Clean Architectureの考えに基づき、「関心事で分離」し、「内側/外側で正しく依存方向を制御」させる事で実現できる
おわりに
文章化したことで内容の理解ができた。
コード書くときにこの考え方を意識し、意図的な負債を無くしていきたい。