IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Linux  >

わかりやすいコードを作成するための 6 つの方法

自分のコードに身を滅ぼされないようにするための方法

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 初級

Jeff Vogel (spidweb@spiderwebsoftware.com), President, Spiderweb Software

2007年 5月 29日

開発者にとって最も貴重なリソースは時間です。時間の節約、それにフラストレーションの解消を望むなら、管理しやすいコードを作成する方法について、ここで説明する 6 つのヒントを参考にしてください。コメントの作成に費やす 1 分につき、1 時間分の苦しみを取り除けるはずです。

私が習得した簡潔で管理しやすいコードの作成方法は、長年の苦戦の結果です。この 12 年間、私は自作のコンピューター・ゲームを、かつては魅力的に響いたシェアウェアという販売手段を使って、ネット上で販売することで生計を立ててきました。そのため、ゲームの作成では、まったく何もない画面からコードの作成に取り掛かり、売れるものが出来上がる頃には、コードは何万もの行に膨れ上がっています。

こういう状況なので、もし複雑なコードのせいでひどく混乱した状態になったとしても、結局は自分でやったことなのです。悪夢のように複雑に絡まりあった大量のコードのなかから午前 3 時にやっとバグを突き止めた末、「この使い物にならないコードを書いた間抜けは一体どこの誰だ」と悪態をついても、「自分」という答えしか見つかりません。

そんななかで役に立っているのが、私が学んだ有効でまっとうなプログラミング手法です。この記事では、これらの手法のいくつかを取り上げて説明します。有能で経験豊富な正統派のコード作成者の多くは、このような手法は言われなくてもわかっているに違いありません。そのようなコード作成者は、この気楽な記事を読んでも、簡潔なコードを信仰する以前に送っていた過酷な生活を思い出すに過ぎないでしょう。

しかし私自身をはじめ、思いもしない方法や珍しいやり方のプログラミングに出会ったはいいけれど、そのようなプログラミング手法を教え込んでくれる人が誰もいないというコード作成者も多くいるはずです。この記事で紹介するヒントは多くの人にとっては基本的な知識ですが、そうでない人にとっては、誰も教えてくれなかったかけがえのない手法になります。この記事は、めちゃくちゃなコードを作成したくない、そんな人たちにうってつけです。

事例

この記事で説明のために使用するサンプル・プログラムは、Kill Bad Aliens という架空のコンピューター・ゲームです。このゲームでは宇宙船を操縦します。宇宙船は画面の下で水平に移動し、上に向かって弾を発射します。宇宙船の操縦に使うのはキーボードです。


図 1. 架空のコンピューター・ゲーム
Our hypothetical game

このゲームはウェーブと呼ばれる時間単位で行われます。ウェーブの期間中は、画面上部からエイリアンが次々と現れ、自由に動き回って爆弾を落とします。エイリアンが現れる間隔は決まっています。一定の数のエイリアンを殺すと、ウェーブが終了します。

エイリアンを殺すとポイントをもらえます。ウェーブを終了すると、どれだけ短時間で終了したかによってボーナス・ポイントももらえます。

爆弾が当たると宇宙船が爆発し、次の宇宙船が現れます。3 回爆弾に当たるとゲーム・オーバーです。スコアが高ければスゴイ人ということになり、スコアが低ければタダの人です。

それでは、腰を据えて C++ で Kill Bad Aliens の作成に取り掛かってください。定義するのは、宇宙船、弾、敵、そして敵の弾を表す各オブジェクトです。これらのオブジェクトを描画するコードを作成し、オブジェクトを時間の経過とともに移動させるコードを作成します。また、ゲーム・ロジック、エイリアンの AI、そしてユーザーの意思をキーボードから読み取るコードも必要です。

さて、ここで質問です。ゲームが出来上がったときのコードが、理解するにも管理するにも簡単で、全体として滅茶苦茶にならないようにするにはどうすればいいでしょうか。




上に戻る


ヒント 1: 賢い人にならってコメントを付けること

コードにコメントを付けてください。当然のことですが、プロシージャーを作成してコメントを付け損ねた場合、何ヶ月かしてそのプロシージャーを作り直すときに (あり得ることです)、コメントがないと時間がかかります。時間は最も貴重なリソースです。失った時間は取り戻せません。

ただしコメントを付けるということは、他のあらゆることと同じように 1 つのスキルなので、練習すれば上達するものです。いいコメントもあれば、悪いコメントもあるのは仕方ありません。

コメントを付けすぎるのは禁物です。例えば、関数に対するコメントを書いて、そのコメントが将来的にはコードを理解する時間を 10 分節約するとします。それはそれで素晴しいことですが、そのコメントが冗長で書くのに 5 分かかり、後で読むときには 5 分かかるものであったとしたらどうでしょう。節約できる時間は差し引きゼロです。それでは元も子もありません。

コメントが少なすぎるのも同じく禁物です。コードの内容の説明がなく 1、 2 ページにも及ぶとしたら、そのコードは水晶のごとくクリアであることを願います。そうでなければ、この先、時間を無駄にするだけだからです。

また、間の抜けたコメントを書くのも避けなければなりません。初めてのコメント作成では熱が入りすぎて、例えば以下のように書く場合がよくあります。

// Now we increase Number_aliens_on_screen by one. Number_aliens_on_screen
= Number_aliens_on_screen + 1; 

まったく当たり前のことです。わかりきったことにコメントは必要ありません。行ごとにコメントが必要なまでにコードが複雑な場合は、まずは他の方法でコードを簡単にしたほうがよいでしょう。コメントは時間を節約するだけでなく消費もします。つまり、コメントは読むのに時間がかかるということです。その上、コメントを入れることで画面上の実際のコードが長くなってしまうため、モニター上のコメントは簡潔にまとめて一度で調べられるようにしてください。

また、以下のようにすることも絶対に避けてください。

Short get_current_score() { [insert a whole bunch of code here.] return
[some value]; // Now we're done. } 

「We're done?」ですか? わざわざ知らせてくれて感謝しますが、開始の大括弧とその後にだらだらと続く空きスペースがこのコメントにつながるとは思いもしませんでした。もちろん、return 文の前に「Now we return a value」というコメントを入れるなんてことも考えないでください。

それでは、指示してくれる上司がいなかったり、従うべき会社のポリシーがない場合にコードを書くときには、どのようにコメントを付ければいいのでしょうか。私がコードを自分で管理し続けるために行っている方法は、序文を書くことです。自分が作成したことを忘れてしまったプロシージャーに戻ったときに、その内容についての説明を知りたいからです。その機構が何をするものなのかがわかれば、実際のコーディングを理解するのが遥かに簡単になります。それには通常、以下が必要です。

  1. プロシージャー/関数の実行内容についての説明文
  2. 渡されている値の説明
  3. 関数の場合は、何を返すかの説明
  4. プロシージャー/関数の内部でコードを短いタスクに分割するコメント
  5. 処理しにくそうなコードの塊については、その内容についての簡単な説明

つまり、冒頭の説明文、そして途中にはその過程の手掛かりとなる説明文が必要だということです。このようなコメントは簡単に付けられるだけでなく、長い目で見れば、かなりの時間を節約することになります。

架空の Kill Bad Aliens からの一例として、プレイヤーが発射する弾を表すオブジェクトを考えてみてください。弾を上に移動して何かに当たったかどうかを確認するための関数は頻繁に使うことになります。そのためのコードは、例えば以下のようになるはずです。

 // This procedure moves the bullet upwards. It's called //NUM_BULLET_MOVES_PER_SECOND
times per second. It returns TRUE if the //bullet is to be erased (because
it hit a target or the top of the screen) and FALSE //otherwise. Boolean
player_bullet::move_it() { Boolean is_destroyed = FALSE; // Calculate the
bullet's new position. [Small chunk of code.] // See if an enemy is in
the new position. If so, call enemy destruction call and // set is_destroyed
to TRUE [small chunk of code] // See if bullet hits top of screen. If so,
set is_destroyed to TRUE [Small chunk of code.] // Change bullet's position.
[Small chunk of code.] Return is_destroyed; } 

コードが簡潔であれば、このようなコメントを付けるだけで十分です。間抜けな誤りを修正するために、この関数に何度戻るとしても、このコメントがあれば相当な時間が節約されることになります。




上に戻る


ヒント 2: #define をたくさん使うこと。ただしやたらに使うのは禁物です

この仮想のゲームでは、プレイヤーがエイリアンを撃退すると、例えば 10 ポイントを与えるとします。それには良い方法と悪い方法があります。以下は悪いほうの方法です。

 // We shot an alien. Give_player_some_points(10); 

良い方法はこちらです。グローバル・ファイルで、以下のようにします。

 #define POINT_VALUE_FOR_ALIEN 10 

そうすると、ボーナス・ポイントを与える場合には当然、以下のように書くことになります。

 // We shot an alien. Give_player_some_points(POINT_VALUE_FOR_ALIEN); 

大抵のプログラマーは、こうしたことを行うということはある程度わかっているはずですが、きちんと行うには規律が必要です。定数を定義するときは常に、一箇所にまとめて定義することを積極的に検討してください。例えばプレイ・エリアを 800 × 600 ピクセルにするとしたら、必ず以下のコードにします。

 #define PIXEL_WIDTH_OF_PLAY_AREA 800 #define PIXEL_HEIGHT_OF_PLAY_AREA
600 

上記のコードにすれば、後になってゲーム・ウィンドウのサイズを変更しようと思い立った場合 (その可能性は非常に高いです)、一箇所で値を変更できるので時間の節約は 2 倍になります。第一に、コード全体でプレイ・エリアの幅を 800 ピクセルに指定したすべての箇所を検索する必要がなくなり (800 ピクセルにするなんて、一体何を考えていたのでしょう)、そして第二に、決まって見逃してしまう参照によって決まって発生するバグを修正する必要がなくなるというわけです。

Kill Bad Aliens での作業中には、ウェーブを完了するために殺さなければならないエイリアンの数、画面に一度に表示するエイリアンの数、そしてエイリアンを出現させる間隔を決めなければなりません。例えば、すべてのウェーブで同じ数のエイリアンを同じ間隔で出現させようとするなら、以下のようなコードを書くことになります。

 #define NUM_ALIENS_TO_KILL_TO_END_WAVE 20 #define MAX_ALIENS_ON_SCREEN_AT_ONCE
5 #define SECONDS_BETWEEN_NEW_ALIENS_APPEARING 3 

かなり簡潔なコードです。後でウェーブが早く終わりすぎると思ったり、エイリアンを出現させる間隔が短すぎると思った場合、上記の値を調整すれば、あっという間にゲームを調整できます。

ちなみに、このようにゲームの値を設定すると、あっという間に変更できるのはとても楽しく、自分がまるで万能にでもなったように思えるという嬉しいメリットもあります。例えば上記のコードを以下のように変更してみます。

 #define NUM_ALIENS_TO_KILL_TO_END_WAVE 20 #define MAX_ALIENS_ON_SCREEN_AT_ONCE
100 #define SECONDS_BETWEEN_NEW_ALIENS_APPEARING 1 

後は 1 回コンパイルするだけで、画面が面白おかしく変身します。


図 2. すべての定数を変更する前の
Kill Bad Aliens before we crank up all of the constants

図 3. すべての定数を変更した後の Kill Bad Aliens (ゲームとしては良くないかもしれませんが、見た目は面白くなっています)
Kill Bad Aliens after we crank up all of the constants. It may not be a good game now, but it's fun to see.

ところで、上記の値には何のコメントも書いていないことにお気付きですか。これは、それぞれの値の意味は変数名を見れば明らかだからです。この話の流れに続くのが次のヒントです。




上に戻る


ヒント 3: わかりにくい変数名を使わないこと

全体的な目標は至って単純です。つまり、内容をまるで知らない誰かが読んでも、できる限り短時間で理解できるコードを作成することです。

この目標を達成するための重要な方法のひとつは、変数やプロシージャーなどに特徴を説明した適切な名前を付けることです。変数名をひと目見るだけでその内容がわかれば、 incremeter_side_blerfm が何を表しているのかの手掛かりを見つけるために、プログラム全体を検索する 5 分の時間を節約することができます。

ただし、ほどよいバランスを取ってください。それが何のことだか理解できるような長さと簡潔さを持った名前を付けることは肝心ですが、名前がだらだら長すぎるとコードが読みにくくなってしまいます。

私は実際には、前のセクションで使ったような長い名前を定数に付けることはしません。長い名前を使用したのは、読者が前後関係なしでその意味を完全に理解できるようにしたかったからです。プログラム自体では以下のような名前は定義しません。

 #define MAX_ALIENS_ON_SCREEN_AT_ONCE 5 

代わりに、ほぼ間違いなく以下のように書くでしょう。

 #define MAX_NUM_ALIENS 5 

短い名前で意味が混同しても、すぐに解決するはずです。さらに、名前を短くすればコードが遥かに読みやすくなります。

ここで、画面全体ですべてのエイリアンを動き回らせるために頻繁に読み出すコードのスニペットについて考えてみましょう。私だったら、以下のようなコードを書きます。

 // move all the aliens for (short i = 0; I < MAX_NUM_ALIENS; i++) if
(aliens[i].exists()) // this alien currently exist? aliens[i].move_it();

すべてのエイリアンの配列を単に aliens と名付けている点に注目してください。完璧な名前です。私の意図を正確に説明していると同時に、入力も苦にならないくらいの短い名前です。これなら 1000 回入力しても気が狂わずに済みます。おそらくやたらと使うことになるこの配列を例えば all_aliens_currently_on_screen という名前にしたら、コードは相当な長さになり、したがって簡潔さも失われることになります。

同様に、ループ変数も余分なコンマを使わずにただ i と呼んでいます。自明の変数名といったことを意識するのが初めてだと、「counter」などのような名前にしがちですが、その必要はありません。変数に名前を付ける上でのポイントは、読む人がひと目で変数の内容を理解できるようにするということです。「i」、「j」のような名前であれば、誰でもループに使われるものだということがわかるので説明は一切要りません。

当然、変数のネーミングに関してもっと慎重になることも可能です。一例として、ハンガリアン記法と呼ばれる命名方法があります。この方法には多くの特色がありますが、基本的な考えとしては、変数名の先頭にその型を表すタグを付けるということです (例えば、符号なし long 型変数は ul で始まるなど)。多少曖昧な言い方ですが、必要な知識として知っておいてください。簡潔にするための作業に時間がかかり過ぎてしまうこともありますが、ある程度の努力は必要です。




上に戻る


ヒント 4: エラー・チェックを行うこと。誰にだって間違いはあります

そこそこの大きさのプログラムであれば、関数とプロシージャーは相当な数になるはずです。確かに厄介なことですが、その 1 つひとつに多少のエラー・チェックが必要です。

プロシージャー/関数を作成するときには、常に「悪意のある非常識な誰かがあらゆるタイプの変な値を渡したとしたら、この軟弱なコードはどんな自衛手段を使ってコンピューターの暴走を防げるのだろうか」と考えてください。そのうえで、奇妙なデータをチェックして、そのデータから防護するためのコードを書きます。

Kill Bad Aliens を例に挙げると、このいかしたスペース・ゲームの主たる目標はエイリアンを殺してポイントを稼ぐことなので、スコアを変更するためのプロシージャーが必要です。さらに、ポイントを追加するときにはスコアをキラキラ輝かせるルーチンを呼び出すことにします。最初に作成したコードは以下のとおりです。

 Void change_score(short num_points) { score += num_points; make_sparkles_on_score();
} 

今のところ、いい感じです。ここで、上手くいかなくなる可能性を考えてみてください。

まず明らかなケースとして、 num_points が負の値だったらどうなるでしょう。プレイヤーのスコアを下げられるようにする必要があるでしょうか。そういった可能性もあるにはあるでしょうが、前述したゲームの説明では、ポイントの減点については何も触れていません。それに加え、ゲームは楽しくなければなりません。ポイントを失うのは決して楽しいことではないので、ポイントが負の値の場合はエラーとしてキャッチすることにします。

上記は単純なケースですが、それよりも微妙な問題があります (私のゲームで常に対処している問題です)。それは、 num_points が 0 の場合です。

これは非常にありそうな状況です。ウェーブの終了時には、プレイヤーがウェーブを完了した早さに応じてボーナス・ポイントを与えるという条件を思い出してください。プレイヤーが極端に時間をかけた場合は、ボーナス・ポイントを 0 にすることにしたらどうでしょう。これで、明け方の午前 3 時になってやっと、 change_score を呼び出して 0 を渡すことが可能になります。

ここで問題になるのは、表示された数値が変わらないときには、見映えのいい色でスコアボードを点滅させないようにする場合です。そこで、以下のようなコードにしてみます。

 Void change_score(short num_points) { if (num_points < 0) { // maybe
some error message return; } score += num_points; if (num_points > 0)
make_sparkles_on_score(); } 

どうですか? 断然良くなりましたよね。

これは極めて単純な関数だということに注意してください。若者が好むような手の込んだ流行のポインターは使っていません。配列やポインターを渡すとしたら、確実にエラーや不良データがないかどうか、なおさら用心する必要があります。

このようにするメリットは、プログラムが暴走するのを防ぐことだけに留まりません。優れたエラー・チェックはデバッグの時間も短縮します。例えば、作成しているデータが配列の境界を超えることがわかっていて、それがコードのどこで発生するかを調べているとします。プロシージャーであらゆるエラー・チェックが適切に行われていることがわかれば、誤りを見つけるために詳細を調べる時間を省けます。

この方法は相当な時間を節約できるだけでなく、繰り返しにも耐えられます。時間は最も貴重なリソースであることをお忘れなく。




上に戻る


ヒント 5: 「Premature optimization is the root of all evil (早まった最適化は諸悪の根源である)」 - Donald Knuth

上記の格言は、私が作ったものではありません。Wikipedia にも載っているくらいなので、核心をついているはずです。

他の人を悩ませようとしているのでない限り、コードを作成するときにまず目指すのは明瞭さです。簡潔なコードは作成する時間が少なくて済みます。また、後から戻って理解するのも簡単で、デバッグにも時間がかかりません。

最適化は明瞭さの敵です。それでも、時には最適化の必要があります。それはゲームではなおさらのことですが、極めて重要な点は、何を最適化する必要があるのかは必ずと言っていいほど、実際に機能しているコードを使ってプロファイラーでテストするまでわからないということです (プロファイラーとは、異なる呼び出しでプログラムが費やす時間を突き止めるプログラムのことです。素晴しいプログラムなので、確認してみてください)。

ゲームを最適化するたびに、私は常々驚かされます。最も心配していたコードはいつだって問題なく、考えもしていなかったコードが遅かったりします。何が早くて何が遅いかまったく見当がつかなかったために、実際のデータを取得する前に費やした最適化の時間がまるごと無駄になった経験もあります。実際、時間の無駄よりも厄介なのは、最適化によってコードが混乱してしまうことです。

最適化は従うのが困難なルールです (そもそも簡単に守れるものだったらルールにはなりません)。有能なプログラマーにとっては、体裁の悪いコードはその処理速度が向上したとしても気に障るものです。

でも喜べることもあります。 あれこれもっと時間を費やすべきだと説教した後にあえて、最適化に関しては怠けても OK だと言っているのです。これは私がめったやたらに言うことではありません。

まずは簡潔で、機能するコードを書いてください。最適化でコードを見苦しくする時間は後でいくらだってあります。正しいコードを作成できたと確信するまで、最適化は行わないでください。

辛いことをすると言えば、最後にもう 1 つアドバイスがあります。




上に戻る


ヒント 6: あまりにも賢くなりすぎないこと

IOCCC という言葉を聞いたことがありますか。これは「International Obfuscated C Code Contest」の略で、つまり解読するのが難しい C コードのコンテストのことです。C と C++ にはそれぞれかなりのメリットがありますが、いずれも悪夢のように複雑なコードを作成するのにも一役買っています。そんな狂気じみた複雑さを競い合うことで、逆に簡潔なコードの価値を見せ付けるこのコンテストは実に見ものです。

プログラミング言語の豊富な知識が羞恥心の欠如と結びつくと、どれほどの害をもたらすことができるのかを見るだけでも、このコンテストを訪れる価値はあります。十分な知識がありさえすれば、10 行のコードを 1 行に詰め込むことだってできます。ですがその代償として、コードに紛れ込ませてしまったバグを素早く修正できなくなるだけです。

ここでの教訓は、コードを作成する上で複雑な優先ルールに関する詳しい知識が必要だったり、あるいは自分がしていることを把握するために何らかの本を読み返してみようと思い立たせるような場合は、賢くなりすぎているということです。

コードの複雑さに対する我慢の限度は人それぞれです。私自身はというと、杓子定規ののろまな安全運転のようにプログラムを書いています。私に関する限り、C コードで i++ と ++i との違いを理解しなければならないような場合、その複雑さは手に余るということになります。

意気地なしと思ってくれても結構です。それはその通りですが、コードを理解しようと奮闘する時間は遥かに少なくて済んでいます。




上に戻る


まとめ

この記事を読んで、「まったくの時間の無駄だった。こんな当たり前の内容は誰でも知っていることじゃないか。どうしてこんな記事をわざわざ書いたりしたのだろう」という感想を持ったとしたら、私の願うところです。つまり、あなたはもうすでに賢いということになります。

しかし、誰にとってもこの内容のすべてが明らかだとは思わないでください。知らないという人も実際にいるからです。だからこそ質の悪いコードが始終書かれているわけですが、そのまま放っておくこともありません。

莫大な量のコードに反感を持っていて、そのコードに身を滅ばされないようにしようとしているのなら、この記事のヒントを役立ててください。コードを単純に簡潔に保つことで、かなりの時間の節約になり、イライラで悲鳴を上げなくても済むようになります。



参考文献

学ぶために

製品や技術を入手するために
  • SEK for Linux を注文してください。 この 2 枚組 DVD セットには、Linux 対応の DB2 ®, Lotus ®, Rational ®, Tivoli ®, and WebSphere ®の最新 IBM トライアル・ソフトウェアが収録されています。

  • developerWorks から直接ダウンロードできる IBM トライアル・ソフトウェア を使用して、Linux で次の開発プロジェクトを構築してください。


議論するために


著者について

Jeff Vogel は、1994 年から Spiderweb Software を経営しています。賞を獲得した Exile、Avernum、Geneforge シリーズをはじめ、Windows および Macintosh 対応の多数のロール・プレイング・ゲームを作成しています。また、「The Poo Bomb: True Tales of Parental Terror」などのユーモア溢れた本の著者でもあります。現在、シアトル在住です。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


DB2, Lotus, Rational, Tivoli, and WebSphere are trademarks of IBM Corporation in the United States, other countries, or both. Linux is a trademark of Linus Torvalds in the United States, other countries, or both. Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both. 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ