自打小学开始,人们就被告知,做题之前应该先审题。这道理虽然很浅显,但却无比重要。
然而正是由于这道理过于浅显,所以总免不了被轻视——甚至被遗忘。人类历史上发生过无数次因为轻视简单浅显的道理而招致失败或走弯路的事情。轻视看起来简单的真理,最后总是要受到惩罚的。在缺乏常识的时代更应该重视常识。
编程也是如此。人们往往以为编写程序代码才是最有难度的工作,而编程要解决的问题本身是否成立或合理却常常被忽视,认为那是显而易见的事。在软件工程中,需求分析在很长的历史阶段里都被错误地认为是最简单的一个步骤。直到近些年来,有研究指出,软件50%以上的缺陷是由于需求分析的错误造成的,编码错误引起的软件缺陷基本上只占全部缺陷的1/4左右。这时不少人才恍然大悟地发现:正确地提出问题比解决问题重要的多;因为如果没能正确地提出问题,就根本谈不上正确地解决问题。
道理容易懂,但要真正做到认真地对待需求分析,却是另一回事。因为这还需要训练有素的职业修养和职业习惯。绝对不是仅仅在思想上认识到需求分析的重要性,就能养成好的职业习惯;良好习惯的培养需要大量的自觉的自我训练。只有经过不断反复的练习才能形成良好的习惯。
这种训练越早越好。一旦开始学习编程,就应该开始主动地进行良好编程习惯的培养和训练,而不是等到学习软件工程时才开始培养这种意识。
在学习编程时,养成良好编程习惯方法之一就是通过抄题来主动地训练自己。在抄题的过程中注意审查问题是否明确、合理。这样做的另一个好处是,阅读你代码的人能够清楚地了解这段程序要解决的是什么问题。
下面通过一个例子,来初步地看一下应如何审题:
“输入两个数,求其最大公约数”
在这里,问题要求输入的两个数是什么样的数就不明确。是整数?分数?实数?复数?这样的问题其实是无法解决的。因为问题本身就不周密、不严格。
抄题时还可以思考对程序的具体要求,明确程序的功能。如果需要也可以自己进一步明确写出。网上流传的一套所谓的“C语言经典一百例”,其中的第一题就是一个很好的反例:
【程序1】
题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?
1.程序分析:可填在百位、十位、个位的数字都是1、2、3、4。组成所有的排列后再去掉不满足条件的排列。
2.程序源代码:
main()
{
int i,j,k;
printf("\n");
for(i=1;i<5;i++) /*以下为三重循环*/
for(j=1;j<5;j++)
for (k=1;k<5;k++)
{
if (i!=k&&i!=j&&j!=k) /*确保i、j、k三位互不相同*/
printf("%d,%d,%d\n",i,j,k);
}
}
抛开这段代码存在的许多其他问题不谈,问题中的“能组成多少个互不相同且无重复数字的三位数”显然被编程者所忽视了。这样的代码没有什么价值。
因此强烈建议在写代码之前先抄一遍题目。
可能有人觉得题目无法被编译,没关系,这可以成为极好的注释。从某种意义上来说,写注释比写代码更重要。注释虽然不能解决问题,却极其有助于正确地解决问题或更容易地找出及改正错误。
以下给出第一章习题7(21页)的完整编程过程:
0.描述程序功能
/*
Description: 输出下面图案
/\
/ \
/ \
/ \
/________\
*/
1.写出main()
很多初学者从头到尾一个字一个字地写代码,这种写代码的方式非常糟糕。
就如同盖房子要先搭好脚手架一样,写代码也应该先搭建好必要的框架。通常,这种框架就是main()。这个道理所有的建筑工人都懂得,但许多程序员却不懂得。
应当把写代码看成是按照不同的方式填充、完善、完成main()的过程——每一个程序都是如此,就如同装修房屋一样。房屋总是先建好再装修的。还不至于有人愚蠢到边建房子边装修的吧?
/*
Description: 输出下面图案
/\
/ \
/ \
/ \
/________\
*/
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
system("PAUSE");
return 0;
}
至此,应该编译一下。如果代码有错误,越早发现越好。不少初学者急于事功,喜欢带“病”前行,在全部代码完成后再试图排除BUG。这种做法很傻。因为增加了调试难度,工作量可能增加几百倍甚至更多。
应该养成每写一两代码就立刻编译一下的好习惯,这种习惯越是初学者意义越大。就像攀岩一样,要始终保持立足于坚实正确的基点之上,这样才能从胜利走向更大的胜利。试图通过匆忙的、一蹴而就的方式完成程序是不切实际的幻觉。
2.填充main()
编译通过后,开始考虑逐步添充main()。
程序的的功能是在标准输出设备上输出5行字符,输出字符序列的功能可通过调用printf()函数实现。
/*
Description: 输出下面图案
/\
/ \
/ \
/ \
/________\
*/
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
printf("\n"); //依然是先写出必要的部分
printf("\n"); //通过复制粘贴完成
printf("\n"); //通过复制粘贴完成
printf("\n"); //通过复制粘贴完成
printf("\n"); //通过复制粘贴完成
system("PAUSE");
return 0;
}
编译通过后再进入下一步骤。
3.逐步细化
现在,最初抄写的题目部分有了更多的用处。把相应的部分复制粘贴到printf()函数调用的""之内。
不少初学者不注意使用“编辑”功能,这很不专业。应尽量使用编辑器中的“编辑”功能,来提高编程效率。
/*
Description: 输出下面图案
/\
/ \
/ \
/ \
/________\
*/
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
printf(" /\ \n"); //依然是先写出必要的部分
printf(" / \ \n"); //通过复制粘贴完成
printf(" / \ \n"); //通过复制粘贴完成
printf(" / \ \n"); //通过复制粘贴完成
printf(" /________\ \n"); //通过复制粘贴完成
system("PAUSE");
return 0;
}
编译。这时编译器可能会给出警告。
4.完善
由于""内的“\”是转义字符,无法直接写出。应将""内的“\”改写成“\\”。这应该通过“查找”“替换”来完成。替换过程中应注意有些“\”替换而有些“\”不替换。
熟练应用IDE的编辑功能,可以提高写代码的效率和正确性。
/*
Description: 输出下面图案
/\
/ \
/ \
/ \
/________\
*/
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
printf(" /\\ \n"); //依然是先写出必要的部分
printf(" / \\ \n"); //通过复制粘贴完成
printf(" / \\ \n"); //通过复制粘贴完成
printf(" / \\ \n"); //通过复制粘贴完成
printf(" /________\\ \n"); //通过复制粘贴完成
system("PAUSE");
return 0;
}
至此,代码完成。编译,运行程序并检查运行结果是否正确。
忠告:
程序员不但应当完成正确的代码,而且更应当用正确地方式完成代码。遵循良好的工序是代码质量的重要保证,程序员的聪明才智则不是。