美しいソフトウェアをなぜ、どうやって書くか

  • 74
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

最近、デザイナー(兼ほとんどエンジニア)の先輩と話していて確信したのが、ソフトウェアデザインはエンジニアが相手のデザインであるということです。デザイナーとエンジニアがやっていることに似通っていることがいろいろと挙がりました。

ソフトウェアも良さを「美しい」とか「きれい」とか表現すると思います。でも美しさ難しいです、人それぞれあると思います。僕の中で思っている美しさがなんなのかを吐き出してみます(実際この通りに書けてるか、これが本当に美しいのか、とか言われるとちょっとドキッとしそうなので自戒の念も込めて・・)。

なぜ美しいソフトウェアを書かねばならないのか

ハッカーと画家(翻訳版)に出てくる一文(原文はSICPからの引用)です。

プログラムは、人々がそれを読むために書かれるべきである。 たまたま、それが計算機で実行できるにすぎない。

以下はハッカーと画家からの前後の引用です。

ソフトウェアがやらなければならないことのひとつに、 自分自身を説明するということがある。 だから、良いソフトウェアを書くには、ユーザがどれだけ何も知らないかという ことを理解する必要がある。 ユーザは何の準備もなくやって来て、いきなりソフトウェアに向かい、 マニュアルなんか読もうともしないだろうから、 ソフトウェアはそういう人が期待するように振舞うのが良い。 この点で、私が知っている最高のシステムは、1985年当時の、オリジナルのMacintoshだ。 それは他のほとんどのソフトウェアが決して為し得なかったことを為した。 何もしないでも、ちゃんと動いたのだ。

ソースコードもまた、自分自身を説明すべきだ。 プログラミングに関して皆に一つだけ引用文句を覚えてもらえるならば、 私は「計算機プログラムの構造と解釈」の冒頭のこの文を選ぶ。

プログラムは、人々がそれを読むために書かれるべきである。 たまたま、それが計算機で実行できるにすぎない。
ユーザに対する共感だけでなく、コードを読む人に対する共感も必要だ。 それはあなた自身のためでもある。あなた自身もあなたのコードの読者だからだ。

美しさの定義

このコードで伝わるだろうか、より簡単な、よりわかりやすい書き方ができるだろうか、など読み手(=エンジニア)のことを考えながら書いています。本当に優れたソフトウェアは、(コメントやファイル構造も含め)コードを読んだだけで意図や動きが伝わり、使い方がわかるものだと思います。表面に触れただけで類推ができ、そして内部に触れた時に雑念が生まれないことが大切だと思っています。

統一感・秩序

統一感や秩序は、コードの読みやすさに直結します。例えば下記のような事柄です。

スタイルの統一

  • インデント、空白の位置や有無が統一されていると、読む際のノイズ(雑念)が減ります。書く際は迷わずに済みます。
  • インスタンス変数はメソッドより上で宣言する、定数は一番上、などと統一されていると、それらを探すときに見るべき位置が瞬時に分かり、また書き足す際の迷いが減ります。
  • スタイルは多くの場合、言語ごとにベストプラクティスとされるものが存在するのでそれに合わせておくと他のコードベースとも統一が取れます。(例:Rubyのrubocopのガイドライン、GoogleのJavaのガイドライン、etc.)

言葉の統一

クラス名、メソッド名、ファイル名の間に揺れがないようにすることはもちろん、作ってるソフトウェアに現れる概念を表す単語を統一します。例えば、写真アルバムアプリの「アルバム」は、Album、MediaLibrary、PhotoCollectionなどと様々な名前で表現できると思いますが、1つの単語に統一しておくべきです。これによって、変更したいファイルの検索や、コード上のクラス名からの意味の類推が簡単になります。同じ事柄に2つ以上の名前が付いていると類推や検索が難しくなります。

また、可能な限り、コードに関わる全てのもの(コミットメッセージやコメントも)に対して英語を使用すると、上記のメリットを得られつつ、さらに言語間での頭の切り替えのコストが下げられます。プログラミング言語は英語を基準に作られ、書かれ、読まれるものであるため、英語であることはそれだけ統一感を高めます。

(コミットメッセージの書き方について: https://hiroki.jp/2012/09/05/5523/)

ファイル・クラス配置の統一、ソフトウェア構造の統一

例えばModelに対応するViewクラスがいる時に、その配置が統一されているとファイル間の行き来が楽になります。

また、「ドメイン駆動開発」や「クリーンアーキテクチャ」のように、ソフトウェアをどのように分割するかの方法論が様々ありますが、なるべく広い範囲で統一されていると混乱が少なくて済みそうです。

インタフェースや振る舞いの統一

例えばAPI設計については、認証方式やURLの決め方、エラーの返し方、リソース(レスポンス構造)の定義、などといった、API全体の関心事の上に、提供したい機能を載せます。このとき、API全体の関心事の部分はAPI全体で共通になっていると、使う側が個別対応したりドキュメントを読みあさる必要がなくなります。

別の例として、個々のリソース(投稿、ユーザ、etc.)のAPIに対して検索機能を付ける場合、検索クエリの指定方法はどのリソースに対しても同じになっていると、使う側のわかりやすさと負担軽減ができると思います。逆に、同じ検索クエリが指定された時の検索結果の絞り込み方法はなるべく同じになっていることが望ましいです。

パターン化

上の内容と少しかぶりますが、統一性は同じパターンを使い回すことで実現される場合もあります。同じような処理がいくつも登場する場合、なるべく同じやり方でできるようにしそれを使うようにすると、一度理解したものが再登場したときに高速に理解できます。

ロジックの中に直接 x > y ? x : y って書かれているより、 max()を定義して max(x, y) と書かれていた方が読みやすいです。

スタンダードへの準拠

ベストプラクティスや標準、デファクトスタンダードにはそれだけの意味があります。これに従うことは、読み手・使い手の共通知識による理解を促進します。美しさとは違いますが、積み上がった様々なノウハウを一度に適用することができたり、相互運用性が高まる場合もあります。

例えば、APIにはSOAPとXMLではなくRESTfulとJSONを使います。ぶっちゃけ個人的にAndroidでSOAP APIを叩いたときは悲惨でした・・。Railsはなるべくレールを外れない(Railsの命名規則やファイル配置などの規則に従う)ようにします。

シンプルさ

ソフトウェアの全体設計、提供する振る舞いの仕様、何かのアルゴリズムの記述方法(コード)、etc.、全て可能な範囲でシンプルなものに保ちます。シンプルには「簡単で理解しやすい」という意味があります。シンプルさの大敵、複雑さや無駄があるということはそれだけ雑念をもたらします。

例えば、アルゴリズム(ビジネスロジックも含む)を記述する際に、取りうる限り最も簡潔な表現を追求します。「実はその処理書かなくても動くんじゃない?」と思った時、それは一番簡単ではないかもしれない、無駄があるかもしれないということです。「短い」と「短くするためにわかりにくいことをやっていない」を両立して、「簡潔」を目指します。

ただし、元々複雑なものを無理やり簡単に見せると、今度は「どのように動いているのかわからない」といった現象を発生させる場合があるので注意が必要です。その場合は、複雑なものを「簡単なものの集合」で表現するとうまく解決できることが多いように感じます。つまり問題を分割します。

「短い」「行数が少ない」が全て善ではない例も1つ挙げておきます(アドリブなので微妙かもですがお許しください)。

def post_comment
  return error_status 404 unless blog_post.present? && !blog_post.draft? && !blog_post.deleted? && user_session.present? && !user_session.expired?
  ...
end

やったぜ一行で書けたぜ!!じゃねぇよwというやつです。行数増やしてでも説明変数や別のメソッドを導入したほうが読みやすいので、行数や現れる要素が少ないことが一概に良いとは言い切れないことがわかります。

def post_comment
  is_valid_blog_post = blog_post.present? && blog_post.published?
  return error_status 404 unless is_valid_blog_post && valid_user_session?
  ...
end

# In Helper
def valid_user_session?
  user_session.present? && !user_session.expired?
end


# In BlogPost model
def published?
  !(blog_post.draft? || blog_post.deleted?)
end

"ただ1つ"であること、"1回のみ"はシンプルさ

(追記)

ただ1つしかない、ただ1箇所にしかない、ただ1回しか書き込まれない、1という数字には美しさがあります。

一番簡単な例はコピペされたコードが複数回登場するのを消して、1回だけにするとコードのかさがぐっと減るパターンです。一方で、コードの扱いだけじゃなく、データの扱いについても"1"が重要です。

例えば、Single source of truth(単一の真実の源・・?)という言葉を時たま見聞きします。これは例えば、データの読み込み・保存を取り扱うときに、ただ1カ所にだけデータを置くようにすることで常に最新の値を参照できるし、同期の複雑さや不整合が出にくいということを指します。キャッシュ処理は可能限り避けられるとシンプルで安全だという話でもあります。

また、最近の言語ではローカル変数も「一度しか代入できない」変数だとマークするものが出てきています(Swift、Kotlin)。変数の中身が変わらないということは、変わることを考えなくてすむので、変わるものよりずっと簡単です。ローカル変数だと範囲が狭くて効果は限定的ですが、不変(immutable)なオブジェクトにすることは大きな効果を発揮するはずです。

車輪の再発明を避ける

実はその処理、言語機能やフレームワーク、別のライブラリで提供されいているよ?ということはよくあります。無駄を省くという意味では、他の方法がないかどうかチェックしてみることは重要だと思っています。

意味が伝わる

全てのものごとに意味があります。書き方を工夫することで、コードの中に「意味」を織り込むことができます。最近使った例をいくつか挙げます。

(メソッドの中の)コード

読む際にコードが持つ意味を伝わりやすくする工夫をしています。コメントはもちろんですが、下記のような方法で行間の意味を作ることがあります。

  • メソッドの途中で現れる空行:意味のある処理の単位をまとめる
  • 説明変数:処理の返り値や計算結果に名前をつける場合に使う(int lastIndex = list.size() - 1など)
  • 早期リターン( if (...) { ...; return; } ):最も一般的に通るコースではないことを表す

(ここの内容はほとんどリーダブルコードに書かれている内容な気がするのでそちらを読んだほうが良いのかもしれません。)

(クラスやメソッドの)インタフェース

そのクラスやメソッドを使う人に、その意味や制約が伝わるような工夫をしています。

  • 重たい処理をする場合はgetXXX()ではなくてcalculateXXX()というメソッド名にすることで、計算処理であることを伝える
  • ある条件を満たしている時だけ処理を実行する場合は、xxxIfNecessary()、xxxIfNeeded()などと後ろにつけて伝える
  • 適切な型やnull許容かどうかの情報をつけたり、privateや書き換え不能(Java: final, Swift: let)で宣言したりする(と、宣言通りに使うことが強制される)
  • クラスの依存関係(そのクラスが必要とするオブジェクト)はsetterではなくnewの引数にすることで、必須であることを伝える (※DI使う場合はこの限りではない)

シグニファイア

上記のクラスの例のように、誰かが「使う」ものに関しては、デザイン界で著名な本「誰のためのデザイン?」の著者が提唱している、「シグニファイア」についてよく考えています。signifier、つまりサイン、記号に関わります。

※誰のためのデザイン本ではアフォーダンスという言葉で紹介されていて、僕もアフォーダンスって言ってましたが、それは誤用だと著者自身が言っているようです。

try! SwiftではSwift版誰のためのデザインの発表がありました。

美しさを習得する

リーダブルコードやその他の本を読む

新卒研修でこの本をまとめたような内容がありました。必読だと思いました。Qiitaでも度々取り上げられているようです。 http://qiita.com/Kenya/items/faec4cc374edd5ffbba6

研修をしてくださった先輩の良いソフトウェアを作るための本まとめ記事です・・!
http://qiita.com/hirokidaichi/items/d30714f0698dcff1200f

ツールを使う

全部完璧に揃えるのは正直面倒なところもあります。そこでチェックツールやIDEの機能を使えば強制できたり、自動修正できたりします。

Code ClimateHoundCISideCIのようなサービスを使用するという手もあります。

他人のソースコードを読む

オープンソースも含め、コードを読む回数が増えるとそれだけインプットの量が増えます。そのコードが読みやすかったか、どういう意味があったのかを考えると、あとで良いやり方を再利用したり、ダメなものに気づくことができます。

ネタですがこんなサイトもあります。。w http://unkode-mania.net/

既存の設計パターンを学ぶ

特に「設計」レベルの話になると、パターン化されていることが多いように感じます。それらのパターンが、エンジニア間で共通知識になっていれば、設計を見たり考えたりする際に理解を早めることにつながります。

例えば、GoFデザインパターンをある程度(必要に応じて調べる程度)把握していると、必要な箇所に導入したり名前を揃えたりすることで、パターン化できます。最近使った例として、JavaでBuilderと命名されたクラスが存在するということだけで引数の渡し方がわかりやすいです。

また、「ドメイン駆動設計」や「クリーンアーキテクチャ」といったあたりは、全体設計をどうしていくべきなのかというところに大きく影響を与えていると思います。完全に取り込むのはコストが大きくかかるものの、それに近い命名や構造を取るケースはいくつか見たことがあります。構造を美しくする方法論としてはBoundariesというスライドがとても良かったです。

デザインしようと考えながら書く

特段勉強したわけではないのですが、ユーザ視点、つまりコードを読んだり使ったりするエンジニアを相手にデザインしようと意識することで自然と考える機会が生まれたような気がします。もしかしたら、デザインについて学ぶことも何か効果を発揮するかもしれません。

くくり

(実は「スタイルの統一」のところだけ、前後と書き方は同じなのに、わざと箇条書きスタイルを適用していました。雑念を感じた方はスタイルの統一をぜひ徹底してください・・!)

ハッカーと画家からもう一文引用します。

ハッカーが実際にやろうとしていること、すなわち美しいソフトウェアを デザインするということを測るのは、ずっと難しい。 良いデザインを判断するためには、 デザインに対する良いセンスが必要だ。

センスは時に「生まれつきの素質」という言い方をする場合もあります。確かに得手不得手はあるのかもしれません。しかしこれは「生まれつき」ではないと、僕の考え方を決定的に変えたツイートがこちらです。

May the sense be with you :)