前面幾章中講過的Hello貓咪、打地鼠以及其他應用都是些非常小的軟件項目,并不需要用引入軟件工程的概念。工程的概念借用自其他行業,意為設計并建造,教程中的應用就像是用預制件拼裝起來的房屋模型,而軟件工程才是設計并建造真正用來居住的房子。這個例子雖然稍顯夸張,但一般來講,某些極其復雜的建造過程,的確需要大量的前期構思、規劃以及技術分析,這些過程都可以歸結為工程。

但凡接手過一個相對復雜的項目,你就會理解,只要在功能上稍稍增加一點復雜度,軟件工程的復雜程度就會急劇增加,兩者之間絕對不是線性的關系。對于我們大多數人來說,在真正開始面對這樣殘酷的現實之前,我們很少能夠意識到將要面臨的困頓。從這個意義上講,你要準備學習更多的軟件工程的原則及調試技巧。如果你已經認可這一點,或者,你是為數不多的、希望通過掌握一些技術來克服成長障礙的人,那么本章就是為你準備的。
## **軟件工程原則**
以下是本章所涵蓋的一些基本原則:
* 未來的軟件使用者應該盡早,并盡可能多地參與到軟件的設計及開發過程中來;
* 建立一個初始的、簡單的原型,并逐步完善;
* 編碼與測試同步進行,不要一次測試太多的代碼(App inventor中的塊);
* 開始編碼前進行邏輯設計:對功能做縱向切割,對技術或實施的復雜度做分層切割,并各個擊破;
* 對代碼塊進行注釋,以便其他人(和你自己)能理解這些程序;
* 學會用紙筆來跟蹤記錄塊的執行過程,以便于理解它們的工作機制。
如果能夠遵循上述原則,你就可以節省時間,避免挫折,從而制作出優秀的軟件。但你很有可能做不到每次都依原則行事!有些原則看似違背常理。一種自然的傾向是,首先有了一個想法,并假設你了解用戶的需求,然后開始把若干個塊拼在一起,直到完成了想象中的任務。現在,讓我們回到軟件工程的第一個原則,在正式開始動手之前,看看如何了解用戶的需求。
### **設計要面對真實的人、現實的問題**
電影《夢幻成真》(Field of Dreams)中的男主角Ray聽到了一個聲音向他低語:“如果你建好了,他們就會來。”Ray聽從了這個聲音,在愛荷華州的農場中間建了一個棒球場,果然,在1919年,芝加哥白襪隊(White Sox)和成千上萬個球迷出現在這里。
不過你現在必須明白,那個低語的建議絕對不可以用于軟件開發。事實上,必須正相反。在軟件開發的歷史中,充斥著各類“沒問題”的偉大方案(如:“讓我們寫個軟件,告訴人們開車到月球需要多長時間!”)。一個優秀的(同時也是極有可能獲利的)軟件的真正目的是解決現實中的問題。想知道問題出在哪里,就要找到有問題的人,并與他們交談,這就是通常被稱作“以用戶為中心”的設計方法,這個方法同樣也可以幫助你做出更好的應用。
如果遇到程序員,你可以問他們,在他們所寫的軟件中,有多少被真正交付到了最終用戶的手中。結果會讓你感到驚訝:即使是對那些偉大的程序員來說,這個比例也還是太小了!許多軟件項目駛入了問題的泥沼而終無見天之日。
以用戶為中心的設計理念意味著盡早并盡可能多地替未來的使用者著想,并與他們交流,這種思考與交流甚至應該在尚未確定目標之前就開始。大多數成功的軟件都是針對某個具體的人,試圖解決他的特定問題,也只有這樣,最終才能發展成一個偉大的產品。
### **快速地創建軟件原型,并展示給未來的使用者看**
如果讓最終用戶閱讀軟件功能的說明文檔,他們多半不會給出任何有效的回應,他們不會對文檔做出反饋。真正有效的方法是,讓他們體驗未來軟件的交互模式,即軟件的原型。原型是一個不完整的、未經重構的軟件版本,創建原型的目的在于充分體現軟件所具有的核心價值,而不必注重細節、完整性或漂亮的用戶界面。拿出原型讓未來的使用者看,然后安靜地傾聽他們的反饋。
### **迭代式開發**
在首次明確了軟件的具體規格之后,采用迭代式開發。你可能很自然地傾向于將所有組件和塊一股腦地添加到應用中,然后下載到手機上看看它是否好用。舉例來說,“答題”應用,在缺乏指導的情況下,多數初學者會一次性添加所有的塊:帶有一長串問題及答案的塊、瀏覽問題的塊、檢查用戶答案的塊,以及與每個邏輯細節有關的塊,所有的塊未經測試就全部羅列在應用中,這種開發方式在軟件工程中被稱為“大爆炸”方式。
幾乎所有的初學者都會采用這種方式。在舊金山大學(USF)的課堂上,當學生忙于創建應用時,我經常會問他一個問題:“進展如何?”
“我想我做完了,”他說。
“好極了,能讓我看看嗎?”
“哦,還不行,我沒帶手機來。”
“那么你還從來沒有運行過這個程序,對嗎?”我問。
“嗯...”
我透過他的肩膀看到了30個左右色彩繽紛的塊,但他居然連一個功能都沒有測試過。
程序員們很容易著迷,他們沉湎于創建UI(用戶界面)并在塊編輯器中創建所需的行為。那些塊天衣無縫地結合在一起,優雅地排布在屏幕上,這些讓他們感到傾心,卻忘記了創建一個讓其他人也能使用的、完整的、通過測試的應用。這聽起來像是洗發水的廣告,但對于我的學生和那些有志成為程序員的人,這是我能給出的最好的建議:**代碼要隨寫隨測,周而復始。**
每次只寫少量代碼,并隨時測試,這個過程本身會變成一種習慣,如此這般,在不久的將來,你會收獲令人驚異且滿意的成果(而且幾乎杜絕了大而難纏的程序漏洞——bug)。
### **先設計,后編碼**
編程要分兩步走:①理解應用的邏輯,②將這些邏輯翻譯成某種形式的編程語言。在開始翻譯之前,要在邏輯上花一些功夫:首先要明確應用中將會發生哪些事情,無論是用戶引發的,還是應用內部的;其次在正式開始將邏輯翻譯成代碼塊之前,要明確每個事件處理程序中的邏輯。
有許多專門討論各種程序設計方法的書籍。有些人喜歡用流程圖或結構圖來做設計,有些則更愿意將設計或草圖寫在紙上,更有人認為所有的“設計”最終應該體現為代碼的注釋,而不是一個與代碼分離的文檔。對于初學者來說,關鍵是要理解所有的程序在本質上都是一套邏輯,而這種邏輯與具體的編程語言無關。當然,思考應用的邏輯和翻譯為編程語言這兩件事有時難免會同步進行,無論這種編程語言是否直觀。因此,在整個邏輯思考階段,應該遠離電腦,想清楚應用最終要實現哪些功能,并以某種方式隨時記錄下你的想法,然后讓設計文檔與應用保持關聯,以便其他人也可以從中獲益。下面我們就來實踐這一過程。
### **對代碼進行注釋**
你已經學過了本書中的教程部分,應該見過塊所附帶的黃色方框(見圖15-1),這就是“注釋”。在App Inventor中,任何的塊都可以添加注釋,方法是在塊上單擊鼠標右鍵,并在快捷菜單中選擇**Add Comment**。注釋絲毫不影響程序的運行。

**圖 15-1 為測試條件塊添加注釋——用簡潔的語言描述塊的作用**
那么為什么要做注釋呢?想想看,如果你的應用很成功,它的生命周期會很長,即便只是擱下一周的時間,你都有可能忘記當時的想法,想不起來這些塊有什么用處。因此,盡管沒有別人會看到你的代碼塊,你也應給添加這些注釋。
假如你的應用很成功,毫無疑問它會傳到很多人手里,人們想了解它、按自己的需要修改它,或者擴展它的功能,等等。在開源的世界里,很多項目會以現有項目為基礎,做進一步的修改和完善,只要你親身體驗過那些沒有代碼注釋的項目,你就會徹底明白為什么注釋是必需的。
為程序添加注釋并不是一種自覺地行為,我也從未見到過初學者重視它,然而,我也從未見到過一個經驗豐富的程序員不重視它。
### **切割、分層、各個擊破**
當問題的規模大到難以應對時,解決之道在于將問題分解,分解的方法有兩種:第一種方法我們非常熟悉,即,將問題分解為若干個部分(如A、B、C),然后各個擊破;第二種則不太常見:將問題按照從簡單到復雜的順序逐層分解。對應到App Inventor的編程方法上,就是先添加少量的塊來實現簡單的功能,并測試其效果,再逐漸過渡到復雜的功能,以此類推。
讓我們以第10章的“出題”應用為例來具體闡述這兩種方法。在應用中,用戶可以點擊“下一題”按鈕對問題進行瀏覽,也可以檢查用戶的答案是否正確。從設計角度,可以將應用分解為兩個部分:問題瀏覽及答案核對,并針對兩個部分單獨編程。
但在每個部分中,還可以對整個過程按照從簡單到復雜的順序進行分解。例如,問題瀏覽環節,先創建代碼來顯示問題列表中的第一題,并測試其是否有效;然后編寫代碼來瀏覽到下一題,暫時不考慮到達最后一題時可能引起的錯誤;當測試結果證明可以從頭至尾瀏覽所有問題時,再添加塊來處理用戶瀏覽到最后一題的“特殊情況”。
究竟是將問題分解為幾部分,還是按照復雜性分解為若干層,這不是一個非此即彼的問題,但卻是一個值得思考的問題,關鍵在于哪種方法更適合于你所創建的應用。
### **理解編程語言:用紙和筆跟蹤記錄**
應用在運行過程中,僅有部分可見。最終用戶只能看到它的外觀——用戶界面上顯示的圖形及數據,而軟件的內部運作機制對外部世界來說是不可見的,就像人類大腦的內部機制一樣(謝天謝地!)。應用在運行時,我們既看不到這些指令(塊),也看不到跟蹤當前正在執行的指令的程序計數器,更無法看到軟件的內部存儲單元(應用中的屬性及變量)。不過說到底,這正是我們想要的:最終用戶只能看到程序需要被顯示的部分,但對于開發者來說,在開發及測試過程中,你需要了解所有正在發生的事情。
作為一個開發者,在開發過程中所看到的代碼,都只是些靜態視圖,因此必須靠想象力來驅動軟件的運行:事件發生了,程序計數器移動到下一個塊,并執行這個塊,內存單元中的值發生了變化,等等。
編程過程中需要在兩種不同的場景之間切換:先從靜態模式——代碼塊開始,并試著想象程序的實際運行效果;一切就緒后,切換到測試模式——以最終用戶的身份測試軟件,看它的運行結果是否與預期的結果相一致。如果不是,必須再切換回靜態模式,調整程序,然后再試。如此循環反復,最終獲得一個滿意的結果。
初學者對于計算機程序的運作方式知之甚少,整個過程看起來就像魔術。依照本教程的指導,學習應該從簡單的應用開始(如,點擊按鈕導致貓叫),再逐漸過渡到較為復雜的應用,而且隨著學習的不斷深入,或許還可以根據自己的需要,對教程中的例子做出修改。從初學者到入門者,對程序的內部運作機制有了一些了解,但依然感到對整個過程無法控制。他們經常會說:“這個不起作用,”或者“它不應該是這樣的。”關鍵是要理解程序如何實現那些你主管想象出來的功能,而且要說:“我的程序正在做這件事”,以及“我的邏輯導致了程序的...”。
了解程序運行機制的方法就是剖析一個簡單應用的執行過程,在紙上精確地描繪出每個塊在執行時,設備的內部發生了什么。想象用戶觸發了某個事件處理程序,然后逐步跟蹤并記錄塊的執行效果:應用中的變量及屬性如何改變,用戶界面上的組件如何改變。就像文學課上的“精讀”環節,這樣一步一步的跟蹤可以促使你檢查語言中的各個要素(即App Inventor中的塊)。
對復雜性的描述幾乎是完全抽象的,重要的是你要放慢思路,理清各個塊之間的因果關系。最終你會明白,這些過程控制的規則,并不像最初想象的那樣難以理解。
以第8章總統測驗為例,如圖15-2所示,思考圖中的這些塊(對原教程做了一點修改)。

**圖 15-2 應用啟動時,將QuestionLabel的Text屬性設置為QuestionList列表的第一項**
你能理解這些代碼嗎?你能跟蹤這些代碼,并說明每一步都發生了什么嗎?
首先跟蹤所有相關的變量及屬性。畫出存儲單元的表格,這個例子中,表頭分別為currentQuestionIndex和QuestionLabel.Text,如表15-1。
**表15-1 記錄text屬性及index值變化的表格**
| QuestionLabel.Text | currentQuestionIndex |
| --- | --- |
|||
接下來,思考當應用啟動時,發生了哪些事——不要以用戶的視角來看,而是從應用的內部來分析它的初始化過程。如果你學過這些教程,你可能知道這個過程,但你可能沒有從機制方面去思考過。當應用啟動時:
1\. 完成了所有組件的屬性設定,它們的值等于在組件設計器中設定的初始值;
2\. 完成了所有變量的定義及初始化;
3\. 執行了Screen.Initialize事件處理程序中的所有塊。
對程序進行跟蹤有助于理解程序的運行機制,那么在完成了應用的初始化之后,表格中應該填寫什么內容呢?
如表15-2所示,currentQuestionIndex的值為1,因為應用啟動時完成了變量的定義,并將其初始值設為1;而QuestionLabel.Text的值為第一題,因為在Screen.Initialize中選擇了QuestionList列表中的第一項,并放入了QuestionLabel中。
**表15-2 總統測驗應用初始化后,QuestionLabel.Text與currentQuestionIndex的值**
| QuestionLabel.Text | currentQuestionIndex |
| --- | --- |
| 哪位總統在大蕭條時期實施了“新政”? | 1 |
下面再來跟蹤用戶點擊“下一題”按鈕時發生的事情。

**圖 15-3 用戶點擊“下一題”按鈕時執行的塊**
逐個檢查每個塊。首先是變量currentQuestionIndex的遞增,說得更具體一些,變量當前值是1,經過+1的運算后,將結果2再賦給變量currentQuestionIndex。接下來看if語句,列表QuestionList的長度為3,顯然currentQuestionIndex的值2小于3,因此if語句的結果是false(假),于是列表中的第2項(第二題)被寫入QuestionLabel.Text中,如表15-3所示。
**表15-3 點擊“下一題”按鈕后的變量及屬性值**
| QuestionLabel.Text | currentQuestionIndex|
| --- | --- |
| 哪位總統在1979年實現中美建交? | 2 |
跟蹤“下一題”按鈕的第二次點擊。現在currentQuestionIndex已經遞增到3,會發生什么呢?繼續閱讀之前,細心地檢查一下,看你能否跟蹤正確。
在if測試中,currentQuestionIndex的值(3)的確≥列表QuestionList的長度(3),于是currentQuestionIndex的值被設為1,第一題被寫入label,如表15-4所示。
**表15-4 “下一題”按鈕被第二次點擊時的值**
| QuestionLabel.Text | currentQuestionIndex |
| --- | --- |
| 哪位總統在大蕭條時期實施了“新政”? | 2 |
我們的跟蹤揭露了一個錯誤:最后一題永遠也無法顯示!
通過類似的跟蹤,最終使你成為一名程序員、工程師。你開始從機制上去理解編程語言,掌握代碼中的語句和詞匯,而不是對一些片段的模糊理解。誠然,編程語言是復雜的,但機器對每個“詞”都有明確而且簡單的解釋,如果理解了塊與變量或屬性變化之間的對應關系,也就理解了如何編寫或修復你的應用,當然也就實現了對應用的完全控制。
現在如果你告訴朋友們,“我正在學習如何讓用戶點擊‘下一題’按鈕來看到下一道題,這實在是太難了!”他們會以為你瘋了。但這個過程的確很困難,困難不在于概念的復雜性,而在于你不得不有意讓自己的腦子慢下來,來搞清楚計算機的每一步處理過程,包括那些你的大腦下意識完成的過程。
## **應用的調試**
逐步跟蹤不僅是理解編程的方法,同樣在調試有問題的應用時,也是一個屢試不爽的方法。
像App Inventor這樣的開發工具(通常被成為交互式開發環境,或IDEs-Interactive Development Environments)一般會提供了一種調試工具,相當于紙筆跟蹤記錄的高科技版本,能夠自動完成某些跟蹤過程,這極大地改善了應用開發的進程。這些工具提供了一個描述正在運行的應用的視圖,程序員可以在其中:
* 在任何一點暫停應用來檢查其中的各個變量及屬性;
* 單獨執行某些指令(塊)來檢查它們的執行效果。
### **監視變量**
說明:監視變量是AI1(App Inventor version1.0)中的功能,目前尚未在AI2中實現。
### **單獨測試塊**
除了可以用監視功能來檢查應用運行過程中變量及屬性的變化,還有另一個工具“Do It”,可以讓你脫離開程序通常的運行順序,單獨測試某些塊的運行。右鍵點擊一個塊,在快捷菜單中選擇“Do It”,這個塊就會開始執行,如果這個塊是一個有返回值的表達式,App Inventor將在塊的上方的方框內(在注釋塊中插入兩行)顯示返回值。如圖15-4及15-5。

**圖 15-4 右鍵點擊事件處理程序中的任何一個塊,會彈出快捷菜單**

**圖 15-5 在快捷菜單中選擇“Do It”,可以執行該塊,并查看返回值(如果有)**
“Do It”在調試塊的邏輯錯誤時非常有用。還是回到“總統測試”例子中的NextButton.Click事件處理程序,并假設程序中存在邏輯錯誤,無法瀏覽所有的問題。調試過程需要在開發環境及測試設備上同時進行。在用戶界面上點擊“下一題”按鈕,然后回到塊編輯器查看是否每次點擊都顯示了適當的問題。也可以監視變量index在每次點擊時的變化。
但是這類測試只允許檢查整個事件處理程序的執行效果,在運行完所有的塊之前,你無法檢查你要監視的變量或用戶界面。(抓不到逐句的中間狀態)
“Do It”允許你減緩測試過程,并檢查任何一個塊執行完成后的整個應用的狀態。一般是從用戶界面上的事件開始跟蹤,直到發現問題所在。在發現無法顯示最后一題之后,你可能在用戶界面上點擊“下一題”一次轉到第二題,然后不再繼續點擊“下一題”,而是在塊編輯器中讓整個事件處理程序一步一步地運行。在NextButton.Click事件處理程序中,每次對一個塊使用“Do It”讓塊執行,如圖15-6中,先右鍵點擊第一行的塊(讓變量index遞增),并選擇“Do It”。

**圖 15-6 使用“DoIt”工具,每次只執行一個塊**
此時index的值變為3,應用停止執行——“Do It”只能使被選中的塊以及它所包含的子塊運行,這可以讓測試者檢查被監視變量以及用戶界面的變化。接下來,選擇下一行要測試的塊(if測試)并選擇“Do It”來執行該行,其中的每一步都能看到每個塊的執行效果。
### **使用“Do It”漸進式開發**
有一點需要強調,這種逐行執行指令的方式不僅僅適用于程序的調試,它同樣適用于開發過程中的隨時測試。例如,如果你寫了一個很長的公式來計算兩個GPS坐標之間的距離,你可能要分步測試這個公式,來驗證這些塊的使用是否正確。
### **啟用與禁用塊**
另一個有助于漸進式調試應用的方法是啟用或禁用某些塊,它允許應用中保留有問題的或未經測試的塊,并讓系統在運行過程中暫時忽略它們,然后充分調試那些啟用狀態的塊,而不必擔心那些有問題的部分。禁用塊很簡單,在塊上點右鍵,在快捷菜單中選擇Disable Block即可,被禁用的塊呈現為灰色,在應用運行時,這些塊被忽略;需要時,還可以重新啟用這些塊,方法是在塊上點擊右鍵并選擇Enable Block。
## **小結**
App Inventor的偉大之處在于它的易用性——可視化的特點讓你可以直接開始一個應用,而不必擔心那些低層的細節。但現實的問題是,App Inventor不可能知道你的應用要做什么,更不知道如何來做。盡管直接進入組件設計器與塊編輯器創建應用是件讓人著迷的事情,但這里要強調的是,花一些時間來思考并詳細、準確地設計應用的功能,是非常重要的。這聽起來有些煩,但如果你能聽取用戶的想法、創建原型、測試并跟蹤應用的邏輯,那么創建出精彩應用的目標指日可待。
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信