天天看點

《從問題到程式:用Python學程式設計和計算》——1.3 程式開發

本節書摘來自華章計算機《從問題到程式:用python學程式設計和計算》一書中的第1章,第1.3節,作者:裘宗燕 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

在用python學習程式設計時,自然需要了解python語言,但更重要的是學習、了解和運用人們長期程式設計工作總結出的經驗,包括正确的思考問題方法、正确的程式開發方法以及一些有益的正常做法,還要養成良好的程式設計習慣。随着學習的深入,需要解決的問題也會變得越來越複雜(當然,實際中的問題和解決它們的程式更複雜得多)。比較複雜的東西不是随随便便就能做好的,需要認真工作,也需要正确的工作方法。本書中許多地方提出了這些方面的建議,希望引起讀者的重視。

本節簡單讨論程式的開發過程,包括程式的設計、實作(程式設計)、測試(testing)和排除錯誤(簡稱排誤,做實際工作的人們通常将其稱為調試,英文詞是debugging)等方面的問題,這些都是程式開發中必經的工作階段。對初學者而言,因為缺乏程式設計實踐,下面講到的一些情況,初讀起來可能無法完全了解,但是這些問題确實應該說明,是以放在這裡集中讨論。希望讀者在學習了本書中的若幹章節,做了一些程式後再回來重讀這些說明,有可能更好地了解這裡的讨論及其重要意義。

1.3.1 程式開發過程

用計算機和python語言解決問題的過程如圖1.6所示,其中包含一系列的步驟,用矩形塊表示,開發過程的主線用寬箭頭表示,其他實線和虛線箭頭表示在一些情況下的工作轉移,說明在這種工作過程中可能有反複。下面是一些說明:

1)分析,嚴格化:需要用計算機解決的問題來自實際,即使是一道習題,一般也是用自然語言說明的。程式設計工作的第一步是把問題弄清楚,确定到底需要做什麼。

2)設計:根據問題的清晰描述,設法找到一種解決問題的途徑(解決方案)。

3)程式設計:采用某種程式設計語言(本書中用的是python)寫出解決問題的程式(代碼)。我們可以用普通文本編輯器或者專門的程式開發環境編寫程式。在下面讨論python程式設計時,考慮的是用python系統自帶的程式設計環境idle。

《從問題到程式:用Python學程式設計和計算》——1.3 程式開發

https://yqfile.alicdn.com/1b2f029163349c3d785fa9b8fca098e65a7c8fad.png" >

4)檢查(檢查源程式):人工檢查和/或用語言系統檢查。發現錯誤時需要設法确定錯誤根源,然後予以更正。實際上,程式設計和人工檢查經常交替進行或同時進行。開發出一段python程式後,人們就會把它送給解釋器檢查。此外,檢查中發現的錯誤也不一定是簡單的程式設計錯誤,有可能是前面的分析或者設計有錯,發現這種情況時就需要回到前面階段。通過了python解釋器檢查的程式就可以運作了。

5)測試/調試:以适當方式運作程式,送給它适當的資料,讓它工作。通過這種試驗運作檢查程式的工作情況是否正常,産生的效果或者結果是否滿足需要。發現錯誤後應設法确定錯誤根源,回到前面步驟修改設計或程式等。

重複進行上述過程,有時可能要回到問題分析、方法設計步驟。經過反複測試,直到确信程式正确為止。下一小節将專門讨論與程式錯誤有關的情況。

1.3.2 程式錯誤

人很容易犯錯誤,做複雜的事情時經常出錯。做一道複雜數學題、寫一篇長文時,很難保證其中沒有寫錯的文字或描述。程式可能很複雜,開發程式的過程可能很長(參見圖1.6)。另一方面,寫出的程式是一段靜态的符号文本,其意義要通過在計算機上執行而展現。程式的靜态文本與動态執行之間的關系并不容易把握,程式設計學習的一項重要内容就是學習了解程式的意義。綜合上面各方面的因素,歸結到一句話:編寫程式的過程中出錯的情況很常見,是以,糾錯是程式設計中不可避免的一項重要活動。

有關排除程式錯誤的術語是debugging,關于它還有一個傳說:在計算機發展早期的某一天,一台計算機出了故障。人們仔細檢查,發現計算機裡有一個被電流燒焦的小蟲(bug),由此造成電路短路,小蟲是這次故障的禍根。此後檢查排除計算機故障的工作就被稱為debugging,也就是“找蟲子”。後來人們也這樣稱呼檢查程式錯誤的工作。

實際上,對于程式設計而言,這個詞并不貼切。在程式裡出現的錯誤都是程式設計式者自己在工作中犯的錯誤,沒有其他客觀原因,也沒有蟲子之類的東西在搗亂。開始學習程式設計就應該牢記這個情況:所謂排除程式裡的錯誤,也就是排除自己在程式設計中犯下的錯誤,排除自己寫在程式裡的錯誤。初學者遇到自己的程式出錯時,常傾向于認為是系統或計算機有問題,常常會說“我的程式絕對沒錯,一定是……”。而有經驗的程式員都知道,如果自己的程式出了錯,基本上可以肯定是自己的錯,自己的責任。

程式錯誤可以分為兩大類,一類是程式的書寫形式在某些方面不符合語言要求。語言系統在處理程式時可以檢查出這類錯誤。另一類是程式的書寫形式沒錯,可以正常執行。但是可能在執行中報告運作錯誤;或者程式的執行能正常完成,但産生的結果(或執行效果)不符合需要。所謂排除程式的錯誤,就是要消除這兩類錯誤。

下面讨論特别針對用cpython和idle開發python程式的情況。使用其他工具開發python程式的情況類似,有些工具在編輯程式時就能及時發現一些錯誤。但無論如何,可能出現錯誤的情況和如何檢查排除的道理方面沒什麼不同。

檢查程式可能發現的錯誤

idle編輯器的run菜單之下有一個check module指令,用于檢查正在編輯的程式。此外,要求python運作程式時,解釋器也會先檢查這個程式。檢查過程中可能發現一些錯誤,這時解釋器的處理立刻停止,産生一些出錯資訊,并在源程式中标出發現錯誤的位置。遇到這種情況,我們就應該仔細閱讀這些錯誤資訊,檢查解釋器指定的位置附近的源程式代碼,找到真正錯誤原因并予以排除,然後再繼續工作下去。

python解釋器在檢查中能發現的錯誤主要有兩類:

1)文法錯誤(錯誤名為syntaxerror):即程式中某些部分的内容或結構不符合python語言的基本文法要求。錯誤的原因如缺少必要的符号(常見的如缺少冒号),幾個字元構成的關鍵組合符号拼寫不正确,使用的名字不符合python基本要求等。

2)程式格式錯誤(indentationerror,taberror):python對程式的編排有特殊要求,如一些代碼行需要互相對齊,不能交替使用空格和制表符(制表符用鍵盤tab鍵輸入)。在程式格式方面,idle和其他專門支援python程式開發的軟體開發環境都能提供很多幫助,它們能自動處理python程式中的格式。如果用普通的文本編輯器寫python程式,或者自己在程式裡随便加空格,就可能造成這類錯誤。

解釋器的工作方式是按照程式文本的描述,一個個字元地順序檢查python程式,如果檢查到某一點确定了程式有錯,就把這一點标記為發現錯誤的位置。源程式裡的實際錯誤有可能出現在解釋器标出的位置,也可能出現在這個位置之前。看到解釋器的錯誤報告後,我們應當從指明的位置開始向前檢查,設法确定錯誤原因。有些錯誤可能在實際出現以後很久才被解釋器發現,也就是說,實際錯誤可能出現在解釋器所指位置之前很遠的地方。一般而言,這類錯誤都比較容易确認和更正。

程式運作中發現的錯誤

一旦程式通過了檢查,我們就可以用python解釋器去運作它。在python程式的運作中可能出現幾類不同的錯誤,需要設法解決:

1)程式的運作突然停止,解釋器報告運作中出錯。這種錯誤稱為動态運作錯誤,說明運作中出現非正常情況,導緻某些操作無法完成。算術運算中遇到除數為0的情況就是一例。可能導緻程式以非正常方式終止的錯誤很多,後面章節中有些介紹。

2)程式執行中不報告錯誤,但也一直不結束,或是長時間沒有任何反應,或是反複地輸出一些類似資訊。這些現象說明程式執行有可能進入了死循環,也就是說,永無休止地重複執行一段代碼。當然,長時間無反應未必說明程式進入死循環。如果一個程式要求輸入,它也會進入等待狀态,直到人輸入資訊後才繼續。另一方面,有的程式确實需要運作很長時間。程式是否真正進入死循環,還需要仔細分析和判斷。在idle裡,可以通過ctrl-c組合功能鍵強制結束目前正在執行的程式。

3)程式能執行到結束,但得到的結果不正确,或程式執行中産生的效果不符合需要。這說明程式有語義錯誤,或稱邏輯錯誤,說明程式編錯了。這種錯誤的根源更複雜,可能是做問題分析工作時沒有看清情況,也可能是算法設計不對,或者是程式設計中有意無意引進的錯誤,例如寫錯了變量名字等。這類錯誤是最難确認更正的,下面有些讨論,後面章節裡還有進一步的說明。

當然,python系統和計算機上的作業系統也是程式,雖然經過仔細測試和長期使用,也不能保證其内部絕對沒有錯誤。如果在運作一個python程式時解釋器突然崩潰,或者整個計算機當機,那就有可能是python系統或計算機作業系統的問題,需要找更專業的人士來解決。但這類情況極罕見。一般而言,程式運作中出問題,都是我們自己的程式有錯,需要設法排除錯誤,這是程式開發過程中的一項重要工作。

排除程式錯誤

解釋器檢查階段可能發現的錯誤通常比較簡單,容易找到根源并更正。本小節主要關注如何解決在程式的試驗運作中發現的錯誤。顯然,這時的工作就是設法弄清錯誤原因,找到實際包含錯誤的代碼并更正之。

發現錯誤後,首先應該人工地做一些分析工作:弄清錯誤的表現,根據發現的情況分析造成錯誤的各種可能,仔細分析輸入資料和得到的結果,分析可能出錯的程式片段,仔細閱讀這些片段,分析其可能行為,逐漸排除疑點。通過這種人工工作,在很多情況下都能最終定位有錯誤的程式代碼,下面的工作就是設法糾正錯誤。如果确認出錯的隻是程式代碼,就應該仔細考慮如何修改代碼,排除錯誤。如果最後發現問題的根源是在程式設計之前的工作步驟,就需要轉回到那裡去設法解決問題。

即使直接的人工檢查不能發現錯誤,也常常會發現程式中的一些疑點。在這種情況下,就應該選擇一些特殊的資料做進一步試驗,設法确認自己的認識,縮小錯誤範圍。進而設法找到導緻錯誤的最簡單資料。修改出錯的程式後再重新試驗運作。經過一系列試驗和仔細分析,程式中較為簡單的錯誤都可能得到确認。

如果直接分析各種可見的現象後仍不能确定錯誤的原因,就需要采用各種動态檢查技術。動态檢查的基本方法是檢查程式執行的過程和中間狀态,最常用的方式是在程式裡有疑問的位置插入一些輸出語句,讓程式在執行中輸出一些變量的值。通過檢查某些關鍵性變量的變化情況,常常可以發現導緻程式錯誤的線索。

python系統的idle與所有功能較強的程式開發環境一樣,為動态檢查程式提供了很好的支援。我們可以在idle裡以調試方式執行程式,這時可用的主要功能包括追蹤、監視、設定斷點、中斷執行等。這裡先對有關概念做一點簡單介紹:

追蹤:以正常方式執行程式時,程式啟動後将一直運作到結束(執行完成而終止,或被強行終止),或者進入死循環。對程式進行追蹤,則是以受控方式執行它。例如,可以要求一個個地執行程式裡的語句(單步執行),或者要求程式執行中暫停在某個特定位置(中斷執行)等。在受控執行中,可以很友善地檢查程式執行的中間狀态,以及在執行過程中一些變量的變化情況,有助于發現程式裡的問題。

設定斷點:在啟動追蹤前标出程式裡的一些位置,要求程式執行到達這些位置時停下來接受檢查。這樣可以很友善地檢查當時執行現場的各種情況(變量的值)。程式在斷點暫停後,可以指令其繼續執行或者結束。

中斷執行:當發現(或認為)程式進入非正常狀态,或在程式執行中需要檢查中間狀态時,可以通過中斷指令中斷程式執行,讓程式停在當時的執行點,但仍處于執行狀态,以便檢查。使用idle時,可以用ctrl-c中斷程式執行。

第4章将介紹idle支援程式調試的功能。

idle這類支援程式設計的工具被稱為內建開發環境(integrated development environment,簡記為ide),它們內建了輔助程式設計、運作、測試和調試程式的多方面功能,可以為程式設計提供很多幫助。在學習程式設計時,也應該學習和掌握這類工具。不同ide可能各有特點,但在對程式開發和調試的支援方面差别不大,掌握一個就可以觸類旁通。

當然,再好的ide也隻是工具。如果能熟練使用,有利于幫助我們發現程式錯誤的線索,但确認和改正錯誤,還必須靠人自己動腦動手。ide雖然可以使程式設計工作更友善,但它不會改變程式設計工作的實質。也應該看到事物的另一面:好的ide不能造就優秀的程式工作者。現在的ide越來越強大,但很多人用它們編出的程式品質卻很差。是以,編好程式的最基本因素仍然是人,不是更好的工具。

要編好程式,最重要的還是要了解這一工作中的規律性,建立良好的程式設計習慣,采用正确的工作方法,積累程式設計的經驗。這些都是至關重要、不可替代的。程式不是代碼的堆積,程式設計中一個最重要的問題就是程式的設計群組織,程式越大,這方面工作的地位和作用就越明顯。良好編寫的程式,不僅更容易做正确,發現了錯誤也更容易定位,容易修改和維護,容易更新改造。本書後面将通過讨論和例子反複強調這一問題。

關于測試,還有一個重要問題。荷蘭計算機科學家dijkstra(圖靈獎獲得者)有一句名言:測試可以發現一個程式裡有錯誤,但是不能确認其中沒有錯誤。一個程式是否正确,是一個非常深刻的問題,關于這個問題,既有許多理論研究,也有許多實際的方法研究。在進入程式設計這個世界之前,請大家首先記住這一點。

1.3.3 從問題到程式

選擇了python作為程式語言,應該如何着手編寫程式呢?程式設計是一種智力勞動,程式設計式就是以計算的方式解決問題。初學程式設計時要解決的問題很簡單,類似于一道數學或實體應用題,當然要做的是程式設計,要求完成一個符合題目要求的程式。

一般說,用程式設計方式解決問題的過程可以分為三步(請重看圖1.6):第一步是分析問題,設計一種解決方案;第二步是用程式語言嚴格描述這個解決方案;第三步是在計算機上試用這個程式,看它是否真的能解決問題。如果發現錯誤,就需要分析錯誤原因,弄清問題後回到前面步驟去糾正錯誤。

工作的第一步與在其他領域裡解決問題類似,隻是考慮問題的基礎不同。做程式設計時需要從計算和程式的觀點出發,這裡有許多新問題,是本書讨論的一個重點。第二步和第三步是程式設計的特殊問題。語言中各種結構有明确定義的功能,把頭腦中形成的解決方案變為程式,往往也不是直截了當的,需要仔細考慮和規劃。進一步說,用符合語言規定的結構和形式寫出程式,也有不少工作要做,這個過程中也可能犯錯誤。前面關于程式中的可能錯誤與排除的讨論,主要關注第二步和第三步之間的小循環,這方面有許多新東西需要學習。如果測試中發現是問題的解決方案本身有錯誤,就需要回到第一步了。

在程式設計領域,在解決小問題與解決大問題之間,為完成課程練習而寫程式,與為解決實際問題而寫程式之間并沒有一條鴻溝。在開發實際程式或軟體系統時,前期工作的比重将大大增加:首先需要把問題分析清楚,弄明白到底要做什麼。本書中讨論的,并通過執行個體反複展示的理論、技術和思考問題的方法,同樣适用于複雜軟體的開發過程。

程式的分解和抽象

還有一個問題值得提出:同樣一個程式有可能在不同的層次上描述。這裡還用日常生活中的程式性活動作為例子,考慮前面學生早晨的活動過程。例如1.1.2節中的活動描述提到刷牙,那裡隻用一個詞描述這個動作。但如果仔細想想,刷牙也是一個複雜過程,可以進一步将其分解為取杯子、裝水、取牙刷、擠牙膏、漱口、刷牙、清洗牙齒等一系列細節動作。如果需要,還可以進一步将上述每個動作分解為一系列的肌肉動作。

最終的程式細節需要分解到哪個層次,依賴于程式設計語言提供的基本功能。但另一方面,程式的描述方式也要照顧到人的需要。複雜的程式可能包含許多功能,直接在語言的基本層面上描述層次太低,程式的意義很難把握,很難保證實作所預想的功能,也難修改程式去滿足新需要,就像看到列出極長的一系列有關肌肉伸縮動作的描述,很難了解這個人做的是刷牙動作一樣。是以,在開發複雜程式時,應該采用高層次的描述,把程式功能在各層次上逐漸分解。随着程式變得越來越複雜,其組織結構問題也會變得越來越重要。

還是用生活中的例子來說明問題。對于學生早上起床後的活動,我們首先應該在很高的層次上描述,就像前面所給出的:

起床

刷牙

洗臉

吃早飯

去教室上課

這個描述把一個複雜程式分解為若幹相對簡單的部分。如果需要進一步細化,那就降到下一層次,把一個高層動作分解為一系列相對低層的基本動作。例如,高層的“吃早飯”動作有可能進一步分解為下面的動作序列:

5.1 拿飯卡

5.2 去食堂

5.3 排隊買飯

5.4 吃早飯

5.5 結束和清理

5.6 離開食堂

必要時再做進一步分解。例如将“排隊買飯”分解為“排隊、選飯、選菜、付款”等。在這種分解過程中,應該保留已有的抽象層描述。這種層次結構有助于了解程式的全局和細節,幫助發現程式錯誤,使其易于根據需要修改。例如,假設學校食堂改為快餐店,由于整個程式已分解為一些獨立的步驟,修改起來也會容易一些。

為計算機程式設計式也需要這種工作方式,程式設計者需要從問題的需求出發,從高層開始設計程式,然後逐漸分解程式功能。分解到一定細節程度後,就可以用程式設計語言的已有結構直接描述了。這是分析和構造程式的正确方法。後面将仔細讨論這些問題。

與之對應的,程式設計語言也應該為程式的分層構造提供支援。參考前面的讨論,作為用于寫程式的程式設計語言,必須包含下面兩方面的基本構成要素:

1)需要有一組基本操作,作為複雜計算活動的實作基礎。機器和彙編語言裡的基本操作就是各種基本指令,進階語言也提供了一組基本操作。

2)需要一套描述計算的流程如何進行的組合機制。機器和彙編語言裡的基本機制是順序執行,以及完成有條件轉移或無條件轉移的專門指令。進階語言也需要提供一套用于組合簡單計算,構造出任意複雜的計算描述的結構。

從理論上說,上面兩類要素的組合足以構造出任意複雜的程式。但從實踐的角度看,隻有它們,實際開發者很難(或說幾乎不可能)寫出很複雜的程式。為了支援複雜程式的開發,語言中需要第三類機制:抽象機制,其直接作用就是把一些複雜的功能包裝成為一個整體,用于支援程式的分層次構造。python為此提供了計算過程的抽象機制和資料抽象機制,有關細節将在後面章節裡仔細讨論。

程式設計能力

本書及相關課程涉及多方面的能力鍛煉,包括知識記憶和靈活運用,解決問題的思維方法,具體處理方法和技巧,實際工作和操作技能。下面列舉幾個重要方面:

1)分析問題的能力,特别是從計算和程式的角度分析問題的能力。需要學會從問題出發,通過逐漸分析和分解,把原問題轉化為能用計算機通過程式方式解決的問題,在此過程中設計出解決方案。這方面的深入沒有止境。各個領域的問題都需要用計算機解決,參與者既需要熟悉計算機,也需要熟悉專業領域。将來的世界特别需要這種相容并包的人才。雖然教科書裡的問題很簡單,但它們也是通向複雜問題的橋梁。

2)掌握所用的程式語言python。語言是程式設計的基本工具,要寫好程式,必須熟悉所用的語言,熟悉其中的各種結構,它們的形式和意義。應該注意,熟悉語言絕不是背誦定義,這裡說的熟悉隻有在程式設計的實踐中才能完成。就像隻是上課和在岸上比劃,做的再多也不能學會遊泳一樣,隻是看書、讀程式、抄程式不可能真正學會寫程式。學習程式設計,必須反複地親身實踐從問題到程式的整個過程,動腦筋想辦法,處理遇到的各種情況。如前所述,目前人們常用于軟體開發的程式設計語言不止python一種。但各種語言有很多共性,學習了一種之後可以作為參照。另一方面,在用一種語言學習程式設計的過程中積累的一般性知識和經驗,在用任何語言開發程式時都可以參考。

3)學會寫程式。雖然寫過程式的人很多,但會寫程式、能寫出好程式的人就少得多了。經過多年程式實踐,人們對“好程式”有了許多共識。例如,解決同樣問題的程式越簡單越好。這裡可能有計算方法的選擇問題,有語言的使用問題。除了程式正确外,人們也特别關注程式是否結構良好,是否清晰,易閱讀和了解,條件或要求改變時是否容易修改去滿足新需要等。後面将反複提到這些問題。

4)檢查程式錯誤的能力。初步寫出的程式經常包含一些錯誤。雖然解釋器能幫助查出其中一些,并通告發現錯誤的位置,但确認實際錯誤和實際位置,弄清應該如何改正,永遠是程式設計式的人自己的事情。對系統報告的運作錯誤,死循環或邏輯錯誤等的認定,更要依靠人的能力。這種能力也需要在學習中培養和鍛煉。

5)熟悉所用工具和環境。程式設計要用一些程式設計工具,要在具體的計算機環境中進行,熟悉工具和環境也是這個學習中很重要的一部分。本書建議用idle做程式實習,熟悉這個環境的使用也很重要,可能大大提高工作效率。不同的程式設計工具之間也有很多共性,學習了一種工具,對了解掌握其他工具也将很有幫助。

後面各章将逐漸展開有關計算和程式設計的讨論:從最簡單的計算問題、最簡單的資料描述和簡單表達式開始,讨論在python中寫簡單程式的情況。而後讨論程式的基本流程結構,以及如何用這些結構解決更複雜一些的計算問題。然後介紹程式的組織和抽象,以及python語言為計算抽象提供的基本機制——函數,還要介紹python程式的基本結構和一些内在的道理。随後的讨論将轉向資料的組織,介紹python的各種資料組織功能。在複雜的計算中,需要處理的資料也更加複雜多樣,需要采用适當的方式将它們組織起來。python語言為資料組織提供了一套标準功能,如果需要,我們還可以自己定義資料的組織方式。在讨論面向對象的第7章裡,将仔細讨論這方面的重要思想和應用技術。本書最後部分還将讨論一些更具體的而且也很重要的程式設計領域和問題。

繼續閱讀