はじめに
この連載は、「 良いコードの書き方」を知ることを目的としています。みなさんの周りにも「良いコード」をものすごいスピードで書き上げて、開発の原動力となっているプログラマがいませんか? 本連載ではそのような「できるプログラマ」たちが「日常的に意識していること」に注目して、彼らの中では常識だけど一般的には気づかれていないような内容を、プログラミング初心者にもわかりやすい言葉で解説していきます。プログラミング言語はJavaを中心に説明しますが、RubyやC#、JavaScriptなどでの例も織り交ぜていく予定です。
第1回の今回はウォーミングアップとして、良いコードを書くために必要な日々の習慣を紹介します。
「良いコード」とは?
「良いコード」とは何でしょう? 一言で良いコードといっても、組織やプロジェクト、プログラマか管理者かなど状況が異なると定義も変わってきます。本連載では「良いコード」を次のように定義します。
正確に動作すること
確実に動作する、信頼できるコードのことです。
素早く効率的に動作すること
似たような実装がいくつかあるときに、あきらかに効率の悪いものを選択する必要はありません。「 良いコード」は適切なパフォーマンスで動作します。
防御的でバグを産みだしにくいこと
「防御的プログラミング」という言葉があります。これは「正常な値が来るはず」という決めつけをせずに、不正な値が来ても被害を受けないように防御的にプログラミングを行うことです。「 良いコード」は防御的で、不測のバグを生み出しにくい作りになっています。
メンテナンスが行いやすいこと
コードは、私たちが想像するよりも長く利用されることが多いです。メンテナンス性が高いことも「良いコード」には大切です。
他人が見ても理解可能であること
将来の自分は記憶力において他人と同然です。つまり、他人が見て理解できるコードであれば、将来の自分が見ても理解できる「良いコード」であると言えます。
無駄な部分がないこと
無駄がないコードは理解するのも修正するのも簡単で時間がかからないため、「 良いコード」と言えます。
「良いコード」を書けると何がうれしいの?
では、私たち開発者が「良いコード」を書けるようになると、具体的にどんなメリットがあるのでしょうか?
プロジェクトを強力に推し進める
「良いコード」はプロジェクトを推し進めて、成功へと導くための基本的な要素となります。
達人たちは、シンプルで、メンテナンス性が高く、安定したコードを、ものすごいスピードで書き上げていきます。場合によっては、単純作業を自作のDSL[1] に置き換えたり、テストが難しいようなレガシーなコードをテスト可能で検証できるコードに変更することで、品質や生産性を数百倍に高めることさえあります(おおげさではなく、本当に数百倍の場合もあるのです!) 。
これはプロジェクトの成功にとって大きなアドバンテージと言えます。もちろん、「 良いコード」があれば必ずプロジェクトが成功するわけではありません。実際は、開発プロセスやマネージメント、コミュニケーションなどほかの要素により左右されることのほうが多いのですが、それを差し引いたとしても、「 良いコード」の持つ力は大きいと言えます。
[1] Domain Specific Language(ドメイン特化言語)のこと。ある特定の問題に対応するための言語のことを指します。詳しくはMartin FowlerのBlikiの記事「ドメイン特化言語 」をご覧ください。
プログラマとしての評価が高まる
「良いコードを書くプログラマ」は、総じてプログラマとして信頼され、評価されます。
「プログラマとしての評価」が組織としての実際の評価や収入に結びつくかどうかは、所属する組織の評価制度やプログラム以外の仕事っぷりも含めて決まるのが現実です。でも、「 良いコードが書けること」がマイナス評価につながることはないでしょう。
仕事に満足感や自信が持てるようになる
もう二度と触りたくない、メンテナンスが不可能なコードを書いたことはありませんか? そのような低いクオリティの仕事をしてしまったときは、仕事に対する満足感を得ることは難しいでしょう。
逆に、自分の意志で適切に良いコードを書き、品質の高い安定したソフトウェアを開発したときは、満足感も高く、自信を持って仕事に取り組めたはずです。
長いプログラマ人生を考えると、「 良いコードが書ける」レベルを目指すことは合理的なことです。
対象読者
本連載の対象読者は、大きく次の3グループを想定しています。それぞれの代表者にコメントしてもらいましょう。
良い仕事をしたい普通のプログラマ
特段に「達人プログラマ」を目指しているわけではないが、良い仕事と成果を出したいと考えているプログラマの人は、本連載により普段知ることのない新しい概念を知ることができたり、興味関心の対象を広げることができたりするでしょう。
達人プログラマを目指す初級~中級のプログラマ
向上心が高く達人を目指すプログラマにとって、本連載が良いドキュメントとしての役割を果たすはずです。
達人プログラマ
すでに「達人」な人には、「 この連載を新人などに見せれば教育に使えるな」という観点で見ていただけるとよいでしょう。
5つの基本的な習慣――読む、書く、道具を磨く、知る、聞く
良いコードは一日にしてならず。
ここからは、良いコードを書くための5つの基本的な習慣を紹介していきます。どれも大切な習慣なのですが、特に重要だと思われる順に紹介します(図1 ) 。
図1 良いコードを書くための5つの習慣
習慣その1 読む――コードを読んで読んで、読みまくれ!
音楽や絵画、建築などの世界で、自分だけの発想で作品を完成させる芸術家はほとんどいません。ほかの先人たちの作品を見て、影響を受けたり、いいところを盗んだりして自分の中で咀嚼することで、オリジナルの作品を生み出してきました。
プログラミングも同じです。「 良いコード」が書けるようになるには、「 良いコード」「 悪いコード」を含めて、他人が書いたコードを普段から意識して読むことが大切です。幸いにも現在ではオープンソースソフトウェア(OSS)が多数存在しますので、「 他人の書いたコード」をいつでも気軽に読むことができます。ちなみに、コードを読むことを「コードリーディング」と呼びます。
コードを読んでみよう
●リスト1 FileSystemUtils.java
public class FileSystemUtils {
private static final int INIT_PROBLEM = -1;
private static final int OTHER = 0;
private static final int WINDOWS = 1;
private static final int UNIX = 2;
private static final int POSIX_UNIX = 3;
private static final int OS;
static {
int os = OTHER;
try {
String osName = System.getProperty("os.name");
if (osName == null) {
throw new IOException("os.name not found");
}
osName = osName.toLowerCase();
// match
if (osName.indexOf("windows") != -1) {
os = WINDOWS;
} else if (osName.indexOf("linux") != -1 ||
osName.indexOf("sun os") != -1 ||
osName.indexOf("sunos") != -1 ||
osName.indexOf("solaris") != -1 ||
osName.indexOf("mpe/ix") != -1 ||
osName.indexOf("freebsd") != -1 ||
osName.indexOf("irix") != -1 ||
osName.indexOf("digital unix") != -1 ||
osName.indexOf("unix") != -1 ||
osName.indexOf("mac os x") != -1) {
os = UNIX;
} else if (osName.indexOf("hp-ux") != -1 ||
osName.indexOf("aix") != -1) {
os = POSIX_UNIX;
} else {
os = OTHER;
}
} catch (Exception ex) {
os = INIT_PROBLEM;
}
OS = os;
}
...
long freeSpaceOS(String path, int os, boolean kb)
throws IOException {
if (path == null) {
throw new IllegalArgumentException(
"Path must not be empty");
}
switch (os) {
case WINDOWS:
return (kb ? freeSpaceWindows(path) / 1024 :
freeSpaceWindows(path));
case UNIX:
return freeSpaceUnix(path, kb, false);
case POSIX_UNIX:
return freeSpaceUnix(path, kb, true);
case OTHER:
throw new IllegalStateException(
"Unsupported operating system");
default:
throw new IllegalStateException(
"Exception caught when determining operating system");
}
}
たとえば、リスト1 はApache Commons IOライブラリ のソースコードです。このコードを一緒に軽く読んでみましょう。
コードリーディングする個所はFileSystemUtilsクラスです。このクラスには、OSごとのファイルシステムの違いを吸収してディスクの空き容量を取得するメソッドがあります。
まず、クラスのstaticイニシャライザでOSの判定を行っています([ 1] ) 。この部分から、OSの判定処理は、このクラスのロード時に1回だけ実行されることが読み取れます。
次に、システムプロパティから取得したOS名を使って([ 2] ) 、Windows、UNIX、Posix UNIX、そのほか(OTHER) 、初期化エラー(INIT_PROBLEM)をそれぞれ判定しています([ 4] ) 。String#indexOfを利用しているので、OS名のどこかに「windows」という文字列が含まれていれば「Windows」がセットされます。ちなみに私のMacBookでSystem.getProperty("os.name")
を実行すると、「 Mac OS X」が返ってきました。String#toLowerCase([ 3] )で大文字・小文字の区別なく判定が行われているので、無事Mac OS XはUNIXとして判定されました。
次にfreeSpaceOSメソッドです([ 5] ) 。これはファイルシステムの空き容量を取得するメソッドです。先ほどのOS種別を使い、switch文で分岐することで、可読性の高いコードになっています。例外処理も参考になります。pathがnullの場合IllegalArgumentExceptionが投げられるので、防御的なプログラミングがきちんと行われています。初期化時にファイルシステムがそのほか(OTHER)や初期化エラー(INIT_PROBLEM)の場合は、適切なメッセージを持ったIllegalStateExceptionが投げられていることもわかります。
ほんの60行程度のコードリーディングでしたが、これだけでも「判定処理で考慮すべき点(判定のタイミング、判定漏れがないように大文字・小文字を意識するなど) 」 「 switch文による可読性の高いコード」「 例外処理の基本的な適用例」などを知ることができました。自分がまだ知らなかったり、あいまいになっているプログラム上の概念やテクニックを、少ないコストで学ぶことができるのがコードリーディングの良いところです。
コードリーディングの良さはわかってもコードを読む方法を知らないと始まりません。「 どうやってコードを読んだらいいんですか?」という意見もときどき聞きます。ここでは、コードのダウンロードから、コードを読むまでの流れを紹介します。
1.対象のソースコードをダウンロード(チェックアウト)する
まずはソースコードを入手しないと始まりません。ソースコードの入手には、ZIPファイルなどに固められた配布物をダウンロードする方法と、ソースコードをSubversionなどのリポジトリからチェックアウトする方法があります。どちらでも問題はありませんが、以下の理由からリポジトリからのチェックアウトをお勧めします。
ビルドファイルなど、ソースコード以外の成果物が入手できる
各種スクリプトやMaven2のビルドファイル、開発者向けのドキュメントなど、開発に必要なものがすべて手に入る。これらは配布物には含まれていない場合がときどきある
コードの変更履歴やコミットログを確認できる
ソースを読みながら気になった個所のコードの変更履歴を、過去にさかのぼってすべて確認できる。コミットログには変更の理由や意図が書かれていることもあるので、コードを理解するうえで参考になる
ほとんどのOSSサイトがSubversionなどのリポジトリを公開しています。先ほどのcommons-ioであれば、トップページのメニューから「SVN Repository」を選び、「 Anonymous access」( 匿名アクセス)でSubversionリポジトリのURLを確認できます。URLがわかれば、次のコマンドでチェックアウトできます[2] 。
$ svn co http://svn.apache.org/repos/asf/commons/proper/io/trunk
上記のコマンドではtrunk(開発の本線)がチェックアウトされます。最新版は不安定な場合もあるので、tags(タグ)配下の「リリース版」のコードをチェックアウトしたほうがよい場合も多いでしょう。
次のコマンドで、どのようなバージョンがtags配下にあるかを確認できます。
$ svn ls http://svn.apache.org/repos/asf/commons/proper/io/tags
IO_1_0/
(中略)
commons-io-1.3.2/
commons-io-1.4/
各バージョンのURLがこれでわかりました。最新のリリース版「commons-io-1.4」をチェックアウトしてみましょう。
$ svn co http://svn.apache.org/repos/asf/commons/proper/io/tags/commons-io-1.4
[2] Subversionのコマンドライン版をインストールしておく必要があります。Eclipseでチェックアウトしてもかまいません。Subversionの基本的な内容については本誌Vol.39の特集1「構成管理実践入門」をご覧ください。
2.ソースコードを読む
ソースを入手したら、早速コードを読んでみましょう。効率的にコードを読む方法として、ここでは3つの方法を紹介します。
検索コマンドを使う
読みたい場所がわかっていれば、エディタで開くだけでOKです。
わかっていない場合は、grepやエディタの検索機能を使って関係のある個所を探して閲覧します。たとえば、先ほどチェックアウトしたcommons-ioのコードから「freeSpace」を含むJavaソースファイルを探すときは、次のコマンドを実行します[3] 。
$ cd commons-io-1.4
$ find . -name '*.java' | xargs grep -l "freeSpace"
./src/java/org/apache/commons/io/FileSystemUtils.java
./src/test/org/apache/commons/io/FileSystemUtilsTestCase.java
Eclipseなど統合開発環境を使う
もう少し効率良くコードを読むなら、Eclipseなどの統合開発環境(IDE)にプロジェクトをインポートしてしまう方法があります。メソッドの呼び出し先、呼び出し元などを自由自在に行き来きできたり、継承関係やインタフェースの実装先などを検索できたりと、IDEは効率的な「コードブラウンジング」に最適です。
Eclipseへのインポートは、Eclipseのプロジェクト設定ファイル(.project)とクラスパスファイル(.classpath)があれば、メニューの[ファイル] →[ インポート]から簡単にできます。
ただ、先のcommns-io-1.4のコードには、Eclipseのプロジェクト設定ファイルはありませんでした。OSSではそれぞれの開発者の開発環境が異なることが多いため、特定の開発環境に依存するファイルはコミットしないルールになっていることが多いようです。
その代わり、commns-io-1.4にはMaven2のプロジェクト定義ファイル(pom.xml)がありました。pom.xmlがあればこっちのものです。Maven2のeclipse:eclipseコマンドを使えば、pom.xmlの情報をもとにEclipseのプロジェクト設定ファイル、クラスパスファイルを生成できます[4] 。
$ mvn eclipse:eclipse
依存するライブラリも一緒にダウンロードされ、Maven2のローカルリポジトリに保存されます。
なお、NetBeansであれば、Mevenide2-Netbeans というNetBeansのプラグインを使ってMaven2プロジェクトを開くことができます。
Emacsやviでタグジャンプを使う
Emacsやviでコードを読むときは、インデックス作成ツール(Emacsではetags、viではctags)でソースコードのインデックスを作成して、関数間を移動できる「タグジャンプ機能」を使うと便利です。etags/ctagsは多くのプログラミング言語に対応していますので、言語を問わず、同じやり方でコードを読むことができます[5] 。
ここでは、Emacs用のetagsを使ってみましょう。先ほどの、commons-io-1.4のディレクトリで、以下のetagsコマンドを使い、関数のインデックスを作成します。
$ etags `find . -name "*.java"`
これで、「 TAGS」というインデックス用のディレクトリが作成されました。
次に、Emacsを起動して、「 M( メタキー ) +x visit-tag-table
」と入力して、インデックスディレクトリ(TAGS)の存在するcommons-io-1.4ディレクトリを選択します。
その後、タグジャンプコマンド「M+.
」を入力すると、ミニバッファ上に「Find tag:」と表示されるので、「 FileSystemUtils.freeSpaceOS
」と入力します。入力途中で[Tab]キーを押すと、クラス名、メソッド名が補完されます。[ Enter]キーを押すと、ソースコードの関数定義場所に移動することができます。ソースコードの関数名の個所で「M+.
」を押すとその関数名がデフォルトで選択されているので、素早く移動できます。「 M+*
」で元の場所に戻ります。
[4] Maven2がセットアップ済みでmvnコマンドが利用可能できる必要があります。Maven2の基本的な内容については、しつこいようですが本誌Vol.39の特集1「構成管理実践入門」をご覧ください。余談ですが、JavaのOSSのプロダクトはほとんどがMaven2に移行しています。まだMaven2を使ったことない人がいたら、ぜひ特集記事を活用してMaven2をマスターすることをお勧めします。
3.ソースコードを実行する
ソースコードを読むだけでなく、実行してみる、あるいは一部を書き換えて実行してみることで、さらにコードの理解が深まります。
単体テストを実行する
単体テストは宝の山です。テストは実行可能なので、実行のためのプログラムを書かなくてもすぐに実行できます。テストコードには外からのライブラリの振る舞い(仕様)が記述されていますので、ライブラリの動きを把握するうえでも効果的です。
コードを修正して実行する
コードの特定の個所にログを入れたり、一部を改変して実行することで、コードの実行の順番や変数の値などの挙動を確認できます。場合によってはブレークポイントを設定して、デバッグモードで確認してもよいでしょう。これらは単体テストからの実行で問題ありません。
必要な部分的なコードをコピーして使ってみる
部分的なコードをコピーして小さな実験をすることで、ミニマムの動きを確認できます。
Column ソースコード検索エンジンでコードの海を泳ぐ
最近は、コード専門の検索サイトが増えてきました。クラス名やメソッド名を考えるときに似たようなコードが存在するか確認したり、特定のクラスの利用法を探したりすることができます。気軽に大量のソースコードから検索して読むことができるので便利です。
Google Code Search
Googleが提供するソースコード検索サービスです。正規表現による絞り込み機能、高速な検索速度、登録されているソースコードが多い点が特徴です。
インターネット上に公開されているSubversionリポジトリやアーカイブファイルなどが機械的にクロールされています。
Koders
検索結果の詳細にメソッドの一覧が表示されたり、コード上からメソッド名などをクリックすることで、再検索ができたりと、ある程度ソースコードのセマンティックを含んだ結果を表示してくれる点が特徴です。