作者:不详 来源:互联网   酷勤网收集 2007-11-22

摘要
  程式写完后,还要加工成为可执行的套装软件(Package),一般说来,即使是可以执行的程式,一点错误都没有,离套装软件的程度,却还有一段距离。当然,程式侦错也是必经过程之一,有时侦错与程式写作可以同时进行。

程式写完后,还要加工成为可执行的套装软件(Package),一般说来,即使是可以执行的程式,一点错误都没有,离套装软件的程度,却还有一段距离。
  当然,程式侦错也是必经过程之一,有时侦错与程式写作可以同时进行。但有经验的程式师,对全面有了充份的认识,往往会等到程式联接后再行侦错。
  程式完成后的全面侦错,最好不要依靠写程式的人。因为程式师经常不是使用者,他们仅在自己设计的条件下,依其理念进行侦错。当然这种错误必须更正,但最容易发生的错误,却是使用者不小心在输入时,或运用指令时,违背了程式师的理念。这种错误的发生,是不能原谅的,程式本来就是为使用者设计的,如果令使用者不便,程式就失去了应有的价值。
  程式的品管,就在于检测程式是否符合使用者的需求。一般说来,应有专人负责,也有让写作手册的人,兼做品管的工作。这样可以同时对照手册所描述的功能及操作方法,检查两者之间是否一致。
  还有一种常见的品管方式,是在产品完成时,交给完全没有参与程式设计的第三者,在客观的立场,作全面的试用,并提出测试报告或建议书。
  品管合格了,才是包装、手册等最后的工作。这并不是说包装和手册要最后才做,相反的,尤其是使用手册,经常要在程式设计的同时,准备妥当,如此程式师才不会任意所之,脱离主题。

第一节 测试侦错

  不论使用什么工具,侦错时一定要有清晰的头脑,根据所设定的方式,一步一步地查看流程及指令。
  每个人都会有独特的习惯性错误,最好每次将自己发生的错误记下来,不仅错误会渐渐减少,且在错误发生时,很容易就能找到。
  测试侦错是非常重要的手段,有人认为第一流的程式师不应该犯错,即使犯错,也错得很少。我的看法不一样,并非因为我经常犯错,而且错得离谱。真正的理由是为了适时掌握正确的思考方向,在编程时,有意无意地忽略一些细节,这样反而能一气呵成,不致于再而竭三而衰。
  这就像画图一样,有人喜欢先作草图,有人则习惯由细部画起。不论个人的风格如何,重要的是最后的成果。
  我在写程式之前,首先考虑全体的结构,再把各处的支架备妥,然后考虑有共同特性之处,便一口气写完。而且写时力求快速,以免失去当时的感觉。等到结构大体完成了,最后才去填补一些不太重要的细节。至于正确与否,则全靠测试侦错来修正、弥补。
  这种写法要有很强的整体观念,且对每段程式的性质及功能皆瞭解从何下手。
  我由系统程式到应用工具,大大小小的程式写了不少,对这种写作方法有很深的体会。唯一的缺点是,程式如果太大,超过20KB,我的记忆力就难以负担。(年轻人或许不致如此)但其优点则是结构精简,制作时间极短。以我们的中文系统程式而言,8KB 的程式,连侦错在内,只花了两个月的功夫。
  然而,在训练程式师的过程中,我发现到还是循序渐进较好。每次写完了一段程式,立刻侦错,此段程式正确了,再写下一段。除非有绝对的把握,自信没有问题,否则千万不要等全部程式都写完了,再来调试。到那时,如果发现问题,在各段错综复杂的程式中,要找到错误所在,那是大海捞针了。
  程式发生「当机」的情况,常常是 PUSH 及 POP不平衡所致,也有在做回路时,计数器为负值,以致锁在其中。如果程式分「段」太多,则要特别注意各「段」改变的情况。
  诸如这些细节,最好随时把编程时的假想值记录下来,不要太过相信自己的记忆力,时间一长,程式一大,就什么都忘记了。
  还有一点,在侦错时千万要养成习惯,记录追踪的过程。因为侦错是一种很琐碎的工作,很难一次就发现问题所在,第一次应该是第二次改进的经验。如果不详加记录,每次都要从头做起,将是一种极为痛苦的事。
  不妨把侦错视为猜谜,一种智力的挑战,在遵守一定的规则下,应该是一种有趣的享受。

第二节 研究改进

  想要把程式写好,一定要不断地研究、改进,由错误中学习,由改进中得到经验,培养出敏锐的观察能力和良好的写作习惯。
  在开始时,这种过程需要付出不少时间,但对一位程式师来说,写程式是终身职业,能不精益求精吗?
  以下举两个实例,以说明如何研究改进已完成的程式。
 1,指令的运用:
  以下面这段通讯处理程式而论,不仅语法及指令完全正确,执行时也毫无错误,是不是还可以加以改进呢?
  1-1 按照前面规定,说明项中已用简化的字串:
  SND-传送 RCV-接收 LET-左 
    RGT-右  VER-直  HOR-横
  1-2 程式师代号为'C'。
  1-3 段名省略。

  1: CSND0:
  2:    MOV  DX,03FDH   ; 输出埠
  3:    MOV  AL,80H
  4:    OUT  DX,AL     ; 输出指令
  5:    MOV  DX,03F8H   ; LSB 速度控制
  6:    MOV  AL,06H     ; 速度=19200/秒
  7:    OUT  DX,AL
  8:    MOV  DX,03F9H   ; MSB 速度控制
  9:    MOV  AL,0     ; 速度=19200/秒
  10:    OUT  DX,AL
  11:    MOV  DX,03FBH   ; 行控制暂存器
  12:    MOV  AL,03H     ; NO PARITY,1
                   ; STOP,8
  13:    OUT  DX,AL
  14:    MOV  DX,03FCH   ; 通讯控制
  15:    OUT  DX,AL
  16:    MOV  DX,03F9H   ; 中断有效
  17:    MOV  AL,0
  18:    OUT  DX,AL
  19: CSND1:
  20:    MOV  DX,03FDH   ; 状态暂存器
  21:    IN  AL,DX
  22:    TEST  AL,10H     ; 是否可接收?
  23:    JNZ  CRCV0     ; 可
  24:    TEST  AL,20H     ; 通道已清否?
  25:    JZ  CSND1     ; 8250未清
  26:    MOV  AH,1     ; 键盘有输入?
  27:    INT  16H
  28:    CMP  AL,07H     ; ='CTRL+G'
  29:    JE  CEND     ; 是,完毕
  30:    MOV  DX,03F8H
  31:    OUT  DX,AL     ; 送输入字符
  32:    JMP  CSND1
  33: CRCV0:         ; 接收
  34:    MOV  DX,03FCH   ; 通讯控制
  35:    MOV  AL,08H     ; 暂停中断
  36:    OUT  DX,AL
  37:    MOV  DX,3F8H
  38:    IN  AL,DX     ; 收字符
  39:    MOV  AH,0EH
  40:    INT  10H     ; 萤屏显示
  41:    MOV  DX,03FCH
  42:    MOV  AL,0BH
  43:    OUT  DX,AL     ; 继续接受
  44:    JMP  CSND1     ; 循环工作
  45: CEND:
  46:    RET       ; 完成

  本段程式共 84 个字元,非常精简,但仍然有节省的余地,要点在DX的数值上。
  DX值由 03F8H到 03FDH,可知 DH 之值不变,只需改变 DL 即可。每改变DX一次,需要三个字元,如仅变DL,只需两个字元。这一指令共用了十一次,除第一次有必要外,其他十次就可以省下10个字元。
  再要斤斤计较,还可以榨出二个字元来,在5至8条中,若用INC DX 只需要一个字元。

  此外,31,32及43 ,44是浪费的作法,只要在第18条加一标号,就可以省却两个字元输出的指令。另外,还有35及39两条指令,应该合并,一次即将AX设妥,于是,又省下了一个字元。
    先令 DH=3
  1: CSEND0:
  2:    MOV  DL,0FDH    ; 输出埠
  3:    MOV  AL,80H
  4:    OUT  DX,AL     ; 输出指令
  5:    MOV  DL,0F8H    ; LSB 速度控制
  6:    MOV  AL,06H     ; 速度=19200/秒
  7:    OUT  DX,AL
  8:    INC  DX     ; MSB 速度控制
  9:    SUB  AL,AL     ; 速度=19200/秒
  10:    OUT  DX,AL
  11:    MOV  DL,0FBH    ; 行控制暂存器
  12:    MOV  AL,DH     ;NO PARITY,1
           ; STOP,8
  13:    OUT  DX,AL
  14:    INC  DX     ; 通讯控制
  15:    OUT  DX,AL
  16:    MOV  DL,0F9H    ; 中断有效
  17:    SUB  AL,AL
  18: CSNDA:
  19:    OUT  DX,AL
  20: CSND1:
  21:    MOV  DL,0FDH    ; 状态暂存器
  22:    IN  AL,DX
  23:    TEST  AL,10H     ; 是否可接收?
  24:    JNZ  CRCV0     ; 可
  25:    TEST  AL,20H     ; 通道已清否?
  26:    JZ  CSND1     ; 8250未清
  27:    MOV  AH,1     ; 键盘有输入?
  28:    INT  16H
  29:    CMP  AL,07H     ; ='CTRL+G'
  30:    JE  CEND     ; 是,完毕
  31:    MOV  DL,0F8H
  32:    JMP  CSNDA     ; 送输入字符
  33: CRCV0:         ; 接收
  34:    MOV  DL,0FCH    ; 通讯控制
  35:    MOV  AX,0E08H   ; 暂停中断
  36:    OUT  DX,AL     ; 及显示
  37:    MOV  DL,0F8H
  38:    IN  AL,DX     ; 收字符
  39:    INT  10H     ; 萤屏显示
  40:    MOV  DL,0FCH
  41:    MOV  AL,0BH
  42:    JMP  CSNDA     ; 循环工作
  43: CEND:
  44:    RET       ; 完成
  看来似乎这样太小气,可是所谓艺术,就要具备丝毫不苟且的态度,再说由84个字元变成66个字元,省了近百分之廿,而且,速度也快了。这种程式原本就很精简,只有训练有素,追求完美的程式师,才做得到。
  另一种做法,便是将重复的过程写成回路,约可节省廿几个字元。但是,由于时间定律限制,通讯程式颇重时效,回路是否值得,尚要多方面分析,不可轻率决定。

 2,回路的实例:
  前面曾经讨论过,程式的效率,经常决定于回路的处理方式及其技巧。其对空间上影响比较小,但是良好的设计理念,常使速度上有高达十倍,甚至百倍的差异,读者想必已经知道,但是如何能应用已知的技巧,来改进设计的程式呢?
  回路是利用计数器,反复进行相同的程序作业,这种程式,目的就是为了节省空间,相对地,时间上难免有所损失。
    因此,在设计回路时,必须先行考虑清楚: 空间的节省与时间的交换是否值得? 其次,则要充份掌握回路的特色,要用得恰到好处,不可掉以轻心。
    原则上,在回路中,指令要用得精简,流程要非常明确,尤其重要的是,应力求避免在回路中使用缓冲器,最好充份利用暂存器。如果时间效率极为重要,则不妨放弃回路方式。
    有一个显示程式,目的是要将 16*16点阵字形送到萤幕上。对象是Hercules 640*400的图形卡,计分四区交互传送,这是另一个「高科技」界的新鲜奇事,在IBM PC推出时,最高密度的图形态,只有 640X200点阵,那是迁就电视萤幕的扫描方式,先送单线的水平讯号,再送双线,故分两区。Hercules卡为了加高密度,应用Interlace 技术,又在单双水平扫描线中各加了一行,遂成了四区。
    Hercules很适宜中文的显示,如用 16X16字形,正好显示25行,每行40字,与英文完全兼容。若希望有一状态显示栏,则可用 15X15字形,留出24条线供做状态栏。
    遗憾的是在最需要中文的国内,却偏爱CGA,EGA 等密度不足的显示设备。不但售价偏高,功能也不足,弄得不伦不类。
    最理想的还是VGA 显示,计有 640X480之萤幕点阵,不仅空间大,在记忆体中,只有一区,应用非常灵活。
    下面,我们先介绍 Hercules 的显示方法,同时探讨回路的处理方式。
  1: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  2: ;HERCULES 中文显示处理程式。      ;
  3: ;输入参数:SI=点阵字形,DI=萤幕位置。    ;
  4: ;     DS =CG,ES= 0B800H(萤幕段)。   ;
  5: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  6: CDSP0:
  7:     MOV   CX,16    ;高16点
  8: CDSP1:
  9:     MOVSW      ;移至萤幕上
  10:     ADD   DI,1FFEH    ;加一区,每区=2000H
  11:     JNS   CDSP2    ;未超越区限,继续
  12:     ADD   DI,8050H    ;超越区限,换区加行
  13: CDSP2:
  14:     LOOP  CDSP1
  15:     RET
  程式到此结束,相当精简,技巧在第10至12条区限的检测方式。一般做法是在检查区限时,用:
     ADD   DI,1FFEH    ; 加区值
     CMP   DI,8000H    ; 最大区限值
     JB   CDSP2    ; 未超过
     SUB   DI,8000H    ; 减去区限
  如此则多了一条4字元的指令,加上4个时钟脉冲,做16次回路就损失64个时钟脉冲值。在全萤幕显示时,以1,000 个字来算,为数就不少了。
  当然,取消了回路速度还可以加快,其结果,则要增加130 个字元,时间则快了 272个时钟脉冲,是否值得,就要看实际需要而定了。
  另一个方法,要增加2个字元,但可快上36个时钟脉冲,其法在第11条上:
  11:     JS   CDSP3
  12:CDSP2:
  13:     LOOP  CDSP1
  14:     RET
  15:CDSP3:
  16:     ADD   DI,8050H
  17:     JMP   CDSP2
  再换一个方法,如果先使 BX 为1FFEH,DX为8050H,则在原程式中,将第10条及12条分别改为:
  10:     ADD   DI,BX
  12:     ADD   DI,DX
  这一来,时钟脉冲快了2个,16次则快更多,如果再加上取消回路,其意义更大。空间原增加 130字元,现仅94字元,时间则省下 304个时钟脉冲。如果全萤幕显示了1,000 个字,在8MHZ频率下,将会加快 1/25 秒的速度。
  在回路中,如果讲求时间效益,应极力避免使用PUSH及POP ,因PUSH需15个时钟脉冲,而POP 则要12个,两者相加是27个时钟脉冲,非常不值得。
  解决方法之一是:设法将欲保留之值贮存在没有用到的暂存器中;再若是固定的常数,也不妨在每次要用时重新置入,祇不过是4个时钟而已。最麻烦是变数值,除了在设计模组之前,妥当地安排外,别无良策。

第三节 程式合并

  我所见过的各种组合程式虽不算多,但至少有百余个了。毛病最多的当然是缺乏完整的规划,其次则是信马游缰,一份不折不扣的流水帐!明明大门口在东边,程式硬要朝西,直到游完了大观园,天黑了,才出东门!
  这种程式我收集了一大叠,可是举来做例子,却心有余而力不足。原因无他,实在不耐烦照抄一遍,一见到就头痛!
  电脑最强的功能,便是处理繁杂重复的工作,为什么一般程式师居然存心与电脑争风吃醋呢?不说别的,光把程式输入到电脑中,就要花上几个月宝贵的光阴,真值得这样做吗?
  有一份程式,足足有四十多页,我只略作调整,便缩小到十页,处理速度则快了五倍。为什么会差这样远呢?很简单,有些人不喜欢用大脑,久而久之,习惯成自然,大脑就生了铁銹!除了等因奉此,什么都不会想了。
  要想做一个优秀的程式师,第一个条件是不能偷懒,第二个条件则要有分析观察的习惯,第三个也是最重要的,则是要有追求完美的精神。程式师要像艺术家,不论是自己的或是别人的程式,都要一而再、再而三地玩味改良。
  我曾见过一个扫地的妇人,她不管在哪里,见不得有任何脏乱。这种人才值得尊敬,这种精神是伟大的,与她的职业丝毫无关!
  程式写得不够精简,有三个原因,第一个是程式师无能,这种程式能够写完,可以运行,已算相当难得了;第二个原因是不懂技巧,硬桥硬马的干, 不知什么是效率,也不知道如何达成。自己写的程式都不见得看得懂,遑论他人的?第三则是根本缺乏敬业精神,敷衍塞责,这种人我最瞧不起。
  写程式之初,如果把任务瞭解清楚,然后分析因素,分割模组。所有类似的情况都合并到一处,再以变数代替,统一执行。这原本是份内的工作,前述的情况根本不可能发生!
  问题是发生了以后怎么办呢?我建议最好重写,如果一定要改,只好采用程式合并的技巧,浓缩一下。
  合并的目的是为了增进效率,而合并的方法则因情况不同而异,就像人生了病,必须先查出病因,否则无法下药。我试着以所知道的一些例证,简要地解说如后。

一、过程的合并:

  要做过程的合并,首先要查明下列各点:
 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
 121:    CALL  XYDISP    ;DISP NEW XXX,YYY
 122:    JMP  PP1
 123: NEXT2:
 124:    CMP  AX,4B00H  ;←键
 125:    JNE  NEXT3
 …
  这段程式总共要检查八次,才能确定是否有游标移动以及哪个游标在移动。然后,还要一一检查其他变化,共有十八种有效码。我实在佩服这种程式师,不但有无比的耐性,还有非凡的想像力,居然能把一段极为简单平凡的程式,写得这样的精彩动人!
  如果是我,我会写得毫无趣味:
 100: PP1:  SUB  AH,AH
 101:    INT  16H
 102:    OR  AL,AL
 103:    JNZ  PP1    ;AL 非0无效
 104:    MOV  BH,AL
 105:    MOV  BL,AH
 106:    SUB  BL,47H    ;最小之字标键
 107:    JLE  PP1    ;非处理范围
 108:    SHL  BX,1
 109:    CALL  FUNC[BX]
 110:    JMP  PP1
  这是主流程,程式短,速度快,维护容易,一眼看过去,有什么错误立刻分明。
 …
 1000: FUNC  DW  NEXT02    ;↖
 1001:    DW  NEXT0    ;↑
 1002:    DW  NEXT04    ;↗
 1003:    DW  PPRET    ;无效
 1004:    DW  NEXT2    ;←
 1005:    DW  PPRET    ;无效
 1006:    DW  NEXT4    ;→
 1007:    DW  PPRET    ;无效
 1008:    DW  NEXT12    ;↙
 1009:    DW  NEXT1    ;↓
 1010:    DW  NEXT14    ;↘
 …
  因为这是子程式,加一段、减一段容易非常。
  即使是子程式,也有很大的考究,就以前段来说,在 104至110 之间,就值得三思。
 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
  首先,104 和105 会重复多次,109 及110 亦然,为什么不合并为一呢?这也是很常见的程式合并手法,两次调用合为一次,速度及空间都较为经济。
  在子程式 SETDLT 之前,先调用一次 MOVDATA,另XYDISP也是一样,首先备妥:
 3000: SETDATA:
 3001:    CALL  MOVDATA   ;假设本程式有他用
 3002: SETDLT:
 3003:     …
 …
 3100: XYDIDOT:
 3101:    CALL  XORDOT    ; 同上
 3102: XYDISP:
 3103:    …
 …
  再来设计NEXT0 的子程式:
 110: NEXT0:
 111:    CALL  SETDATA
 112: NXT01:
 113:    DOTUP 应搬至此,无需设为子程式。
 …
 120:    LOOP  NXT01
 121:    JMP  XYDIDOT   ; 如有必要,可先
                  ; 设好参数
  这样合并一下,效果决不止高上十倍,等到真正学会了程式的技巧,写作时速度也可以提高数倍。二、分支的处理:

  分支是程式中不可避免的手段,使用得好,整个程式气势一贯,有行云流水之妙。
  前面的例子根本不具分支的条件,故不能算是分支不良,而是程式师观念错误。
  下面再举一例,由于分支不良,以致程式支离破碎。这是一则计算拋物线的快速程式,妙在没有用乘除法,也没有任何函数。其中有几段是这样的:
 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也应改写,如此,整个程式就完全不同了。
  原来由 105为:
 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
  现改为:
 107: BE1:       ;原为DEC AX分支处理
 108:       INC  DX   ;原161子程式作主流程
 109:      ADD  CX,DX
 110:     ADD  CX,DX
 111:      INC  CX
 112:       ADD  DI,BUFX
 113:     CMP  DI,BX
 114:      JLE  BE11
 115: ;     CALL  BE01   ;本子程式重写如下:
 116:      CMP  BYTE PTR[SI+1],1
 117:       JNE  BE1A
 118:      INC  BYTE PTR[SI+1]
 119: BE1A:
 120:      SUB  DI,BX
 121:      JC  BEG00   ;原111
 122:      …     ;原BE3 程式
 …
  又 125条三个指令也是分支错误,白白浪费。
 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  ;127移到此
 133:      JNZ  BE142   ;128移到此
 134:       … 
  第四节 定案包装

一、手册:

  手册写作本来与程式写作无关,但由于一般程式师都不知道手册的重要性,往往程式写得极佳,而市场口碑却不良,以致惨遭滑铁泸之败。
  实际上,当今市场的趋势,都倾向于萤幕提示,以致于手册仅具辅助作用,帮助使用者理解各种功能的发挥而已。
  问题就出在这里,一个功能的介绍、说明,与该功能应用的发挥,完全是不同的层次。「萤幕提示」经常由程式师自行制作,而程式师对文字概念的应用及理解能力,往往并不太高明,其结果可想而知。
  手册应该有专人写作,这种人既要对文字概念应用裕如,又要充份瞭解电脑的功能。难的是,培养一个程式师,了不起三个月到半年,而一个能达意的作家,起码需要三至五年。遗憾的是,一般电脑公司没有这种眼光,以为写程式需要技术,手册则随便找人应付了事。
  手册的重要性,并非仅止于此,一个有价值的程式,一定有周详的计划,有制作的蓝图。这种计划及蓝图,经过文字概念上的整理,应该就是手册本身。换句话说,有良好规划的程式,必然是先有手册作为蓝图,再根据手册制作程式。

二、版本:

  程式完成以后,除非一些特殊的原因,只要有实用价值,必然需要不断改进、强化。
  这一来就面临版本更新的问题,程式师在制作之初,必须事先考虑周全。不要希望一次把程式写得尽善美,完整无缺,不仅那是不可能的梦想,也是自找麻烦。
  任何一个人,即使是不世天才,也不可能经历人间所有的事件。而程式所需要适应的范围,则是动态的、随着人的知识及经验不断增长。因此,一个崭新的程式一旦问世,就成为人世间的新生事物,人的经验扩展后,新的需求即接踵而至。刚刚完成的程式,在完成的那一剎,就已成为过去式。
  所以在程式规划时,必须高瞻远瞩,考虑得越是周全,程式的生命力越是旺盛。同时,在另一方面,程式必须交到使用者手中,才有实际的价值。是以如何在周全的规划,和尽快的完成工作之间,作有效的斟酌取舍,则是个难题。
  解决的方法之一,就是利用「版本」观念,将产品分为数个时期。这样,不仅产品可以很快地交到使用者手中,而且使用者可以提供其应用经验的回馈,更有利的,是程式得以不断地增长、成熟、完善。
  有了版本的观念,还需要对版本的制作有明确的计划,每一个版本的档案维护,修订更正,都要有专人负责。否则,当已经上市的版本还需要修改,而新的版本业已开始设计,若是一个不小心,分不清档案属于哪个版本时,其后果之不堪,将非局外人所能领会的了。

三、包装:

  此处所提的「包装」,不是商业上所谓的如何将产品美化伪装起来。而是指一个程式交到使用者手中时,应该具备哪些必备的,哪些选用的「配备程式」。
  一般大型的应用程式,经常提供很多片磁盘,要先执行一个很复杂的「初始化」程式,才能使用。如果采用组合语言制作,其目的本就是为了节省空间。空间小了,应该可以避免这种多余的手续。
  这就是包装所要考虑的问题,比如说,在我们的“聚珍整合系统”中,附有如下一些配备程式及手册:
 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 资料档。
  这些程式及档案,都要放在同一片磁盘中,不仅为了方便省事,也可降低成本。
  在我们的经验中,这些工作说来容易,做来却大费周章。唯有在事先做好妥善的规划,最后才能省时省事,达到理想的预期效果。
  仅以萤幕提示为例,由于资料所占空间太大,就导致了极大的困难。如果事先有准备,将资料作适当的压缩,显然会省却不少麻烦。
  此外,手册的印刷,磁盘的复制,所有一切应行考虑的,都要事先想清楚。要知道,一个应用软件,其成本完全在开发及最后的包装过程,为了成功,代价是必须先付出的。

分类: 汇编学习 嵌入式开发 黑客技术



关于酷勤 | 联系方式 | 免责声明 | 友情链接