Software is Beautiful

第16回生産性を上げるソースコードの書き方

ソフトウェア開発の難しさ

ソフトウェアの開発プロジェクトに少しでも関わった人は誰でも知っていると思うが、ソフトウェア作りで最も難しいのは「スケジュール通りにソフトウェアを完成させること」である。

バグがなかなか修正できず泥沼にはまってしまったり、変更され続ける仕様のために当初立てたスケジュール表がまったく役に立たなくなってしまったり、スパゲッティコードに頭を抱えたりということはよくある。出口の見えない状況でソフトウェアエンジニアが過酷な労働を強いられる状況を「デスマーチ」death marchと呼ぶが、そんな言葉が存在すること自体が、ソフトウェア作りの難しさを表している。

ソフトウェアの開発は「生産活動」ではあるのだが、建物を建てる、料理を作る、野菜を育てる、ハードウェアを組み立てるなどの生産活動とは大きく違うのだ。

建物の場合で言えば、明確に定義された「設計図」がある。そして、その設計図に基づいて土台を作る、骨組みを作る、屋根を付けるなどの「行程」を定め、それを順番に実行していく。建物がユニークなデザインをしていても、⁠土台を作る」などの行程そのものはどの建物でもほとんど同じだ。そのため、100坪の建物の土台を作るにはどのくらいのコンクリートと人手が必要で、期間がかかるのかという見積りは難しくない。実際の作業も、天候などに左右されることはあるが、ほとんどの場合見積り通りの期間とコストで完了できる。

一方ソフトウェアの場合は、設計とコーディングが切っても切れない関係にある。良いソフトウェアを作るためには、プロトタイプを作りながら徐々にアーキテクチャを固めていき、実際にソフトウェアを走らせて問題点を洗い出しては、アーキテクチャや仕様に変更を加えていく、という作業が不可欠なのである。

ソフトウェア開発の特殊性

なぜソフトウェアがこんな特徴を持っているかに関してはいろいろな説明のしかたがあるが、すべてがデジタルであるソフトウェアの場合、建物における「建築⁠⁠、ハードウェアにおける「組み立て」行程のコストがゼロである、という説明が私は好きである。そのためソフトウェアの製造コストは、建物やハードウェアで言えば「設計図を書く⁠⁠、料理で言えば「レシピを作る」という、クリエイティビティが必要な知的生産活動に100%のコストが集中している。それゆえ、建物を建てるときのように「複数の行程に分割して、それぞれの作業コストを見積もる」ことが難しい。

リスクを最小限にするプロジェクト管理方法

ソフトウェアの生産性を上げるためには、プロジェクトに関わる人全員に、この「ソフトウェア作りはクリエイティビティが必要な知的生産活動である」という事実をちゃんと認識してもらう必要がある。建物を建てるときと違って「単純な工程に分割してコストと開発期間を見積もる」ことが難しいことを理解したうえで、それに応じたスケジューリングとリスク管理を行うことが必須なのだ。

この原稿を書いている2012年8月現在、前回でも紹介したneu.TutorというiPhone用の教育アプリの開発が終盤にさしかかっている。このプロジェクトを進めるうえで最も気を遣ったのが、米国の学校で新しい学年が始まる9月にあわせてアプリをリリースすることであった[1]⁠。

プロトタイプを作り始めた4月の時点で存在したのは、共同開発者であるアクティブラーニングの羽根さんの頭にある、漠然とした「ポケットの中にある家庭教師(Tutor⁠⁠」というイメージだけであった。

5ヵ月間でこの「漠然としたイメージ」「リリース可能なアプリ」に仕上げるのは簡単な話ではなかった。⁠仕様作り」に時間をかけている余裕もなかったし、そもそも紙の上で仕様を作ったところで、実際にアプリとして作っていく段階でうまくいかないこともあるし、新しいアイデアもあとから浮かんでくる。

そこで私が選んだのは、その「漠然としたイメージ」を、私なりの解釈を加えながらプロトタイプとして形にしながら仕様を固めていく、という手法だった。私の解釈が間違っていて、せっかく実装した機能をゼロから作り直すことも何度もあったが、それを嫌っていては良い物はできない。

同時に、アーキテクチャの大規模な変更を何度も行った。neu.TutorはCoreData[2]を使っているが、あまりにもアーキテクチャの変更が激しいために、スキーマのマイグレーションコードを書いている余裕がなくなってしまった。そこで、keyvalue型のデータベースを別途用意して、それを介してデータのマイグレーションをするという工夫をして生産性を上げた。

そんな形で、6月の終わり近くまでの時間を「仕様とアーキテクチャを固めるためのコーディング」に費やした。つまりプロジェクト全体の5分の3の時間を使って「neu.Tutorというアプリはどんな機能を持って、どんな価値をユーザに提供するのか」という最も大切な部分を固めたのである。

別の言い方をすれば、⁠仕様を固める」⁠アーキテクチャを決める」という最も難しく、リスクの高い部分に3ヵ月という時間をたっぷりと使い、それと同時に一通りの実装もほぼ終わっているところまで持っていくことにより、⁠開発期間の後半になって仕様が大幅に変更になる」⁠決めた仕様の実装に予想したよりも時間がかかる」などのリスクを最小限にしているのである。

オブジェクト指向と生産性

もちろん、プロジェクトの進め方だけでなく、プログラミングにおいても、常に「生産効率」を考えてコードを書くことが大切だ。

私が最も意識しているのは、やはりオブジェクト指向だ。iOSプログラミングではObjective-Cを使うが、プログラミング言語がたとえCやアセンブラであってもオブジェクト指向は常に意識している。

大切なことは、本連載第4回でも解説したモジュール間のインタフェースを可能な限り簡潔に定義し、依存関係を減らすこと。不必要な情報はできるだけ隠すようにし、モジュール間の結合を疎結合にするのである。

モジュールの再利用も常に意識してプログラムを書く。1つのプロジェクト内で同じモジュールを再利用するケースもあるし、あとあとほかのプロジェクトに流用できる場合もある。neu.Tutor にはFacebook 上のフィードを表示するしくみを持っているが、この部分は将来ほかのプロジェクトにも流用する可能性が高いので、できるだけ独立性を高めて、再利用しやすく設計している。

iOSアプリのようにユーザと直接やりとりをするアプリの場合、MVCModel-View-Controllerを明確に分けることも意識して行っている。iOSアプリだけではないが、だらしなくプログラミングを書いているとコントローラが大きくなりやすいので、意識してモデル化できる部分はModelクラスとしてコントローラから切り離すようにしている。

neu.Tutorの場合だと、Facebookセッションを管理するFacebookSessionクラス、非同期通信を担うHTTPLoaderクラスとGraphAPIHandlerクラス、Facebookから取得した画像をキャッシュするCached ImageManager クラスなどが代表的なModelクラスだ。

ミスを減らすテクニック

ケアレスミスを避けるためのさまざまなテクニックも使う。

上で書いたモデルとコントローラを実装しているときに注意しているのは、データの整合性に対する責任をModelクラスに持たせることにより、コントローラ側のバグのためにデータの整合性が壊れてしまわないようにしておくことである。コントローラはモデルと比べるとどうしても仕様変更やUIの微調整で頻繁に変更を加えることになるので、モデル側の実装を確固たるものにしておくことによりケアレスミスを減らすのだ。

また、バックグラウンドで非同期に処理をするにはスレッドは便利だが、どうしてもミスが生じやすいので極力使わないようにしている。それよりも非同期APIを上手に使い、ほとんどの処理をメインスレッドで行いながらもユーザにはストレスを与えないアーキテクチャのほうが、ミスも少ないしメンテナンスもしやすい。

ただし、デリゲート[3]を使うタイプの非同期API は、API を呼び出すコードとAPI の結果を処理するコードが離れてしまって可読性が落ちるので、Objective-Cのブロックを使って可読性を上げる工夫をしている。上に挙げたHTTPLoader クラスやGraphAPIHandlerクラスはそのために導入したラッパクラスだ。

neu.TutorはARC[4]を使っていない[5]⁠。そのため、オブジェクトのリファレンスカウントには注意が必要だが、次のような「ルール」を作ってケアレスミスを減らしている。

  1. オブジェクトをallocを使って生成する場合には、⁠後のほうでreleaseやautoreleaseせずに)必ずその行でautoreleaseを行う
  2. プロパティとしてリファレンスしているオブジェクトをリリースする際には、⁠releaseメソッドを呼ばずに)必ずself.{propertyname} = nil;でリリースする(releaseメソッドで解放すると、その後もオブジェクトへのリファレンスが可能になってしまうので)

また、コードを書く際には可読性を最も重視している。可読性を増すためだけに、一度変数に代入してから関数のパラメータとして渡す、などのテクニックはよく使う。ほかの人や1年後の自分が見たときに、⁠そのプログラムが何をしているのか」が可能な限り自明になるように注意を払っている。

たとえば、リスト1のようなコードが良い例である。1行で書いてしまうことも可能だが、urlGroupPage という変数に一度代入することにより、あとで読む人に「ここでグループページのURL を生成したうえで、self.webViewにそれをロードするように指示している」ということが明確になる。

リスト1 変数を使い可読性を向上させているコード
NSURL* urlGroupPage = [NSURL URLWithString:[NSString stringWithFormat:…]];
[self.webView loadRequest:[NSURLRequest requestWithURL:urlGroupPage]];

知的生産活動としてのプログラミング

冒頭にも書いたが、プログラミングはクリエイティビティを必要とする知的生産活動であり、工場の組み立て作業などとは大きく違う。そこをちゃんと理解したうえで、いかにリスクを最小にするか、ミスを減らすかを常に意識しながらコードを書くこと、プロジェクトを管理することが大切だ。

特に「作ってみなければ使い勝手の評価ができない」スマートフォン用アプリの場合、⁠仕様を固めるためのプロトタイピングとコーディング」のフェーズが必要不可欠なので、そこに時間と労力を費やすことを惜しんではならない。

おすすめ記事

記事・ニュース一覧