Programování je do značné míry věda o řešení problémů pomocí počítače. Protože problémy jsou často obtížné, řešení - a programy, které tato řešení implementují - mohou být také obtížné. Abychom vám usnadnili vývoj těchto řešení, musíte přijmout metodiku a disciplínu, která sníží úroveň této složitosti na zvládnutelnou míru.
V počátcích programování byl koncept výpočetní techniky jako vědy víceméně experimentem zbožného přání. V té době nikdo o programování moc nevěděl a jen málokdo ho považoval za inženýrskou disciplínu v konvenčním smyslu. Jak programování dozrálo, taková disciplína se začala objevovat. Základním kamenem této disciplíny je pochopení, že programování se provádí v sociálním prostředí, ve kterém musí programátoři spolupracovat. Pokud jdete do průmyslu, budete téměř jistě jedním z mnoha programátorů, kteří pracují na vývoji velkého programu. Tento program je navíc téměř jistě schopen žít a vyžadovat údržbu nad rámec původně zamýšleného použití. Někdo bude chtít, aby program zahrnoval nějakou novou funkci nebo pracoval jiným způsobem. Když k tomu dojde, musí nový tým programátorů jít dovnitř a provést nezbytné změny v programech. Jsou-li programy napsány v individuálním stylu s malou nebo žádnou společností, je velmi obtížné, aby všichni spolupracovali produktivně.
V boji proti tomuto problému začali programátoři vyvíjet sadu metodik programování, které jsou souhrnně nazývány softwarové inženýrství . Používání dobrých dovedností softwarového inženýrství nejen usnadňuje ostatním programátorům číst a porozumět vašim programům, ale také usnadňuje psaní těchto programů na prvním místě. Jedním z nejdůležitějších metodických postupů, které vyplynuly ze softwarového inženýrství, je strategie design shora dolů nebo postupné zušlechťování , který spočívá v řešení problémů tím, že začíná problém jako celek. Celý problém rozbijete na kousky a pak každý kus vyřešíte, pokud je to nutné, rozbijete je. Tato strategie shora dolů je doplněna iterativní testování tam, kde se ujistíte, že menší části řešení fungují, než se vydáte dál.
Abychom ilustrovali koncept postupného zdokonalování, naučme Karla vyřešit nový problém. Představte si, že Karel nyní žije ve světě, který vypadá takto:
Na každém ze sloupů je věž o kuzel s neznámé výšky, i když některé sloupce (například 7. a 9. ve vzorku) mohou být prázdné. kuzel prací je shromáždit všech kuzel s v každé z těchto věží, vrátit je zpět na nejvýchodnější roh 1. řady a pak se vrátit do výchozí pozice. Když tedy Karel dokončí svou práci ve výše uvedeném příkladu, všech 25 kuzel s, které jsou v současné době ve věžích, by mělo být naskládáno na rohu 9. sloupce a 1. řádku takto:
Důležité je, že Karel počátečnízačínás nulou kuzel s ve svém sáčku. Každý kuzel zvednutý je přidán do jeho sáčku. Když je v rohu kuzel s, může karel použít beepersInBag() test.
Klíčem k vyřešení tohoto problému je rozložit program správným způsobem, přičemž je stále možné testovat. Tento úkol je složitější než ostatní, které jste viděli, což je pro výběr úspěšného řešení důležitější.
Klíčovou myšlenkou v postupném zdokonalování je, že byste měli začít navrhovat svůj program z vrcholu, který odkazuje na úroveň programu, která je koncepčně nejvyšší a nejvíce abstraktní. Na této úrovni je problém věže kuzel jasně rozdělen do tří nezávislých fází. Nejprve musí Karel vyzvednout všech kuzel s. Za druhé, Karel je musí uložit na poslední křižovatce. Za třetí, Karel se musí vrátit do své domovské pozice. Tento konceptuální rozklad problému naznačuje, že metoda run pro tento program bude mít následující strukturu:
public void run() {
sbíratVšechnyKuzelS();
poklesVšechKuzelS();
vrátitSeDomů();
}
Na této úrovni je problém snadno pochopitelný. Samozřejmě ještě zbývá ještě několik detailů ve formě metod, které jste ještě nenapsali. Přesto je důležité se podívat na každou úroveň rozkladu a přesvědčit se, že pokud se domníváte, že metody, které se chystáte napsat, vyřeší problémy správně, pak budete mít řešení problému jako celku. .
Nyní, když jste definovali strukturu programu jako celku, je čas na move na první subproblem, který spočívá ve shromažďování všech kuzel s. Tento úkol je sám o sobě složitější než jednoduché problémy z předchozích kapitol. kuzel všechny kuzel s znamená, že musíte vyzvednout kuzel s v každé věži, dokud se nedostanete do posledního rohu. Skutečnost, že musíte opakovat operaci pro každou věž, naznačuje, že zde potřebujete smyčku. Cyklus while bude opakovat proces sbíratJednuVěž a pak se pohybuje.
Pozor: Je nebezpečné pokusit se napsat celý program bez testování jak to půjde. Pokud uděláte chybu, bude těžké najít chybu. Víme, že budeme opakovat proces sběru jedné věže. Napište a test sbírat jednu věž před tím, než dáme SbíratJednuVěž procesu ve smyčce. Tím pádemtemporariliymůžeme začít s následující definicí sbíratVšechnyKuzelS:
private void sbíratVšechnyKuzelS() {
/* dočasné provádění pro účely testování */
sbíratJednuVěž();
move();
}
Jako hlavní princip, pokud máte komplexní smyčku, otestujtetělosmyčky před zápisem celé smyčky.
Když se říká sbíratJednuVěž, Karel stojí buď na základně věže kuzel s nebo stojí na prázdném rohu. V prvním případě musíte sbírat kuzel s ve věži. V druhé, můžete jednoduše move na. Tato situace zní jako aplikace pro příkaz if, ve kterém byste napsali něco takového:
if(beepersPresent()){
sbíratSkutečnouVěž();
}
Než přidáte takový kód do kódu, měli byste přemýšlet o tom, zda potřebujete provést tento test. Často mohou být programy mnohem jednodušší tím, že si všimneme, že případy, které se zpočátku jeví jako zvláštní, mohou být zpracovány přesně stejným způsobem jako obecnější situace. V současném problému, co se stane, když se rozhodnete, že na každé třídě je věž o kuzel s, ale že některé z těchto věží jsou nulové kuzel s vysoké? Využití tohoto přehledu zjednodušuje program, protože již nemusíte testovat, zda je na určité třídě věž.
Metoda sbíratJednuVěž je stále natolik komplexní, že další stupeň rozkladu je v pořádku. Aby bylo možné shromáždit všech kuzel s ve věži, musí Karel provést následující kroky:
Tento přehled opět poskytuje model metody sbíratJednuVěž, která vypadá takto:
private void sbíratJednuVěž(){
turnLeft();
kuzelLinkaKuzelS();
turnAround();
moveKeZdi();
turnLeft();
}
Příkazy odbočitVlevo na začátku a na konci metody sbíratJednuVěž jsou pro správnost tohoto programu kritické. Když se říká sbíratJednuVěž, Karel je vždy někde na 1. řadě směrem na východ. Po dokončení operace bude program jako celek fungovat správně pouze v případě, že Karel bude na stejném rohu opět na východ. Podmínky, které musí být před voláním metody pravdivé, se označují jako předpoklady ; podmínky, které musí platit po dokončení metody jsou známy jako podmínky .
Když definujete metodu, dostanete se do mnohem menších potíží, pokud si zapíšete přesně to, co jsou před a postconditions. Poté, co jste tak učinili, musíte se ujistit, že kód, který píšete, vždy zanechává splněné podmínky, za předpokladu, že byly splněny předpoklady. Přemýšlejte například o tom, co se stane, když zavoláte sbíratJednuVěž, když je Karel na 1. řadě směrem na východ. První povel odbočitVlevo opustil Karla směrem na sever, což znamená, že Karel je řádně vyrovnán se sloupem kuzel s představujícím věží. Metoda kuzelLinkaKuzelS - která ještě musí být napsána, ale přesto plní úkol, kterému rozumíte koncepčně - jednoduše move s bez otáčení. Na konci výzvy kuzelLinkaKuzelS bude Karel stále na sever. Volání turnAround proto opustí Karla směrem na jih. Stejně jako kuzelLinkaKuzelS, metoda move KeZdi nezahrnuje žádné zatáčky, ale místo toho prostě move s, dokud nenarazí na okrajovou zeď. Vzhledem k tomu, že Karel stojí na jih, bude tato hraniční stěna v dolní části obrazovky, hned pod 1. řadou. Závěrečný příkaz odbočitVlevo proto zanechává Karla na 1. řadě směrem na východ, což splňuje postcondition.
Vy jste run váš program a to úspěšně vymaže jednu věž a opustí Karla v slíbené postcondition. Wahoo! Právě jste narazili na milník při řešení tohoto tvrdého úkolu! Nyní musíme opakovat proces čištění jedné věže pomocí smyčky while.
Ale jak to vypadá, když smyčka vypadá? Především byste měli přemýšlet o podmíněném testu. Chcete, aby se Karel zastavil, když narazí na zeď na konci řady. Chcete tedy, aby Karel pokračoval, dokud je prostor vpředu jasný. Takže víte, že metoda sbíratVšechnyKuzelS bude obsahovat smyčku, která používá test frontIsClear . Na každé pozici, chcete, aby Karel shromáždil všech kuzel s ve věži začínající v tom rohu. Pokud dáte této operaci jméno, které by mohlo být něco jako sbíratJednuVěž, můžete pokračovat a napsat definici metody sbíratVšechnyKuzelS, i když jste ještě neuvedli podrobnosti.
Musíte však být opatrní. Kód pro sbíratVšechnyKuzelS nevypadá takto:
private void sbíratVšechnyKuzelS(){
/* buggy smyčka! */
while(frontIsClear()) {
sbíratJednuVěž();
move();
}
}
Tato implementace je buggy z naprosto stejného důvodu, že první verze obecného PlaceKuzelŘádek z kapitoly 6 nedokázala svou práci. V této verzi kódu je chyba fencepost, protože Karel musí testovat přítomnost věže kuzel na poslední třídě. Správná implementace je:
private void sbíratVšechnyKuzelS(){
while(frontIsClear()) {
sbíratJednuVěž();
move();
}
sbíratJednuVěž();
}
Všimněte si, že tato metoda má přesně stejnou strukturu jako hlavní program z programu PlaceKuzelŘádek uvedený v kapitole 6. Jediný rozdíl je v tom, že tento program volá sbíratJednuVěž, kde druhý volal putBeeper . Tyto dva programy jsou příklady obecné strategie, která vypadá takto:
private void sbíratVšechnyKuzelS(){
while(frontIsClear()) {
provést nějakou operaci.
move();
}
provést stejnou operaci pro poslední roh.
}
Tuto strategii můžete použít, kdykoliv budete potřebovat provést operaci na každém rohu, jak jste move podél cesty, která končí u zdi. Pokud si vzpomenete na obecnou strukturu této strategie, můžete ji použít vždy, když narazíte na problém, který vyžaduje takovou operaci. Opakovaně použitelné strategie tohoto druhu se často objevují v programování a jsou označovány jako programovací idiomy nebo vzory . Čím více vzorů znáte, tím jednodušší bude najít ten, který vyhovuje určitému typu problému.
Ačkoli byla vykonána tvrdá práce, stále existuje několik volných konců, které je třeba vyřešit. Hlavní program volá dvě metody - poklesVšechKuzelS a vrátitSeDomů - které jsou dosud nepsané. Podobně sbíratJednuVěž volá kuzelLinkaKuzelS a move KeZdi. Všechny tyto čtyři metody jsou naštěstí dostatečně jednoduché na kódování bez dalšího rozkladu, zejména pokud používáte move KeZdi v definici vrátitSeDomů. Zde je kompletní implementace: