大体において、プログラミングはコンピュータによって問題を解決する科学です。問題はしばしば困難であるため、解決策、およびそれらの解決策を実装するプログラムも同様に困難になる可能性があります。これらのソリューションをより簡単に開発できるようにするには、その複雑さのレベルを管理可能な規模にまで削減する方法論と規律を採用する必要があります。
プログラミングの初期の頃は、科学としてのコンピューティングの概念は、多かれ少なかれ希望的思考の実験でした。当時のプログラミングについては誰も知っていませんでしたが、それを従来の意味でのエンジニアリング分野とは考えていませんでした。しかし、プログラミングが成熟するにつれて、そのような規律が出現し始めました。その分野の礎は、プログラミングは、プログラマーが協力しなければならない社会的環境で行われるという理解です。あなたが産業に入るならば、あなたはほぼ確実に大きなプログラムを開発するために働いている多くのプログラマーのうちの1人になるでしょう。そのプログラムは、さらに、ほぼ確実に実行され、本来の用途を超えた保守を必要とします。誰かがプログラムにいくつかの新機能を含めるか、または何らかの方法で動作させることを望みます。それが起こるとき、プログラマーの新しいチームは参加してプログラムに必要な変更を加える必要があります。プログラムがほとんどまたはまったく共通性のない個別のスタイルで書かれている場合、全員が生産的に協力することは非常に困難です。
この問題に対処するために、プログラマーは総称される一連のプログラミング方法論を開発し始めました。 ソフトウェア工学 。優れたソフトウェアエンジニアリングスキルを使用すると、他のプログラマーが自分のプログラムを読んで理解しやすくなるだけでなく、そもそもそれらのプログラムを簡単に書くことも容易になります。ソフトウェア工学から得られる最も重要な方法論的進歩の1つは、 トップダウン設計 または 段階的な洗練 それは全体として問題から始めることによって問題を解決することから成ります。問題全体を細かく分割してから各部分を解決し、必要に応じてそれらをさらに細かくします。このトップダウン戦略は、以下によって補完されます。 反復テスト 先に進む前に、ソリューションの小さい部分が機能していることを確認します。
段階的洗練の概念を説明するために、新しい問題を解決するようにKarelに教えましょう。カレルが今、こんな感じの世界に住んでいると想像してみてください。
それぞれの柱の上には、高さが不明なbeeper秒の塔がありますが、いくつかの柱(サンプル世界の7番目、9番目など)は空の場合があります。 Karelの仕事は、これらの塔のそれぞれにあるbeeperをすべて集め、それらを1列目の最東端の角に戻してから、開始位置に戻すことです。したがって、上記の例でKarelが作業を終了すると、現在タワーにある25 beeperのすべてが、次のように9列1行目の角に積み重ねられます。
重要なのは、あなたはカレルの初期と仮定することができます始まるその袋に0 beeper秒で。拾ったそれぞれのbeeperはそのバッグに追加されます。 beeperコーナーに置くとき、カレルは beepersInBag() テスト。
この問題を解決するための秘訣は、正しい方法でプログラムを分解することです。それでも、あなたが行っている通りにテストすることは可能です。このタスクは、これまで見てきた他のタスクよりも複雑です。そのため、適切なサブ問題を選択することが、成功する解決策を得るために重要になります。
段階的改良の重要な考え方は、プログラムの設計を上から開始する必要があるということです。これは、概念的に最も高く、最も抽象的なプログラムのレベルを指します。このレベルでは、 beeperタワー問題は明らかに3つの独立したフェーズに分けられます。まず、Karelはbeeperをすべて集めなければなりbeeperん。第二に、カレルはそれらを最後の交差点に預けなければなりません。第三に、カレルはホームポジションに戻らなければなりません。この問題の概念的な分解は、このプログラムのrunメソッドが以下の構造を持つことを示唆しています。
public void run() {
beeperをすべて収集();
beeperすべてドロップ();
帰宅();
}
このレベルでは、問題は理解しやすいです。もちろん、まだ書いていないメソッドの形で残っている詳細がいくつかあります。それでも、分解の各レベルを調べて、これから作成しようとしているメソッドがサブ問題を正しく解決すると考えている限り、問題全体に対する解決策が得られることを確信してください。 。
今、あなたは、全体として、プログラムの構造が定義されていること、それはまでの時間であるmoveすべて収集から成り最初の部分問題へのbeeper sが。この作業自体は、前の章の単純な問題よりも複雑です。 beeperすべてbeeperということは、最後のコーナーに着くまで、すべてのタワーでbeeperを拾う必要があるということです。各タワーに対して操作を繰り返す必要があるという事実は、ここでwhileループが必要であることを示唆しています。 whileループは以下のプロセスを繰り返します。 一つの塔を集める そして動いています。
あぶない: せずにプログラム全体を書いてみるのは危険です。 テスト あなたが行くようにそれは。あなたがミスをした場合、それは間違いを見つけるのは難しいでしょう。私たちは一つの塔を集めるプロセスを繰り返すつもりであることを知っています。書きましょう テスト 我々が置く前に単一の塔を集める 一つの塔を集める forループで処理します。したがって一時的beeperをすべてbeeperの次の定義から始めることができます。
private void beeperをすべて収集() {
/* テスト目的での一時的な実装 */
一つの塔を集める();
move();
}
指針として、複雑なループがある場合は、体あなたがループ全体を書く前に、ループの。
ひとつの塔をbeeperと呼ばれると、カレルはbeeper秒の塔のbeeper立っているか、空いている角に立っています。前者の場合は、タワーでbeeperを収集する必要があります。後者では、単純にmoveにすることができます。この状況はif文のアプリケーションのように聞こえます。その場合は、次のように書きます。
if(beepersPresent()){
実際の塔を集める();
}
そのような文をコードに追加する前に、このテストを行う必要があるかどうかを検討する必要があります。多くの場合、プログラムは、最初は特殊であると思われるケースを、より一般的な状況とまったく同じ方法で扱うことができることを観察することによって、はるかに単純にすることができます。あなたがそこのタワーであると判断した場合、現在の問題では、何が起こるbeeper sがすべての道ではなく、これらの塔の一部がゼロであることをbeeperの高いですか?この洞察を利用すると、特定の通りに塔があるかどうかをテストする必要がなくなるため、プログラムが単純化されます。
一つの塔をまとめる方法は、まだ十分に複雑であるため、追加のレベルの分解が適切です。タワーでbeeperをすべて収集するには、Karelは次の手順を実行する必要があります。
もう一度、このアウトラインは一つの塔をまとめる方法のモデルを提供します。
private void 一つの塔を集める(){
turnLeft();
beeperの行を収集();
turnAround();
壁にMove();
turnLeft();
}
一つの塔をturnLeftの最初と最後のturnLeftコマンドは、どちらもこのプログラムの正確さにとって極めて重要です。一つの塔をまとめると呼ばれると、カレルはいつも東を向いて一列目のどこかにいます。操作が完了すると、プログラムは全体として正しく動作します。これは、Karelが再び同じコーナーで東を向いている場合に限ります。メソッドが呼び出される前に真にならなければならない条件は、次のように呼ばれます。 前提条件 ;メソッドが終了した後に適用する必要がある条件は、 事後条件 。
メソッドを定義するとき、事前条件と事後条件が何であるかを正確に書き留めておけば、問題がはるかに少なくなります。いったんそうしたら、あなたはあなたが書くコードが必ず前提条件が最初に満足されていると仮定して、事後条件が満足されたままであることを確認する必要があります。たとえば、カレルが東を向いて1行目にいるときにひとつの塔を集めるとしたらどうなるかを考えてみましょう。最初のturnLeftコマンドはKarelを北に向けたままにします。これは、Karelがタワーを表すbeeperの列と正しく揃っていることを意味します。 beeperの行を収集方法 - これはまだ書かれていませんが、それでもなおあなたが概念的に理解するタスクを実行します - をmoveずに単純にmove秒です。したがって、 beeperの行を収集への呼び出しの終わりに、Karelはまだ北を向いています。したがって、 turnAround呼び出しでは、カレルは南向きになります。 beeperの行を収集のように、壁にMoveメソッドはターンを必要としませんが、代わりに境界壁に当たるまでmove秒です。 Karelは南向きなので、この境界壁は画面の一番下、1行目のすぐ下にあります。したがって、最後のturnLeftコマンドは、東を向いた1行目のKarelを残します。これは事後条件を満たします。
あなたはrunあなたのプログラムを成功させ、それは成功すると1つの塔をクリアして約束された事後条件でKarelを離れる。ヤフー!あなたはこの困難な課題を解決するための画期的な出来事を達成しました。今度はwhileループを使用して1つのタワーをクリアするプロセスを繰り返す必要があります。
しかし、whileループはどのようなものですか?まず第一に、あなたは条件付きテストについて考えるべきです。あなたはそれが列の終わりに壁に当たったときにKarelが止まることを望みます。したがって、正面のスペースが空いている限り、Karelが動き続けることを望みます。したがって、 beeperをすべて収集メソッドにfrontIsClearテストを使用するwhileループが含まれることがfrontIsClearます。各ポジションで、あなたはKarelにそのコーナーで始まるタワーの中のbeeperのすべてを集めることを望みます。その操作に名前を付けると(それはひとつの塔をbeeperようなものになるかもしれません)、まだ詳細を記入していなくてもbeeperをすべて収集方法の定義を書くことができます。
あなたは、しかし、注意する必要があります。 beeperをすべてbeeperのコードはこのようなものではありません。
private void beeperをすべて収集(){
/* バギーループ! */
while(frontIsClear()) {
一つの塔を集める();
move();
}
}
この実装は6章からの一般的なPlaceBeeper行の最初のバージョンがその仕事をすることに失敗したのと全く同じ理由でバグがあります。このバージョンのコードには、最後の通りにbeeper塔があるかbeeperをテストする必要があるため、fencepostエラーが発生します。正しい実装は次のとおりです。
private void beeperをすべて収集(){
while(frontIsClear()) {
一つの塔を集める();
move();
}
一つの塔を集める();
}
このメソッドは、第6章で紹介したPlaceBeeper行プログラムのメインプログラムとまったく同じ構造を持っていることに注意してください。唯一の違いは、このプログラムが1つの塔をputBeeperところでputBeeperです。これら2つのプログラムは、それぞれ次のような一般的な戦略の例です。
private void beeperをすべて収集(){
while(frontIsClear()) {
何らかの操作を行います。
move();
}
最後のコーナーでも同じ操作をします。
}
あなたが壁で終わる道に沿ってあなたがmoveとしてあなたが隅々で操作を実行する必要があるときはいつでもあなたはこの戦略を使うことができます。この戦略の一般的な構造を覚えていれば、そのような操作を必要とする問題に遭遇したときはいつでもそれを使用することができます。この種の再利用可能な戦略はプログラミングで頻繁に登場し、そして プログラミングイディオム または パターン 。知っているパターンが多ければ多いほど、特定の種類の問題に適したパターンを見つけやすくなります。
大変な作業は行われましたが、解決する必要があるいくつかの未解決の点がまだあります。メインプログラムは2つのメソッドをbeeperます - beeperすべてドロップとbeeper - それらはまだ書かれていません。同様に、一つの塔をbeeper calls beeperの行を収集と壁に移動。幸いなことに、これら4つの方法はすべて、特に分解を行わずにコーディングできるほど簡単です。特に帰宅の定義で壁にMoveを使用する場合は特にそうです。これが完全な実装です。