What is it, naokirin?

デザインの七原則と個人的にプログラミングで考えていること

今回はプログラミングにおける、個人的に考えていることを「誰のためのデザイン?」で紹介されているデザインの七原則と対比して整理していきたいと思います。まだまだ整理しきれておらず、荒削りな部分もあると思いますが、ご了承ください。

「デザイン」と「設計」

日本語では「デザイン」と「設計」は微妙に意味の異なる単語ですが、英語においては同じ意味です("Design"の日本語訳が「設計」という意味です)。プログラミング界隈で有名なものであれば、「デザインパターン」などはそういう意味ですね。そう捉えると、いわゆる海外における「デザイン」に対する知見は、プログラミングにおける設計にも応用できる可能性があるのではないかと思います。

これまでプログラミングをする中で考えていたことについて、再度整理していくにあたり、「誰のためのデザイン?」という書籍が非常に参考になりました。

「誰のためのデザイン?」では、デザインの七原則が紹介されています。

  1. 発見可能性:どのような操作が可能で、どのような状態かわかる
  2. フィードバック:操作した結果がわかる
  3. 概念モデル:どう動くかの簡素化された説明
  4. アフォーダンス:物理的なモノと人との関係
  5. シグニファイア:人に適切な行動を伝えるヒント
  6. 対応づけ:要素同士の関係性
  7. 制約:行動の選択肢を制限する

プログラミングや設計の際にも、この原則が自分の根底にある考え方と非常に合致していると感じたため、このデザインの七原則をもとに再整理してみました。

「誰のため」のプログラミング

七原則に入っていく前に、「誰のため」を明確にするべきでしょう。

プログラミングは、なにかコンピュータ上で実行できるコードを書き、それをプログラムとして実行することで達成したい挙動をさせることになります。

この点においては、「達成したいこと」を求めている人が「誰」になります。これは非常に大事なのですが、もう一点、忘れてはならない対象となる人がいます。

それは、「コードやプログラムを改修、運用する人」です。この人は、チームなどにおける自分以外の人であったり、引き継ぐ人であったり、さらには未来の自分であったりします。

プログラミングの設計においては、「達成したいことを求めている人」と「コードやプログラムを改修、運用する人」を意識する必要があります。

今回はこのうち、「コードやプログラムを改修、運用する人」に焦点を当て、それらの人がコードを読む際のことを考えていきます。

七原則とプログラミング

各原則に入る前に、前提となる重要なポイントがあります。それは、このコードやプログラムに触る人の知識です。

例えば、このプログラムではPythonで書かれている、MVCアーキテクチャが導入されている、Reactが使われている、デザインパターンを知っているなどです。また、メタプログラミングや言語やフレームワーク仕様などを知っているかも重要です。

とはいえ、前提知識の全くない人に向けて書くわけにはいきません。基本的な知識はありつつも、あったとしても難しいという状況を避けるのが大事になります。

発見可能性

まずは発見可能性です。

皆さんは、プログラミングをしている際に「今から実装する機能のうち、この機能がすでに実装されていないか?」ということを調べた経験はあるでしょうか?おそらくプログラミングをしていれば、ほとんどの人が経験していると思います。たとえば、ライブラリやフレームワークにある機能を探すときもそうですし、チーム開発などですでに実装された機能としてないかを調べるときもそうです。

また、「この関数はこの値を渡したときになにを返すのだろうか」ということを知りたい場合もあります。

こうした私たちが既存のコードからの「発見」がスムーズにできるためには何に気をつけるべきでしょうか。

命名

まずは命名です。これは、プログラミング界隈では常識として語られる「名前重要」の法則です。

名前は探す際の重要なヒントです。

アーキテクチャのレイヤー

次にアーキテクチャにおけるレイヤーです。どのレイヤーに定義されるかというのは重要なヒントになります。

例えばですが、本体価格に対する消費税を計算する関数が、UIのレイヤーに定義されていたらどうでしょうか。おそらく気づくことが難しいでしょう。これらが適切なレイヤーにあることで、発見可能性を高めることができるはずです。

定数

定数は、マジックナンバーの解消に使われることのある「システムの実行中に変更されない値」です。定数になることで、「システム全体を通してどのような値を想定しているか」がわかりやすくなります。

コメント

コメントは、コードだけではわかりにくい意図を伝える際に重要です。

コメントがなくともわかる方がよいですが、残念ながらコードだけでは伝わりにくいことは多々あります。そうした際に、「この機能はこうした意図や注意点がある」と示すことができるコメントは、発見可能性を上げることに一役買います。

宣言的プログラミング

宣言的プログラミングを活用することで、コードの詳細を細かく見ることなく、期待されている動作を理解しやすくなります。

ガード条件

他とやや粒度が異なりますが、ガード条件は、期待される動作を素早く発見するのに役立ちます。

対称性

対称性は重要な発見の要素になります。

たとえば、Rubyの配列(Array)には、 #first という先頭の要素を取得するメソッドが存在します。ここで、最後の要素を取得したい場合、どのようにすればよいと考えるでしょうか?

たいていの場合、対になる #last が存在すると考えると思います。これは対称性を暗黙のうちに想定しているからです。

また、クレジットカードのポイントを表すクラスの中の(publicな)メソッドが、 incrementgivePoints となっていたらどうでしょう。前者は挙動を表していますが、後者は意図を表しています。このような対称性のない状態よりも対称性があるほうが、発見しやすくなります。

フィードバック

フィードバックに関して、人間の情報処理の観点からいくつか注意するべき点があります。

フィードバックでは、「すばやく」「適切な情報量で」「計画的であり」「優先度の高いものが優先される」必要があります。

コード量

あなたが特定の機能が実装されているか探す際に、対象となりそうなクラスのコードの行数が1万行あったらどうでしょうか。そこにメソッドが2000個も定義されていて、継承元のクラスも同様だとどうでしょうか。

このクラスを読む際に与えられるフィードバックは、非常に遅く、膨大な情報量になっていることが容易に想像できます。このことから、コードの読み手に対して、適切な量の情報を与えられるように実装も整理する必要があると考えられます。

命名

コード量と合わせて、グルーピングする名前も重要なフィードバックです。

呼び出しているメソッド、関数名やクラス名、変数名などは、呼び出し元を詳細にたどることなく知ることができる重要な情報です。詳細を追わずとも理解できるような名前をつけていくことが、すばやく情報を取得できるようにするためには重要です。

概念モデル

概念モデルは、どのように動くかという理解をするための簡素化された説明です。

たとえば、センサー式の自動ドアは人を感知してドアが開く仕組みですが、この仕組みについてなんとなく「『特定の範囲に人がいる』ということを検知してドアが開くようになっている」という理解をしていると思います。いっぽうで詳細に踏み込めば、反射した赤外線を検知する方式や、横切った際に光線を遮ったことを検知する方式、超音波で検知する方式など、様々な方式があります。簡素化した説明では「特定の範囲に人がいる」ことについて説明していませんが、ほとんどの場合、この説明に従って、スライド式のノブや取っ手のないドアは自動ドアだろうと思って「特定の範囲」に近づいてドアが開くことを期待しています。

一方で、この概念モデルから外れた動作をする場合に、利用する人は困惑します。

メタファー、共通の語彙、ユビキタス言語

XPで語られるメタファーやドメイン駆動設計におけるユビキタス言語は、関係者の中で共通の概念モデルを構築するためのものです。

UI/UXデザイン等においては、一般的にユーザーが持つであろう概念モデルを想定していますが、プログラミングにおいてはコミュニケーションも含めて概念モデルを作り上げる場合もあると思います。

フレームワーク

フレームワークが持つ仕組みは、概念モデルになっている場合が多いと思います。例えばですが、Ruby on Railsは、有名なWebのMVCフレームワークですが、Ruby on Railsを導入しているシステムがSMTPサーバだったらどうでしょうか。事前の説明がなければ、Ruby on Railsである時点で、ほとんどの人はWebサーバとして想定して読み始めると思いますが、じつはSMTPサーバであるという事実に気がつくまでに時間を浪費するかもしれません。

アフォーダンス

アフォーダンスは「物理的なモノと人との関係」とされています。これは、「人に知覚される、どのように使用できるかというモノの特性」を表します。プログラミングで特に注意すべきなのは、「偽のアフォーダンス」と「非表示のアフォーダンス」です。

偽のアフォーダンス

偽のアフォーダンスは、見せかけのアフォーダンスです。つまり、人には「このように使用できる」と思われるにも関わらず、実際にはそのような使用はできないような状態を指します。これは勘違いや誤解に繋がります。

プログラミングでは以下のようなものがあります。

  • 名前と挙動の不一致
  • コメントと挙動の不一致
  • アーキテクチャのレイヤーと挙動の不一致
  • 不適切な公開範囲

コメントは多く書きすぎると追従が難しくなっていくため、必要最低限に留めるようにしています。

非表示のアフォーダンス

非表示のアフォーダンスは、利用可能な特性であるにも関わらず、そのように認識できない状態です。

プログラミングでは以下のようなものがあります。

どれも疎結合や柔軟性の確保、シンプルな動作を実現するために重要な手段ですが、使いすぎるとコードを読む人にはどのような動作をするのか、またどのように利用できるかがわからなくなっていきます。

シグニファイア

シグニファイアとは、人に適切な行動を伝えるヒントです。

プログラミングでは、以下のようなものがあります。

  • 名前
  • コメント
  • 定義されたレイヤー
  • インターフェース
  • 継承、Mix-in
  • 公開範囲

対応づけ

人が、適切にモノを利用できるためには、行動と想定される結果の関係性が紐付けられている必要があります。

例として、複数のスイッチと複数の照明があった場合、どのスイッチがどの照明に対応しているかわからないと、それぞれを切り替えてみて確認することになります。

対称性

対称性は、発見可能性でも記載しました。

対応づけという観点では、例えば adddelete というメソッドがあった場合、 addして delete すれば、基本的には元の状態に戻るとたいていの場合は考えると思います。これは、対称性のある名前のメソッドは、対称的な挙動で対応づいていると考えるからです。

ビジネスドメインユビキタス言語

ビジネスで利用されている名前と同じ名前がコード上で出てくれば、それはそのビジネス上の対応した事柄と対応づいていると考えると思います。そのため、名前は注意深く付ける必要があります。

制約

制約は、行動や想定に一定の制限をかけ、適切な利用を促すものです。

自動テスト

自動テストは、想定した挙動となっているかを機械的に反復して検証できるようにします。これにより、変更を加える場合にも、想定した挙動から変更されていないかを適切に検証できます。

リンター

リンターは、プロジェクト等で規定された、特定の利用方法やコードの書き方になっているかを検証できるツールです。これも、コードに変更を加える人に、適切な制約を課してくれます。

型は、利用方法に強い制約を与えます。その意味では、この制約を与えやすい静的型付き言語はこの制約を与えやすいといえます。

まとめ

今回は、普段のプログラミングの設計などで考えていることを、デザインの七原則とプログラミングを対比して整理してみました。

まだまだ、整理しきれていない印象はあるので、もっとブラッシュアップできればと思います。