プログラミングにおける原則一覧


プログラミングにおける原則

様々な研究者やプログラマーが自身の経験や考えを元に、よりよりコード、よりよいソフトウェアのために必要な考え方や教訓がプログラミングにおける原則として存在します。
自分のためにも複数存在する原則をDartのサンプルコードを用いながらわかりやすく簡潔にまとめたいと思います。

Abstraction principle(抽象化の原則)

プログラムの重要な機能は、ソースコードの1カ所にまとめて実装する必要があるという原則です。同じような機能が別々のコードで実行されている場合は、さまざまな部分を抽象化して1つにまとめると効果的に機能します。

Code reuse(コードの再利用)

コードの再利用とは既存のソフトウェアまたはソフトウェアの知識を活用し、新たなソフトウェアを構築することを指します。

Command–query separation(コマンドとクエリの分離)

副作用を最小限に抑えるための設計原則で、あるメソッドは何らかのアクションを実行する「コマンド」または呼び出し元にデータを戻す「クエリ」のいずれかでなければならず、両方の機能を兼ね備えないようにします。

原則に則さない例

class Sample {
  int x = 0;
  
  int commandQuery () {
    x++;
    return x;
  }
}

原則に則した例

class Sample {
  int x = 0;
  
  void command () {
    x++;
  }
  
  int query () {
    return x;
  }
}

Composition over inheritance(継承よりも委譲)

クラスを拡張する場合は継承ではなく委譲(合成)を用いて拡張します。

Defensive programming(防御的プログラミング)

予想外の事態が発生してもソフトウェアが機能し続けることを目的として高可用性、安全性を高めるための設計・原則です。

防御的ではないメソッド

int getNextNumberUpToTen(int x) {
  return x + 1;
}

防御的なメソッド

int getNextNumberUpToTenDefensive(int x) {
  // 引数が期待するものと合致しない場合の処理は設計や仕様によって左右される
  if (x > 9) {
    return 10;
  }
  return x + 1;
}

Dependency inversion principle(依存性逆転の原則)

ソフトウェアモジュールの結合度を下げるための原則です。原則は下記の2点を提唱しています。

  • 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも抽象(例としてインターフェース)に依存するべきである。
  • 抽象は詳細に依存してはならない。詳細(具象的な実装内容)が抽象に依存するべきである。

下記は依存性逆転の原則を適応する前の状態です。

下記は依存性逆転の原則を適応した後の状態です。

Deutsch limit(ドイッチュ限界)

ビジュアルプログラミング言語の情報密度に関する下記の警句のことです。

ビジュアルプログラミングの問題は、画面上にいちどに50を超えるビジュアルプリミティブを表示できないことである。

ビジュアルプログラミング言語におけるプリミティブとは、プログラムを構成する独立した視覚的要素のことです。ビジュアルプログラミング言語のスケーラビリティの低さに関する問題を提起している内容です。

Discoverability(発見可能性)

ファイル、データベース、またはその他の情報システムの検索で、コンテンツや情報の一部を見つけることができる程度のことを指します。

Don't repeat yourself(DRY原則)

これは下記の原則のことです。

すべての知識はシステム内において、単一、かつ明確な、そして信頼できる表現になっていなければならない。

しかし、重複させることに意味があること(ミラーリングやコードのバージョン管理等)は原則の対象外です。

Fail-fast

故障やエラーを示す可能性のある状態を即座に報告(例外投げる等)する原則のことです。

Gall's law(ゴールの法則)

John Gallの著作「発想の法則:物事はなぜうまくいかないか」で示された下記の経験則です。

A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.
日本語訳:正常に動作する複雑なシステムは、例外なく正常に動作する単純なシステムから発展したものである。逆もまた真であり、ゼロから作り出された複雑なシステムが正常に動作することはなく、またそれを修正して動作させるようにもできない。正常に動作する単純なシステムから構築を始めなければならない

GRASP(汎用的責任性割り当てパターン/原則)

GRASPはGeneral Responsibility Assignment Software PatternあるいはあるいはPrincipleの頭字語であり、オブジェクト指向設計に向けた九つの原則セットまたはパターンセットのことです。九つのパターン/原則は下記です。

  • Information Expert(情報エキスパート)
    メソッドや計算フィールドなどの責任をどこに委ねるかを決めるために用いられるパターン/原則です。責任を割り当てる一般的な方法は、与えられた責任を見て、その責任を果たすために必要な情報を判断し、その情報がどこに保存されているかを判断することです。
  • High Cohesion(高凝集)
    高凝集は、オブジェクトが適切に責務を集中させており、管理・理解が可能な状態を保つための尺度のことです。高凝集性とは、ある要素の責務が強く関連しており、またその要素に集中していることを意味します。
  • Low Coupling(疎結合)
    結合度は、1つの要素が他の要素にどの程度強く接続されているか、知識があるか、または依存しているかを示す尺度のことです。結合度は可能な限り小さくしたほうがよいとされています。
  • Creator(クリエイター)
    クラスの新しいインスタンスを作成することに責任を持つのは誰かという問題を解決するパターン/原則です。たとえば、二つのクラスA,Bがあったときに下記の条件に当てはまる場合はクラスBがクラスAの作成に責任を持つべきとされます。
    1. BがAを包含する
    2. Aを合成・集約する
    3. Aを密接に使用する
    4. Aの初期化情報を持っている
  • Controller(コントローラ)
    システム全体やユースケースシナリオを表現するユーザインタフェースでないクラスにシステムイベントを扱う責務を割り当てるというパターン/原則です。コントローラは、UI層以上にあって、システムに対する操作を受け取り調整する(制御する)最初のオブジェクトとして定義されます。
  • Indirection(間接化)
    オブジェクト同士が直接つながらないように、コンポーネントまたはサービスを仲介する中間オブジェクトを用意して、それに責務を割り当てるパターン/原則です。
  • Polymorphism(多態性)
    型別の動作のすげ替えならば、同じ動作名義に対してオブジェクト別の異なる動作内容を結合できるポリモーフィックな型を用意するパターン/原則です。
  • Protected Variations(保護的変容)
    周辺の要素(オブジェクト、システム、サブシステム)の変動から注目する要素を保護するために、不安定な部分をインタフェースを用いて収束させ、ポリモーフィズムを用いてインターフェイスを実装するパターン/原則です。
  • Pure Fabrication(純粋造形)
    高凝集性や疎結合性、再利用性を促進するために、問題領域の概念を一切表現しない便宜上のクラスを作り、そこに高度に凝集した責任を割り当てるパターン/原則です。ドメイン駆動設計ではサービスに該当するものです。

If it ain't broke, don't fix it(壊れていないなら直すな)

壊れていないなら直すなということは言い換えれば「システムが上手く機能しているのであれば、それを変える必要はない。」ということを意味しています。

Information hiding(情報隠蔽)

変更の可能性が高い設計上の決定を分離することで、設計上の決定が変更された場合にプログラムの他の部分を大規模な修正から保護する原則です。具体的にはプログラミング言語の機能(プライベート変数など)や明示的なエクスポートポリシーを用いて、クラスやソフトウェアコンポーネントの特定の機能がクライアントからアクセスできないように制限します。

Interface segregation principle(インターフェイス分離の原則)

使用しないメソッドに依存するようにコードを強制するべきではないという原則です。インターフェースは小さくより具体的なものにすることで不必要なメソッドへの依存が発生しないようにします。

原則に則さない例

// Dartではクラスを宣言すると暗黙的にインターフェースも生成される
abstract class Animal {
  void sleep();
  void walk();
  void fly();
}

class Human implements Animal {
  void sleep() {
    print('sleep');
  }
  
  void walk() {
    print('walk');
  }
  
  void fly() {
    // 人間には不要なメソッド
  }
}

class Bird implements Animal {
  void sleep() {
    print('sleep');
  }
  
  void walk() {
    print('walk');
  }
  
  void fly() {
    print('fly');
  }
}

原則に則した例

// Dartではクラスを宣言すると暗黙的にインターフェースも生成される
abstract class Animal {
  void sleep();
  void walk();
}

abstract class AnimalWithWings implements Animal {
  void fly();
}

class Human implements Animal {
  void sleep() {
    print('sleep');
  }
  
  void walk() {
    print('walk');
  }
}

class Bird implements AnimalWithWings {
  void sleep() {
    print('sleep');
  }
  
  void walk() {
    print('walk');
  }
  
  void fly() {
    print('fly');
  }
}

Inversion of control(制御の反転)

従来の制御フローと比較して、制御フローを反転させることを意味します。
オブジェクト指向プログラミングにおいては下記の主要な実装方法があります。

  • Factory パターンの使用
  • Service locator パターンの使用
  • 依存性の注入を使用
  • Template method パターンの使用
  • Strategy パターンの使用
  • 文脈化された参照

KISS principle(KISSの原則)

KISSは「Keep it simple, stupid.」の頭文字を取ったもので能な限りシンプルさを保つべきであるとする原則です。

Law of Demeter/principle of least knowledge(デメテルの法則/最小知識の原則)

あるオブジェクトが自分以外のその他のオブジェクトが持つプロパティやメソッドに関する知識を可能な限り必要としないようにすべきという原則です。

Liskov substitution principle(リスコフの置換原則)

SがTのサブタイプである場合、プログラム内のタイプTのオブジェクトは、そのプログラムの望ましいプロパティを変更せずに、タイプSのオブジェクトに置き換えることができる(置き換えても問題がおこならない)ようにするという原則です。継承やダックタイピング、インターフェース等を用いた多態性(is-aの関係)の実現において正しく、そして問題が起きないようにするため原則です。
原則でサブタイプは下記の動作条件を満たす必要があります。

  • 事前条件を派生型で弱めることは可能ですが、強めることはできません。
    ※事前条件は処理に必要な情報や引数の数や型などを指します。
  • 事後条件を派生型で強めることは可能ですが、弱めることはできません。
    ※事後条件は戻り値の型や副作用、例外処理などを指します。
  • 不変条件は派生型でも保護する必要があります。
    ※不変条件に関してはこちらを参照ください。
  • スーパータイプから新規追加またはオーバーライドされたメソッドは、スーパータイプで許可されていない方法でプロパティーを変更してはいけません。

Ninety–ninety rule(90対90の法則)

Tom Cargillによる下記の格言のことを指します。

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.
日本語訳:コードの90%が、開発時間の最初の90%を占めている。残りの10%のコードが、他の90%の開発時間を占めている。

Offensive programming(攻撃的プログラム)

バグや予想外の事態が発生した場合は、発生したタイミングで動作を停止させる(例外を投げる等)設計・原則です。

攻撃的ではないメソッド

int getNextNumberUpToTen(int x) {
  return x + 1;
}

攻撃的なメソッド

int getNextNumberUpToTenOffensive(int x) {
  if (x > 9) {
    throw Exception;
  }
  return x + 1;
}

Open–closed principle(開放/閉鎖の原則)

ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張のために開いている必要がありますが、修正に関しては閉じている必要があるという原則です。

原則に則さない例

bazの呼び出しが必要になったときにmethodを修正しなければならない実装

class Sample {
  String value;
  
  Sample(this.value);

  void method() {
    switch(value) {
      case 'foo':
	print('foo!');
	break;
      case 'bar':
	print('barbar!!');
	break;
      default:
        print('default');
        break;
    }
  }
}

原則に則したした例

bazの呼び出しが必要になったときにクラスを拡張すれば実現可能な実装

abstract class Abstract {
  void method();
}

class Foo implements Abstract {
  void method() {
    print('foo!');
  }
}

class Bar implements Abstract {
  void method() {
    print('barbar!!');
  }
}

class Sample {
  Abstract state;

  Sample(this.state);

  void method() {
    state.method();
  }
}

Principle of least astonishment(驚き最小の原則)

これは下記の原則のことです。

If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.
日本語訳:必要な機能でも驚くような場合(想定外な場合)は、再設計が必要かもしれません。

簡単に言うとソフトウェアやユーザーインターフェース、または設計等はユーザーやアクターが期待したり想定しうる動作を行う必要があり、そうでない場合は見直す必要があることを示す原則です。

Pristine Sources(原始的な情報源)

Red Hat系のLinuxディストリビューションで使われるパッケージ管理システムRPMを開発する際に考案された設計思想です。RPMは、パッケージ化および構成方法に関係なく、アプリケーションの開発者からのソースを使用するように設計されています。
詳しい内容に関してはこちらに記載があります。

Rule of three(3つのルール)

重複を避けるために類似したコードをいつリファクタリングするかを決定するためのコードリファクタリングの経験則のことで、重複が3箇所ある場合はメンテナンスのコストがリファクタリングのコストと潜在的な悪い設計を確実に上回り、重複2箇所しかない場合は上回らないかもしれないことを意味しています。
C++における3つのルールというのも存在しますが、ここではコンピュータプログラミングにおける3つのルールを紹介致しました。(C++もコンピュータプログラミング言語ですが、C++に限らず多くの言語に適応できるということです。)

Separation of concerns(関心の分離)

コンピュータプログラムを異なるセクションに分離するための設計原理です。プログラムを関心事が分離された構成要素(アーキテクチャ上の分類によってクラスや関数などに別れた要素)で構築することで、モジュールの更新、再利用、独立した開発が行いやすくなります。

Separation of mechanism and policy(機構と方針の分離)

機構(操作の認可と資源配分を制御するシステム実装部分)は、どの操作を認可するかやどの資源を割り当てるかといった決定を行う際の方針を表すべきでない(あるいは過度に制限すべきでない)とするものです。機構の実装と方針の仕様を分離することで、異なるソフトウェアが共通の機構と異なる方針を使用することが可能となります。

Single responsibility principle(単一責任の原則)

モジュール、クラス、または機能がそのプログラムの機能の1つの部分に対して責任を持ち、その部分をカプセル化する必要があることを示す原則です。責任は、そのシステムを使うアクター(ユーザやステークホルダー、画面等)に対して持つことになります。

原則に則さない例

class Employee {
  // アクターは経理部門となる
  int calculatePay() {
    // 本来は様々な計算を行って返却
    return 320000;
  }

  // アクターは人事部門となる
  String reporyHours() {
    // 本来は様々な計算を行って返却
    return '173時間';
  }

  // アクターはIT部門となる
  void save() {
    // 何かしらの保存処理
  }
}

原則に則した例

// アクターは経理部門となる
class Payment {
  int calculatePay() {
    // 本来は様々な計算を行って返却
    return 320000;
  }
}

// アクターは人事部門となる
class Attendance {
  String reporyHours() {
    // 本来は様々な計算を行って返却
    return '173時間';
  }
}

// アクターはIT部門となる
class EmployeeRepository {
  void save() {
    // 何かしらの保存処理
  }
}

SOLID(SOLID原則)

ここでも紹介している下記の原則の頭文字を取った原則です。

  • S...Single responsibility principle(単一責任の原則)
  • O...Open–closed principle(開放/閉鎖原則)
  • L...Liskov Substitution principle(リスコフの置換原則)
  • I...Interface segregation principle(インターフェイス分離の原則)
  • D...Dependency inversion principle(依存性逆転の原則)

Uniform access principle(統一アクセスの原則)

Bertrand Meyerによって提唱された下記の原則のことです。

All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.
日本語訳:モジュールによって提供されるすべてのサービスは、それらがストレージによって実装されるか、計算によって実装されるかを裏切らない、統一された表記法で利用可能であるべきです。

簡単に言うとオブジェクトの属性の取得、設定に対する操作に違いがあってはならないということを意味しています。

原則に則さない例

class Sample {
  String _foo;
  String _bar;
  Sample(this._foo, this._bar);

  String get foo => _foo;
  String getBar() => _bar;

  set foo(String foo) => this._foo = foo;
  String setBar(String bar) => this._bar = bar;
}

原則に則した例

class Sample {
  String _foo;
  String _bar;
  Sample(this._foo, this._bar);
  
  String get foo => _foo;
  String get bar => _bar;
  
  set foo(String foo) => this._foo = foo;
  set bar(String bar) => this._bar = bar;
}

Worse is better(悪い方が良い)

ソフトウェアの品質が機能によって必ずしも向上するわけではないという内容です。下記の中心的な特徴を強調しています。

  • シンプルさ
    実装が容易であること、さらに重要なことは、シンプルなインターフェースであること。
  • 正確性
    デザインが見分けがつくほど正しく、間違いがないこと。
  • 一貫性
    デザイン全体の統一性を意味し、単純性、完全性よりも重要であり、正確性と同様に重要である。
  • 完全性
    デザインは、期待されるすべての変数をカバーし、全体的な経験を提供すべきであるという考えを指す。ただし、シンプルさを増すために完全性を低下させてはならない。

YAGNI(YAGNI原則)

"You ain't gonna need it"を縮めたもので、機能は実際に必要となるまでは追加しないのがよいとする原則です。

Zen of Python(Pythonの禅)

Pythonプログラマが持つべき心構えを簡潔にまとめたものです。
Pythonがインストールされている環境なら対話型インタプリタでimport thisを実行することで表示されます。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Zero one infinity rule

特定のタイプのデータや構造のインスタンスの数を任意に制限してはいけないという主張のことです。具体的には、具象の生成を禁止するか、1つだけ許可するか、あるいは無限に許可べきという内容です。

サンプルコードの実行環境

Dart: 2.15.1
OS: Debian GNU/Linux 11 (bullseye)

参考