第三節 程式合並
我所見過的各種組合程式雖不算多,但至少有百余個了。毛病最多的當然是缺乏完整的規劃,其次則是信馬游缰,一份不折不扣的流水帳!明明大門口在東邊,程式硬要朝西,直到游完了大觀園,天黑了,才出東門!
這種程式我收集了一大疊,可是舉來做例子,卻心有余而力不足。原因無他,實在不耐煩照抄一遍,一見到就頭痛!
電腦最強的功能,便是處理繁雜重復的工作,為什麼一般程式師居然存心與電腦爭風吃醋呢?不說別的,光把程式輸入到電腦中,就要花上幾個月寶貴的光陰,真值得這樣做嗎?
有一份程式,足足有四十多頁,我只略作調整,便縮小到十頁,處理速度則快了五倍。為什麼會差這樣遠呢?很簡單,有些人不喜歡用大腦,久而久之,習慣成自然,大腦就生了鐵銹!除了等因奉此,什麼都不會想了。
要想做一個優秀的程式師,第一個條件是不能偷懶,第二個條件則要有分析觀察的習慣,第三個也是最重要的,則是要有追求完美的精神。程式師要像藝術家,不論是自己的或是別人的程式,都要一而再、再而三地玩味改良。
我曾見過一個掃地的婦人,她不管在哪裡,見不得有任何髒亂。這種人才值得尊敬,這種精神是偉大的,與她的職業絲毫無關!
程式寫得不夠精簡,有三個原因,第一個是程式師無能,這種程式能夠寫完,可以運行,已算相當難得了;第二個原因是不懂技巧,硬橋硬馬的干, 不知什麼是效率,也不知道如何達成。自己寫的程式都不見得看得懂,遑論他人的?第三則是根本缺乏敬業精神,敷衍塞責,這種人我最瞧不起。
寫程式之初,如果把任務瞭解清楚,然後分析因素,分割模組。所有類似的情況都合並到一處,再以變數代替,統一執行。這原本是份內的工作,前述的情況根本不可能發生!
問題是發生了以後怎麼辦呢?我建議最好重寫,如果一定要改,只好采用程式合並的技巧,濃縮一下。
合並的目的是為了增進效率,而合並的方法則因情況不同而異,就像人生了病,必須先查出病因,否則無法下藥。我試著以所知道的一些例證,簡要地解說如後。
一、過程的合並:
要做過程的合並,首先要查明下列各點:
1,首先找出過程類似的,全部移到一堆,如果找不到,那就沒救了。
然而,這種程式要就是太小,根本不可能有類似的情況,再不就是寫作時雜亂無章,信馬游缰。分明有類似的過程,但沒有共通的原則,無從濃縮。當然,也可能有些程式,因工作量及處理的細節太多,以致無法濃縮。
2,在類似的程式中,找尋相異的指令或流程,再若沒有,那就是重復了,正宜合並。
3,把相異的指令或流程用變數取代,或將不同程式之入口放在暫存器裡。
4,將各程式在應用該流程前,設好變數及使用的暫存器。
5,合並相似的程式段,不同處應用變數取代之。
下面舉一實例,系一繪圖程式之片斷,茲改變原用標題,並將分散在各處若干不同之段,列述如下:
189: MASK PROC NEAR
190: MOV DX,3C4H
191: MOV AL,2
192: OUT DX,AL
193: MOV DX,3C5H
194: MOV AL,PCOLOR
195: OUT DX,AL
196: RET
197: MASK ENDP
…
380: MOV DX,03CEH
381: MOV AL,3
382: OUT DX,AL
383: MOV AL,18H
384: INC DX
385: OUT DX,AL
386: RET
…
490: MOV DX,3CEH
491: MOV AL,3
492: OUT DX,AL
493: MOV DX,3CFH
494: MOV AL,0H
495: OUT DX,AL
496: RET
…
589: CROSS PROC NEAR
590: MOV DX,3C4H
591: MOV AL,2
592: OUT DX,AL
593: INC DX
594: MOV AL,0FH
595: OUT DX,AL
596: RET
597: CROSS ENDP
…
這樣的段落有十多處,看來每個都略有不同,似乎不能合並。然而仔細分析,顯然是程式師訓練不夠,把一個非常有規則的程式,安排得非常紊亂,以致到這個地步。
我們先歸納問題,決定如何合並。第一,上述各段程式,應該統一作為子程式;第二,全部變數只有四個,其中兩個是傳送值,兩個是輸出入埠。後者有連續關系,等於只有一個。因此,在調用此子程式前,應先令DX為輸出入埠,再將變數裝入AX中,一次調用即可。此子程式如下:
300: SUB:
301: OUT DX,AL
302: INC DX
303: MOV AL,AH
304: OUT DX,AL
305: RET
這樣簡短的子程式,有無必要,端視時空的效益而定。不論怎樣整理,都遠比原來的要好。
另外有種情況,更為可怕,就是在鍵盤輸入後,用流程方式,一一比較輸入碼,再一一分別處理。
比如說,為了檢查游標鍵的左、右、上、下等八個方向的移動,以便作相應的處理,程式居然寫成:
100: PP1: MOV AH,0
101: INT 16H
102: CMP AX,4800H ;↑鍵
103: JNE NEXT1
104: CALL MOVDATA ;SET BUFFERS
105: CALL SETDLT ;SET INCREMENT
106: NXT01:
107: CALL DOTUP
108: LOOP NXT01
109: CALL XORDOT ;SET NEW DOT
110: CALL XYDISP ;DISP NEW XXX,YYY
111: JMP PP1
112: NEXT1:
113: CMP AX,5000H ;↓鍵
114: JNE NEXT2
115: CALL MOVDATA ;SET BUFFERS
116: CALL SETDLT ;SET INCREMENT
117: NXT02:
118: CALL DOTDOWN
119: LOOP NXT02
120: CALL XORDOT ;SET NEW DOT
二、分支的處理:
分支是程式中不可避免的手段,使用得好,整個程式氣勢一貫,有行雲流水之妙。
前面的例子根本不具分支的條件,故不能算是分支不良,而是程式師觀念錯誤。
下面再舉一例,由於分支不良,以致程式支離破碎。這是一則計算拋物線的快速程式,妙在沒有用乘除法,也沒有任何函數。其中有幾段是這樣的:
100: BEG00:
101: CMP BP,BUFY
102:? JLE BE7
103: OR CX,CX
104: JG BE20
105: MOV AX,BP
106:? SHL AX,1
107: DEC AX
108: JL BE10
109: BE2:
110: CALL BE1
111: JC BEG00
112: CALL BE3
113: JMP BEG00
…
120: BE14:
121:? LODSW
122: CMP AH,1FH
123: JGE BE141
124: LOOP BE14
125: POP DI
126: POP CX
127: MOV SI,DI
128: JMP BE142
129: BE141:
130: POP DI
131: POP CX
132: MOV SI,DI
…
150: BE10:
151: CALL BE1
152: JMP BEG00
153: BE20:
154: MOV AX,CX
155:? SUB AX,DX
156: SHL AX,1
157: DEC AX
158: JLE BE2
159:? CALL BE3
160: JMP BEG00
161: BE1:
162:? INC DX
163: ADD CX,DX
164: ADD CX,DX
165: INC CX
166: ADD DI,BUFX
167: CMP DI,BX
168: JLE BE1RET
169: CALL BE01
170: SUB DI,BX
171: BE1RET:
172: RET
…
190: BE01:
191:? MOV AL,1
192: CMP [SI+1],AL
193: JNZ BE011
194: INC BYTE PTR [SI+1]
195: RET
…
200: BE141:
…
全部程式並不大,不過一百多條指令,但是稍加改進,卻可以省卻廿多條指令,速度也會加快。重點在於106 到113 的分支錯誤,以致於多出BE10 BE20 BE3 BE01等段程式出來。
照理,BE1 BE3 BE01都不該另設子程式,BE14也應改寫,如此,整個程式就完全不同了。
第四節 定案包裝
一、手冊:
手冊寫作本來與程式寫作無關,但由於一般程式師都不知道手冊的重要性,往往程式寫得極佳,而市場口碑卻不良,以致慘遭滑鐵泸之敗。
實際上,當今市場的趨勢,都傾向於螢幕提示,以致於手冊僅具輔助作用,幫助使用者理解各種功能的發揮而已。
問題就出在這裡,一個功能的介紹、說明,與該功能應用的發揮,完全是不同的層次。「螢幕提示」經常由程式師自行制作,而程式師對文字概念的應用及理解能力,往往並不太高明,其結果可想而知。
手冊應該有專人寫作,這種人既要對文字概念應用裕如,又要充份瞭解電腦的功能。難的是,培養一個程式師,了不起三個月到半年,而一個能達意的作家,起碼需要三至五年。遺憾的是,一般電腦公司沒有這種眼光,以為寫程式需要技術,手冊則隨便找人應付了事。
手冊的重要性,並非僅止於此,一個有價值的程式,一定有周詳的計劃,有制作的藍圖。這種計劃及藍圖,經過文字概念上的整理,應該就是手冊本身。換句話說,有良好規劃的程式,必然是先有手冊作為藍圖,再根據手冊制作程式。
二、版本:
程式完成以後,除非一些特殊的原因,只要有實用價值,必然需要不斷改進、強化。
這一來就面臨版本更新的問題,程式師在制作之初,必須事先考慮周全。不要希望一次把程式寫得盡善美,完整無缺,不僅那是不可能的夢想,也是自找麻煩。
任何一個人,即使是不世天才,也不可能經歷人間所有的事件。而程式所需要適應的范圍,則是動態的、隨著人的知識及經驗不斷增長。因此,一個嶄新的程式一旦問世,就成為人世間的新生事物,人的經驗擴展後,新的需求即接踵而至。剛剛完成的程式,在完成的那一剎,就已成為過去式。
所以在程式規劃時,必須高瞻遠矚,考慮得越是周全,程式的生命力越是旺盛。同時,在另一方面,程式必須交到使用者手中,才有實際的價值。是以如何在周全的規劃,和盡快的完成工作之間,作有效的斟酌取捨,則是個難題。
解決的方法之一,就是利用「版本」觀念,將產品分為數個時期。這樣,不僅產品可以很快地交到使用者手中,而且使用者可以提供其應用經驗的回饋,更有利的,是程式得以不斷地增長、成熟、完善。
有了版本的觀念,還需要對版本的制作有明確的計劃,每一個版本的檔案維護,修訂更正,都要有專人負責。否則,當已經上市的版本還需要修改,而新的版本業已開始設計,若是一個不小心,分不清檔案屬於哪個版本時,其後果之不堪,將非局外人所能領會的了。
三、包裝:
此處所提的「包裝」,不是商業上所謂的如何將產品美化偽裝起來。而是指一個程式交到使用者手中時,應該具備哪些必備的,哪些選用的「配備程式」。
一般大型的應用程式,經常提供很多片磁盤,要先執行一個很復雜的「初始化」程式,才能使用。如果采用組合語言制作,其目的本就是為了節省空間。空間小了,應該可以避免這種多余的手續。
這就是包裝所要考慮的問題,比如說,在我們的“聚珍整合系統”中,附有如下一些配備程式及手冊:
1,功能、操作提示或手冊:
1-1 sm.hlp:在功能提示態下,說明各功能、操作方式及注意事項。
1-2 smvqoq.exe:聚珍整合系統操作手冊閱覽程式
smvqo1.dat--smvqod.dat :操作手冊資料檔。
2,smjooh.exe:繁、簡體檔互轉程式
smjooh.tab:繁、簡體轉換對照表。
3,smjopa.exe:本系統與park文書檔資料互轉用。
4,smjob5.exe:為轉換其他系統生成的文書檔資料用。
5,smjib5.exe:轉換dbase iii 資料檔。
這些程式及檔案,都要放在同一片磁盤中,不僅為了方便省事,也可降低成本。
在我們的經驗中,這些工作說來容易,做來卻大費周章。唯有在事先做好妥善的規劃,最後才能省時省事,達到理想的預期效果。
僅以螢幕提示為例,由於資料所占空間太大,就導致了極大的困難。如果事先有准備,將資料作適當的壓縮,顯然會省卻不少麻煩。
此外,手冊的印刷,磁盤的復制,所有一切應行考慮的,都要事先想清楚。要知道,一個應用軟件,其成本完全在開發及最後的包裝過程,為了成功,代價是必須先付出的。