これはなに?
こんにちは、DMM.comのミノ駆動です。 プラットフォーム開発本部 Developer Productivity Group 横断チームにて、 プラットフォームの設計品質向上に取り組んでいます。
さて、ネット上ではソフトウェア開発における「良いコードとは何か」をめぐって、 いろんな意見が交錯したり、 ときには激論を呼んだりします。 収拾がつかないこともしばしばです。
この記事は、良いコードを考えるうえでの要素を整理し、 建設的な議論を助けることを目的とします。
この記事の理解目標
以下3点を本記事の理解目標とします。
- ソフトウェア品質特性の観点でコードの良し悪しを議論すること
- 原理原則や設計ノウハウを踏まえて議論すること
- サービスの成長度合いと設計戦略の観点を持つこと
良いコードをめぐる議論
良いコードが話に上がるとき、 以下に列挙する3つがよく議論になっているように見受けられます。
議論1: 何をもって良いコードなのか
何をもって良いコードなのか、 人によって基準の異なるケースが見受けられます。
ある人は「読みやすいコードが良いコードだ」と言い、 またある人は「パフォーマンスに優れるコードが良いコードだ」と言ったりします。 「実際に利益を出しているコードが良いコードだ」という意見もあるようです。
このように、良いコードとは何かについて多様な考えがあります。 しかしそれぞれの基準が異なる場合、お互いの意見が噛み合いません。
議論2: 良いコードはどうやったら書けるのか
どうすれば良いコードを書けるかについてもよく議論になります。
しかし議論1で挙げたように、 良いコードの基準がそれぞれ異なっているため、 当然ながら基準を満たすためのアプローチもさまざまです。
こうしたちぐはぐさがあるためか、 「結局良いコードの書き方は人それぞれだ」といった、落としどころのない話に陥りがちです。 技術の話を建設的に進めたり、深堀りするのが難しくなります。
議論3: 「綺麗なコード(良いコード) vs 動くコード」問題
そしてよく激論になるのがこれです。
「綺麗なコードでなければならない」という意見と、 「動くコードが正義」という意見が対立するケースです。 この議論も、落としどころを見いだせず、答えがないまま終わったり、 有益な知見が得られないまま消耗していくだけになりがちです。
議論改善のために提案します
こうした「良いコード」をめぐる議論はどのように整理したら良いでしょうか。 その鍵を握るのがソフトウェア品質特性です。 上記3つの議論について、ソフトウェア品質特性にもとづいて整理することを提案します。
提案1: ソフトウェア品質特性の観点でコードの良し悪しを判断しよう
「議論1: 何をもって良いコードなのか」に対する提案です。
そもそもコードの「良い」「悪い」は、言葉として曖昧です。 基準や目指すべき指標が不明瞭です(だから何が良いのか人それぞれで違ってしまい噛み合わなくなる)。 具体的な指標を用いる必要があります。
その具体指標として有用なのがソフトウェア品質特性です。 ソフトウェア品質特性とは、ソフトウェアの品質を評価する基準です。 下記の表は、ソフトウェア製品に関する品質特性です(JIS X 25010:2013を参考に作成)。 さまざまな特性があります。
品質特性 | 説明 | 品質副特性 |
---|---|---|
機能適合性 | 機能がニーズを満たす度合い | 機能完全性、機能正確性、機能適切性 |
性能効率性 | リソース効率や性能の度合い | 時間効率性、資源効率性、容量満足性 |
互換性 | ほかのシステムと情報の共有、交換できる度合い | 共存性、相互運用性 |
使用性 | 利用者がシステムを満足に利用できる度合い | 適切度認識性、習得性、運用操作性、ユーザーエラー防止性、ユーザーインターフェイス快美性、アクセシビリティ |
信頼性 | 必要なときに機能実行できる度合い | 成熟性、可用性、障害許容性、回復性 |
セキュリティ | 不正利用から保護する度合い | 機密性、インテグリティ、否認防止性、責任追跡性、真正性 |
保守性 | システムを修正する有効性や効率の度合い | モジュール性、再利用性、解析性、修正性、試験性 |
移植性 | ほかの実行環境に移植できる度合い | 適応性、設置性、置換性 |
コードはソフトウェアの構成要素なので、 コードもソフトウェア品質特性の観点で評価できます。 たとえば速く実行できるコードは「時間効率性に優れたコード」と評価できますし、 保守が容易なコードは「保守性に優れたコード」と評価できます。
人によって「良いコード」の基準がバラバラで噛み合わなくなるのは、 どの品質特性なのかを意識せずに意見や議論しているのが原因でしょう。
「良いコードとは」と問われた場合、 どの品質特性に関する良し悪しなのか認識を一致させることが重要です。
提案2: 原理原則や設計ノウハウを踏まえた議論をしよう
「議論2: 良いコードはどうやったら書けるのか」に対する提案です。
良いコードの書き方は、本当に人それぞれなのでしょうか。
前述したように、コードの良し悪しは品質特性の観点から評価できます。 当然、どの品質特性を向上させるかでコードの改善の仕方は違ってきますね。
品質特性の向上を目的とした活動や仕組みづくりを設計といいます。 セキュリティ向上のための判断や仕組みづくりはセキュリティ設計と言えますし、 コードが読みやすくなるようクラスやメソッドに命名する活動は可読性設計をしていると言えます。 そして各品質特性を向上させる設計ノウハウが、多くの先人や有識者によって考案されています。
たとえば上記品質特性における修正性は、変更容易性とも呼ばれます。 変更容易性とは、なるべくバグを発生させず、どれだけすばやく正確にコード変更可能かを示す度合いです。 変更容易性の向上にはさまざまな原理原則や設計ノウハウがあります。 代表例としてカプセル化と関心の分離を挙げます。
カプセル化の説明にあたり、次のOrderItem
クラスを挙げます。
これは注文明細を表現するクラスです。
このクラスには、注文数を表すint
型インスタンス変数quantity
があります。
// 注文明細クラス class OrderItem { int orderItemId; int orderId; int productId; int unitPrice; int quantity; // 注文数 }
この構造には問題があります。
以下に示すように、注文数quantity
に負数を代入できてしまいます。
orderItem.quantity = -1;
正常な値のみ受け付けるよう、 次のようなバリデーションロジックをどこかに実装するかも知れません。
class Validator { boolean isValidQuantity(OrderItem orderItem) { return 1 <= orderItem.quantity; } }
しかしこのようにいい加減な箇所に実装すると、
isValidQuantity
メソッドを呼び忘れた際バグになります。
isValidQuantity
メソッドの存在が認知されず、
別の人によって同じコードが実装されてしまう懸念があります。重複コードです。
他にも、注文数に関係するロジックがあちこち無秩序に実装されると、
注文数に関して仕様変更が生じた際、
関係するロジックを探し回るのに非常に骨が折れます。
こうした問題を解決するのがカプセル化です。 カプセル化とは、データとそのデータを操作するロジックをひとまとめにすることです。 カプセル化の手段として、Javaであればクラス、Go言語であれば構造体、といった形で提供されています。 (※「クラスって何のためにあるの?」と聞かれたら「カプセル化のためです!」と即答できるようになりましょう)
以下は、注文数に関連するデータとロジックをカプセル化したQuantity
クラスです。
クラスでカプセル化する場合、インスタンス変数とインスタンス変数を操作するロジックをひとまとめにします。
class Quantity { private static final int MIN = 1; final int value; Quantity(final int value) { if (value < MIN) { throw new IllegalArgumentException("注文数は1以上指定してください"); } this.value = value; } Quantity add(final Quantity other) { return new Quantity(value + order.value); } }
このQuantity
クラスを利用することで、
正常な注文数のみを扱うことができます。
また、注文数に関係するロジックがQuantity
クラスにカプセル化されているので、
注文数に仕様変更が生じた際はQuantity
クラスを見ればよく、
ソースコードをあちこち探し回る必要がなくなります。
カプセル化以外にも、関心の分離も重要です。
関心の分離の説明にあたり、ショッピングサイトにおけるポイント還元を例に挙げます。 ショッピングサイトには、サービスの継続利用を目的に、購入費に応じてポイント還元するものがあります。 この架空事例では、年に1回ポイントボーナスをユーザーにプレゼントする仕様があるとします。 会員ランクごとのポイントボーナスは、以下の仕様です。
会員ランク | 年間ポイントボーナスの仕様 |
---|---|
一般会員 | 年間購入費の1% |
ゴールド会員 | 年間購入費の1% + 10000ポイント |
これらの仕様を満たすために、次のように実装されたとします。
年間購入費の1%の計算が共通ロジックとして実装され、
ゴールド会員をisGoldMember
で判定して10000ポイント付与する実装になっています。
class YearlyPointBonus { int value; YearlyPointBonus(PurchaseHistory purchaseHistory, boolean isGoldMember) { value = (int)(purchaseHistory.yearlyAmount() * 0.01); if (isGoldMember) { value += 10000; } } }
この構造には問題があります。
ゴールド会員の年間ポイントボーナスが「年間購入費の2% + 10000ポイント」の仕様に変わったとします。
このときYearlyPointBonus
クラス内の0.01
を0.02
に変更すると、
一般会員の年間ポイントボーナスを正しく計算できなくなります。
こうした問題を解決するのが関心の分離です。 関心とは、ソフトウェアの機能や目的のことです。 関心の分離とは、それぞれの関心でモジュールを独立させ、他の関心と分離する考え方です。
関心の分離の観点でYearlyPointBonus
クラスを見てみましょう。
このクラスには、一般会員とゴールド会員、
両方の年間ポイントボーナスについての関心が混在しています。
こうした構造は非常に変更に弱く、
一方の機能の変更により、もう一方の機能が意図せず壊れてしまいます。
関心の分離にしたがって関心事単位で分離すると、
次のRegularYearlyPointBonus
クラスとGoldYearlyPointBonus
クラスになります。
// 一般会員の年間ポイントボーナス class RegularYearlyPointBonus { private static final double POINT_RATE = 0.01; final int value; RegularYearlyPointBonus(final PurchaseHistory purchaseHistory) { value = (int)(purchaseHistory.yearlyAmount() * POINT_RATE); } }
// ゴールド会員の年間ポイントボーナス class GoldYearlyPointBonus { private static final double POINT_RATE = 0.01; private static final int FIXED_POINT_BONUS = 10000; final int value; GoldYearlyPointBonus(final PurchaseHistory purchaseHistory) { value = (int)(purchaseHistory.yearlyAmount() * POINT_RATE) + FIXED_POINT_BONUS; } }
このように一般会員とゴールド会員とで別々のクラスに分けることで、 どちらかの仕様が変わっても、もう一方には影響が生じません。
以上、カプセル化と関心の分離に代表されるように、 設計の原理原則に従うことにより、変更容易性が明確に向上します。
もちろん変更容易性以外の他の品質特性についても設計ノウハウがあります。 たとえば移植性を高めるには、永続化ロジックやプレゼンテーションロジックを別の技術レイヤに隔離し、純粋度を高めます。 UIの再利用性設計にはコンポーネント指向UIがあります。 セキュリティ設計では、エスケープやCSRFトークンといった手法があります。
「良いコードの書き方は人それぞれだ」といった発展性の乏しい結論に終始するのではなく、 品質特性ごとに確立された設計ノウハウを把握したうえで、さらに自分なりにどう工夫するか、 と考えられることが建設的であり技術を深めることにつながるでしょう。
提案3: サービスの成長度合いと設計戦略の観点を持とう
「議論3: 『綺麗なコード(良いコード) vs 動くコード』問題」に対する提案です。
この議論を整理するには、私たちが何のためにソフトウェアを開発しているのか、という点に立ち返る必要があります。 個人の趣味ではなく、会社業務としてのソフトウェア開発は、ビジネスのための開発です。 ビジネスでは、利益を上げるためにソフトウェアを開発します。
利益を単純に表現すると、次の計算式になります。
利益 = 収益 - コスト
- コスト:業務遂行に必要な諸々のお金。人件費や事務所の光熱費など。
- 収益:商品やサービスが稼いだお金の総額。
- 利益:収益からコストを差し引いた金額。
利益を出すには、コストを上回る収益が必要です。 逆にコストが収益を上回ると赤字です。 「動くコード vs 綺麗なコード」を整理するうえで、 この収益とコストの関係が重要です。 このあと解説しますので、よく覚えておいてください。
さて、技術は問題解決のためにあります。 設計も同様であり、品質特性上の問題を解決するための技術です。 問題解決にはコストがかかります。
ところで「動くコード vs 綺麗なコード」においては、 それぞれ次のような主旨で述べられることが多いです。
- 動くコード:ちゃんと動くコードを書いて顧客に使ってもらわなければ話にならない。綺麗なコードを書くのに時間を割いていられない。
- 綺麗なコード:綺麗なコードを書かないと保守や変更が大変になる。とても機能開発どころではなくなる。
どの問題解決にコストをかけるのか、 コストの奪い合いの議論になっていることが分かります。
では動くコードと綺麗なコード、 それぞれどんな問題を解決しているのかを整理します。 整理にあたり、「動くコード」「綺麗なコード」これらも意味として曖昧ですから、 品質特性の観点からどういうコードなのかそれぞれ明確にします。
動くコードとは、文脈的にはシステムとして機能するコードであると推察します。 これは品質特性における機能適合性(機能性)に該当します。 機能性とは、顧客のニーズを満たす度合いです。 機能性が高いほど顧客が満足し、結果として収益が上がります。
一方綺麗なコードは、変更容易性の高いコードであると考えられます。 変更容易性が高いほど、将来の開発コストが低減します。
該当する品質特性 | 解決したい問題 | |
---|---|---|
動くコード | 機能性 | 収益の向上 |
綺麗なコード | 変更容易性 | 開発コストの低減 |
つまり「動くコード vs 綺麗なコード」を品質特性の観点で見ると、 機能性と変更容易性、どちらにコストを投じるのかトレードオフの議論になっている、 と言えます。
ここで利益の計算式を見てみましょう。
利益 = 収益 - コスト
この計算式にもとづくと、 動くコード(機能性)も綺麗なコード(変更容易性)も、 両方利益に寄与しますよね。 それなのに一体なぜ意見が対立してしまいがちなのでしょうか。
収益を上げるためには機能性の満足が必須です。 サービスを軌道に乗せるためのスタートアップ時は特に重要です。 しかし機能性ばかりを重視し変更容易性をないがしろにすると、技術的負債が増大します。 負債によって開発速度は低下し、機能追加が困難になっていきます。 書籍『Clean Code』では、とある人気アプリの開発において、 製品リリースを急ぎすぎたためにコードがどんどん粗悪になっていき、 アプリがバグだらけになってついに廃業になってしまったエピソードが語られています。
こうした恐ろしい事態に陥らないためにも、 変更容易性の設計にコストを投じることは重要です。 ではいつでも変更容易性を重視すべきなのでしょうか。 コードベースが小さい内は、変更容易性はさして問題にはなりません。 大した問題でもないのに過剰に変更容易性の設計をすると、 かえって開発速度を出せず、機能性を発揮するうえでの足かせになってしまいます。
これはつまりどういうことかと言うと、 サービスの成長度合いによって、各問題の相対的な大小が変化している、ということです。
時間も予算も、開発リソースは有限です。 そうした限られた条件の中で利益を生み出せるように、 適切な配分でコストを投じる必要があります。 サービスの成長度合いによって各品質特性の相対的な重要性は変化しますから、 どの品質特性にどれぐらいの割合で設計コストを投じるかを判断する、 戦略的思考が不可欠なのです (ちなみにアーキテクチャ品質特性のコントロールに責任を持つ職種がアーキテクトです)。
つまり「動くコード vs 綺麗なコード」が不毛な議論に陥りがちなのは、 サービスの成長度合いを踏まえた設計戦略の視点が欠けているのが原因と言えるでしょう。
サービスの成長度合いは今どのレベルにあるか。 PMF(Product Market Fit)を達成しているか。 変更失敗率が増大したり、リリース頻度が低下してきていないか。 コードメトリクスの値が低下してきていないかなど、 こうした観点からどの品質特性にどれぐらいコストを投じるか議論できるのが良いでしょう。
ちなみに書籍『ドメイン駆動設計をはじめよう』では、次のような記述があります。
事業への貢献が実証されていて、積み重なった技術的負債と膨れ上がった設計の乱雑さを刷新する必要があるシステムこそ、ドメイン駆動設計で取り組む価値がもっとも高いのです。
(『ドメイン駆動設計をはじめよう ―ソフトウェアの実装と事業戦略を結びつける実践技法』p.235より引用)
状況に応じて適切にコスト判断できることが望ましいですね。
以上が良いコードの議論を整理するための3つの提案です。
補足:どの品質特性を追いかけるべきかは状況によって異なる
ソフトウェア品質特性に関して、いくつか説明を補足します。
上記提案3にて、機能性と変更容易性の関係性について解説しました。 それ以外の品質特性についても同様に、どれを重視すべきかは状況により異なります。
再利用性
「良いコード」を語るうえでよく話題にのぼるのが再利用性です。 しかし再利用性を追いかけるべき状況と、そうでない状況があります。
ショッピングサイトやSNS、ゲームといったアプリケーションは、 それぞれのドメイン(事業活動)に特化したロジックになるので、 汎用的なロジックはほとんど生じません。 特化したロジックは再利用できないのです。 したがってアプリケーション開発では再利用性を追いかけるべきではありません。 (※かと言って重複コードを許してよいというわけではありません。 目的や意図が同じコードはDRYにしましょう。目的や意図が異なるコードをDRYにしてはいけません。)
一方でSpring BootやRails、Reactといったフレームワークは、 広く開発上の基盤となるものなので、 非常に高い汎用性が求められます。 開発対象がフレームワークであれば再利用性を追いかけるべきです。
パフォーマンス vs 可読性
パフォーマンス(性能効率性)に優れたコードを書こうとすると、 どうしても人間の読めないコードに近づいていきます。 可読性が低下してしまうのです。
パフォーマンスは大事ですが、可読性を犠牲にして、 なんでもかんでもパフォーマンス最適化したコードを書くのは考えものです。 保守や変更が困難になります。
まずはパフォーマンス要件をおさえましょう。 その上で、パフォーマンス上どこがボトルネックになっているかを計測しましょう。 ボトルネックになっているロジックを、 関心の分離にしたがって別モジュール(クラスや構造体)に分離しましょう。 分離したモジュール内でパフォーマンス最適化しましょう。
こうすることでパフォーマンス要件が不要なコードの可読性が低下せずに済みます。
セキュリティ
セキュリティ設計についても、必要なものとそうでないものがあります。
Webアプリケーションは不特定多数のユーザーに利用されるソフトウェアですから、 セキュリティ設計が必要です。
一方で社内ツールなど、 ネットワークにつながらず、ローカルでのみ使われるソフトウェアには、 基本的にセキュリティ要件がありません。 セキュリティ設計は不要です。
以上解説したように、どの品質特性を追いかけるべきかは状況により異なります。 特に再利用性とパフォーマンスについては、 実際の開発でもどうすべきか議論になりやすいですから、 各品質特性の性質を踏まえて適切な要件に落とし込むことが肝要です。
まとめ
以上、ソフトウェア品質特性にもとづくことが、 建設的にコードの良し悪しを議論するうえで重要であることを述べました。
- ソフトウェア品質特性の観点でコードの良し悪しを議論すること
- 原理原則や設計ノウハウを踏まえて議論すること
- サービスの成長度合いと設計戦略の観点を持つこと
より高い解像度で判断できることが、サービス成長につながるでしょう。
おわりに
弊社DMM.comでも、コード改善は目下重要な取り組みとなっています。
弊社にはDMMプラットフォームがあります。 アカウント管理、決済、不正対策、DMMポイントといった、 弊社のさまざまなサービスで共通利用される基盤です。
変更容易性の足かせとなっている技術的負債を解消し、 DMMプラットフォームの開発生産性を高めるのが私のミッションです。
エンジニアの設計スキルを高めるため、 ワークショップ形式の勉強会を開催したり、 個別のチームに直接入って設計指導する、といった活動を展開しています。 直近で指導に入ったチームでは、 私から見ても十分に品質が高いと言えるドメインモデルを設計できています。
弊社DMM.comは組織規模が大きく、 コード改善をどのようにスケールさせていくかが非常に大きな課題となっておりますが、 そうした課題を乗り越え着実に成果に結びつけていきたいと考えております。