程序设计箴言

       在静寂的空宇里,一种神奇的物质形成并诞生了。它立刻便静止了,独自守候着,毫无动静,然而又处于永恒的运动之中。它是所有程序的源头,我不知道它的名字,所以我将称它为编程之道。


       程序设计是计算机专业的核心内容,大学本科阶段的《程序设计语言》、《数据结构》、《算法分析》等课程正是围绕这一核心内容而设置的。但要精通程序设计却不仅仅是精通这几门课就能做到的。下面所给出的这二十条"程序设计箴言"涵盖了编程风格、性能、排错、测试等方面的内容。这些内容是一般书籍很少涉及的,但有时却会对程序设计起决定性的作用。这些箴言中的大多数来源于实践,是经验之谈,它们可以帮助程序员提高效率,编写出更优质的代码来。但值得说明的是,对于每一条箴言,必须亲身实践才能掌握其精髓,仅仅记住是毫无价值的。另外,每一条箴言都不是"放诸四海而皆准的",例外情况总是存在的。在实践中应该敢于打破成规,敢于创新,灵活运用。或许就像张无忌学习太极拳一样,当即忘记这二十条箴言时,你已经掌握了真正的编程之道。


1. Include precise preconditions and postconditions with every function that you write. 
你编写的每一个函数都应该包含准确的执行先决条件和后置条件。


       每一个函数都应该带有一个说明文档,它由函数的先决条件和后置条件组成,有时还包括该函数所调用函数的函数名列表。所谓先决条件就是函数被调用前必须做的一些初始化工作以及各种形式返回的变量的含义;而后置条件则包含函数的功能和通过函数调用后以各种形式返回的变量的含义。编写函数说明文档的意义不仅在于阅读程序时,可以迅速地对函数有一个大体的了解,更重要的是它能帮助我们避免两个函数之间的接口错误。 
一个完整的函数说明文档如下: 
/* QuickSort: Sort contiguous list by the quicksort method. 
Pre: The contiguous list list has been created。 Each entry of list contains a key。 low and high are valid positions on the list list. 
Post: The entries of list have been rearranged so that the keys in all the entries are sorted into nondecreasing order. 
Uses: Swap. */ 
Void QuickSort(List *list, Position low, position high) 

…… …… 



2. Always name your variables and functions with the greatest care, and explain them thoroughly. 
要十分慎重地给你的变量和函数命名,使其完整准确地解释它的功用。


       尽管数据和算法可能已事先存在,但只有当你给他们以确切的名称时,它们才能在程序中被有效地组织,才能被赋以"生命"。所以一个程序能够正常工作,准确的掌握每个变量的含义和每个函数的功能是非常的重要的。文档固然能起到解释的作用,但一个恰如其分的名字却能起到事半功倍的功效。 
你是不是经常面对这样的变量:s=p*n;s1、s2;LoopCounter++;bl01?你或许会问:"有问题吗?"当然编译器这一关肯定会顺利的通过,但仔细想想问题不少:s, p, n分别都是什么意思呢?s1, s2之间的区别在哪儿呢?i++是不是更简洁呢?是blO1(字母l,字母O,数字1)还是b101(数字1,数字0数字1)或许是blOl(字母l,字母O,字母l)? 
如何避免上述这些问题的发生呢?下面这几条建议或许会有所帮助: 
1>全局变量用具有描述意义的名字。 
2>局部变量(特别是循环变量)用短名字。 
3>函数采用动作性的名字。 
4>保持命名的一致性。 
5>同一变量名不要有多种含义。 
6>不要用过于相似的变量名。 
7>变量名中一般不要带有数字。 
8>规模较大的程序使用匈牙利表示法1。 
9>显式地说明变量。 
10>对变量最好做出注释说明其含义。


3. Keep you documentation concise but descriptive. 
保持你的文档简洁,但有较强的描述性。


       很多人将编写文档视为一件次要的任务,认为文档只是在程序完成后对程序作的解释和说明。如果只是写一个小程序,你的确可以把实现的每一个细节都牢记在心。所以此时的文档只是写给别人看的。但如果是一个大程序呢?把任何东西记住似乎是不可能的。所以在写程序的时候,对于其每一个部分都应该同时编写相应的文档。一个好的习惯是编写程序与注释文?quot;并发执行"。甚至有时文档的某些部分应该在程序之前完成。


       并不只所有的文档都是恰当的,看看下面这段文档吧! 
int func(int i) 

int j; //The declaration of i. 
j=0; // Initiate the variable. 
while (i > a[j]) 
j++; //Increase i by 1. 
return(a[j]); //Return the value. 

       函数是完成什么任务的呢?程序中明明定义了j,为什么文档中说是i?这些注释有价值吗?看看下面的建议吧。 
一个好的文档通常做如下工作: 
1>像箴言1中建议的那样给函数作注释。 
2>每当声明一个变量、常量、类型时,解释它是什么以及它怎么使用。当然起一个"望文知义"的名字是一种更好的选择。 
3>程序中的每一个模块都应该以一段简明的说明其功能和行为的注释开始。 
4>对程序中没有明显结束标志的部分做标记。 
5>不要大谈明显的东西。 
6>对每个使用了某些小技巧或是含义不明确的语句作注释,更好的做法是避免这种语句的出现。  
7> 代码本身应该解释程序是如何运作的,文档应该用来解释做了什么和为什么这样做。 
8>无论何时,当代码改变了,一定要让文档也随之改变。 
9>注释不要与代码矛盾。 
10>不要注释不好的代码,而应该重写。 
       再来看看上面的文档,它给出了太多明显的东西。而文档与程序不一致,可能是因为修改了代码,却忘记了修改文档罢。


4. The reading time for programs is much more than the writing time. Make reading easy to do. 
阅读程序的时间要比编写程序的时间长得多,所以要保证它的易读性。


       程序中的空格,空行,缩进也是文档的一种重要形式,他们使程序易于阅读,可以使你一眼看出其内部各部分的相互关系。所以你应该选择一套固定的编程格式,并在你所有的程序中使用它。许多专业的程序设计团队,都在编程格式上达成共识,并坚持所有成员都使用它。这样对成员间的相互沟通大有益处。 
下面这两个函数吧,如果两个水平相当的程序员分别同时阅读它们,可以肯定的是第二个要快得多。你可以做这个实验。(因为有特殊目的,故不给出相应文档。) 
//The first function. 
int SomeUse(int PositiveArray[], int NegitiveArray[], int SomeVariable) 

int i; 
if (SomeVariable > 0) { 
for (i=0;SomeVariable > PositiveArray[i];i++); 
return(PositiveArray[i]);} 
else if (SomeVariable < 0) { 
for (i=0;SomeVariable < NegitiveArray[i];i++); 
return(NegitiveArray[i]);} 
else return 0; 
}


//The second function. 
int SomeUse(int PositiveArray[], int NegitiveArray[], int SomeVariable) 

int i; 
if (SomeVariable > 0) { 
for (i=0;SomeVariable > PositiveArray[i];i++) 

return(PositiveArray[i]); 

else if (SomeVariable < 0) { 
for (i=0;SomeVariable < NegitiveArray[i];i++) 

return(NegitiveArray[i]); 

else return 0; 

记住下面几条,会对你提高程序的易读性有所帮助: 
1. 使用首行缩进,以表现出各语句逻辑上的层次关系。 
2. 使用表达式的自然形式。 
3. 尽量使用括号以排除歧义,不要假定读者熟记运算符的优先级。 
4. 分解复杂的表达式。 
5. 使用字符形式的常量,不要用整数。


5. Don't lose sight of the forest for its trees. 
不要为了几棵树而放弃整片森林。(不要因小失大。)


       通常解决一个问题的过程中,最重要的一步是如何将一个问题分解为若干个规模较小的可以在细节上被理解的子问题。如果解决某些子问题仍有困难,则再将其细化,直到问题的规模足够小为止。在一个大公司里,高级经理往往把精力放在对问题整体的认识和把握上,而将相应的工作委派给自己的下属。同样的中级经理也不是什么都做,他将问题细化后,交给手下处理。程序设计亦应如此。解决任何问题时都应该采用这种自顶向下,逐步细化的策略。实现上,根据对问题的整体分析,先写出主函数,而使各个子问题以函数的形式存在于其中,然后明确各个函数的功能,各个击破。


6. Each function should do only one task, but do it well. 
每个函数只须执行一个任务,但要把它做好。


       这条箴言从实现的角度阐明了函数应该具有强内聚性的原则。所谓内聚性是指一个子程序中各种操作之间互相联系的紧密程度。有调查表明,强内聚性子程序比弱内聚性子程序出错的几率要小得多。所以强内聚性的目的是使函数的功能尽可能的单一,以提高子程序的可靠性。如何判断一个函数的内聚性呢?一个简单的方法是,如果你使用了较大的篇幅来确定一个函数的先决条件或后置条件,那么或者你给出了过多的细节,或者你应该考虑函数的内聚性问题了。


       一个子程序只完成一个基本功能,听起来简单,但是操作起来可能会因为某些原因忽略了对函数内聚性的关注。其实只要你在起草设计书时花一点时间设计好框架,在写函数文档时多一点细心。这些都会给你带来意想不到的益处。


7. Each function should hide something. 
每个函数都应有封装性。


       承着箴言5中的例子。大公司中的中级经理并不是将他从下属得到的所有结果直接返还给上级,它必须将下属返还的结果总结整理,去除不必要信息,并自己处理一些问题,而只把必要的结果返还。同样他也不是将他从上级所得到的任务直接传达给下属,它传达到每个人时,任务已经很明确了。正如他所做的,函数执行的任务亦应如此。它应该在内部封装一些自己的东西。这些东西完成了函数的功能。对上,处理相关的数据,按要求返还结果;对下,细化问题,分配任务。这种封装性大大地降低了程序的复杂性,一旦你完成了某个函数,你就可以不必再考虑它的内部工作细节,只要调用它就可以了。


8. Never cause side effects if you can avoid it. If you must use global variables as input, document them thoroughly. 
如果可能,尽量避免副作用。如果你必须使用全局变量作为输入,那么在文件中透彻地做出说明。


        函数中的数据类型有五种:输入变量(形参)、输出变量、输入输出变量(变参)、局部变量、全局变量。函数间的数据传递应该使用输入变量、输出变量和输入输出变量,而应避免使用全局变量。使用全局变量不仅会使函数间的关系变得复杂,有时还会产生副作用。一个函数完成后,程序员可能很快忘记自己使用了哪些全局变量。如果主函数稍后被改动了,则使用了全局变量的函数可能会变得行为异常。而当全局变量的值在函数中被改变时则会产生副作用。副作用比全局变量作为输入变量产生的危害更大。因为副作用不仅会使该函数的行为异常,还会影响所有使用了该变量的其它函数,进而诱使程序员对那些原本正确的函数进行排错。 
下面这个例子可以很好的做出说明。(后面给出解释,故不再进行注释。)


#include<iostream.h> 
#include<math.h> 
int t; 
float f(float x) 

return((sqrt(2*t)+log(t))/sqrt(t+x)); 

float g(float x) 

return(x/t); 

main() 

float x,y,z; 
cin>>x; 
t=2*x*x+4; 
y=f(x); 
z=g(x); 
if (y > z) 
cout<<y<<endl; 
else 
cout<<z<<endl; 

       这是一个数值计算程序。功能是根据输入的x,根据相应公式计算出临时结果t,然后计算并输出f(x)与g(x)中较大的一个。程序可以顺利编译执行。但在对实际问题分析时发现t的表达式由误,应为t=2x+4。修改程序后,运行发现f(x)在t为负值时报错。原因是函数sqrt和log要求参数为正值。假设修改方法是对t取绝对值。鉴于f(x)的表达式较复杂,所以首先想到的修改方法是在f(x)的return语句前加入"t=abs(t);",以减少程序的改动。这时编译通过,并且对于任何合法输入,都有结果输出。但经测试发现t为负值时结果有误。进一步测试发现g(x)的输出异常,对g(x)跟踪调试发现g(x)没有问题。问题出在哪呢?原来函数f(x)和g(x)中都使用了全局变量t,正是这非正当途径传入函数的全局变量导致了上述问题。如果两函数都将t做为值参,则不会发生两函数互相影响的情况。 
对于这样一个不足30行的小程序,你也许可以一眼看出问题,但如果是一个上百行上千行的程序呢?如果使用的全局变量不是一个,而是几十个几百个呢?所以无论何时,当你决定使用一个全局变量时,一定要慎重考虑,并且在整个程序中对其进行跟踪说明。


9. Keep your input and output as separate functions, so they can be changed easily and can be custom-tailored to your computing system. 
程序的输入和输出应改作为单独的函数而存在。这样他们容易修改,并且可以根据你的计算机系统定制。


       许多正在使用的程序中,输入输出函数往往是最长的。程序的输入必须被严格的检测,以保证其合法性和适应性。输入中的错误必须以各种方式进行处理,以避免致命性错误或输出异常结果。而输出必须被仔细的安排和格式化,慎重的考虑什么该输出,什么不该输出,并提供不同的选择以适应多环境。所以输入与输出必须被有效地组织以保证程序的正常工作。而输入输出的基本操作往往过分的依赖语言,乃至该语言的版本及运行环境。因而写一个适合于各种环境的输入输出是极为困难的,当然也是不必要的。因为作为函数存在的输入输出会轻松的解决这个问题。


10. The quality of test data is more important than its quantity. 
测试数据的质量比它们的数量重要得多。


       程序完成后就应该对其测试。测试是在你认为程序能正常工作的情况下为设法打败它而进行的一系列系统化的实验。测试的工具是测试数据。所以测试数据的选择对测试的效果有非常大的影响。测试应该根据程序的具体情况分测试点进行。在同一测试点进行大量重复的测试对测试的有效性毫无价值。


       下面两个方法对选择恰当的数据进行测试是非常有效的: 
a.黑盒方法 
        大多数程序用户常常对程序中模块的实现细节不感兴趣,他们感兴趣的只是结果。他们希望把整个程序看作一个黑盒。与此类似,测试数据应该按照设计书的要求来选择,而无需关心程序的内部细节。为了确定程序工作正常,测试数据应以以下方式产生: 
(1) 简单的数据 
       由于简单的数据容易手工计算,所以可以很容易地掌握变量在每个时刻的正确的值,便于发现错误。


(2) 典型的实际的数据 
       将程序放在一个模拟的真实的环境中测试往往是最为实际和有效的。


(3) 极端数据 
       极端情况是程序最容易出错的地方,极端数据测试的通过可以在很大的程度上说明程序的有效性。


(4) 非法数据 
       对非法数据的准确识别和处理体现了一个程序的强健性。所以测试非法数据保证程序不会因其异常中止或"Garbage in garbage out2"。


b.白盒方法 
       对一个程序进行测试。如果某些部分总未被执行,则不能保证测试的有效性。白盒方法即设计合适的数据,使case语句的每个情况,if语句的每个分支,以及每个循环的中止条件都得到执行。当然,对一个大程序施行白盒测试是不现实的。但却可以对其每个基准单位实行白盒测试。


        上述两种方法,从表面上看白盒方法进行的比较全面,而实践上黑盒方法更为有效。这是因为程序错误常常发生在函数间的接口上,而白盒方法只是分别测试每个函数,所以对此类问题无能为力。一个较好的建议是对一项大工程进行测试时,对其每一个基准单位运用白盒测试,对其每个规模较大的模块甚至整个工程实行黑盒测试。


11. Program testing can be used to show the presence of bugs, but never their absence. 
程序测试只能找出错误,却不能证明他们不存在。


       这是Dijkstra的名言。要对一个程序进行测试,测遍所有数据或试变所有路径是最可靠的。这是黑盒方法和白盒方法的两种极端情况。对一个中等规模的程序进行这两种测试所花费的时间往往都是天文数字。所以无论测试时选用了多少个例子,它们仅仅是所有可能的输入中极少的一部分。如果执行这些例子的结果是错的,则说明程序确实有问题。然而如果执行这些例子的结果是正确的,我们并不能认为程序中就没有错误了。测试点的通过只能增强我们对程序的信心。


12. Most programs spend 90 percent of their time doing 10 percent of their instructions. Find this 10 percent, and concentrate your efforts for efficiency there. 
大多数程序花费90%的时间来执行10%的代码。找到这10%,集中力量提高其效率。


       每个程序都有自己的核心代码,这段代码往往会在整个程序运行时被频繁的执行,其执行效率对整个程序来说是至关重要的。所以提高其效率是最有效的。要使它执行速度快,最重要的因素是算法与数据结构的选择,如果已经选择了正确的算法程序但速度仍是问题的话,下一步能做的就是调整代码,整理循环和表达式的细节,设法使事情做得更快些。值得说明的是现代的编译器随代码优化会做不少工作,所以编译前打开所有优化开关,往往会得到意想不到的效果。


13. Never code until the specifications are precise and complete. 
一定要在有了准确完整的设计书后再进行编码。


       一项大的软件工程中在合适的时间进行编码是重要的,不能太早也不能太晚。大多数程序员错误的过早进行编码。如果在设计书明确前进行编码,那么在编码时就有可能对设计书做出一些不当的假设。这些假设可能会导致不同的子程序之间相互矛盾,而使程序设计任务变得比原来更加困难。另一方面也有可能编码脱延得太迟,这也是应该避免的。就像我们自顶向下设计一样,我们应该自顶向下得进行编码。一旦某一级的设计书明确完整了,我们就应该在这一级上进行子程序设计和测试3。一旦发现设计缺陷,我们可以改变它,而不必为下一级的函数而操心。


14. Keep your algorithms as simple as you can. When in doubt, choose the simple way. 
尽可能使你的算法简单,如果没把握,那么选择简单的方法。


       选择简单方法的原因有两个:从程序员的角度来看,实现一个复杂的算法往往比实现一个简单的算法要花费更多的时间和精力,而有时这种花费是得不偿失的。特别是那些只执行几次的程序、偶尔用用的工具、测试框架、各种试验和原型程序。如果一个简单算法工作的足够好,我们没毫无必要花尽心思去改进。另外,从存储花费上看,一个复杂的算法要在效率上有所作为,往往是以牺牲存储空间为代价的。复杂的数据结构,以及其上的复杂的操作,必然会占用更多的存储单元,如果同时问题规模比较大,这时就可能会出问题了。


15. Consider time and space trade-offs in deciding on your algorithm. 
在决定你的算法时要权衡时间和空间的花费。


       时间和空间常常是算法代价矛盾的双方,如何取舍往往取决于程序运行的环境。如果有足够的可用存储空间。那么时间应该作为考虑的主要因素。反之则优先考虑空间。 
从计算机诞生至今的很长一段时间内,存储空间都是最贵重的计算机资源。声名狼藉的"两千年问题"是经常被提起的典型例子。如今不同了,存储器的廉价已经快不能让人置信了,特别是虚拟存储器的引入,已经使时-空矛盾变的不那么尖锐了。但我们仍不能掉以轻心,这种矛盾在某种程度上依然存在,就算增大虚拟存储器的容量也无济于事,频繁的换页操作会增加程序运行的花费,使性能降低的让人无法容忍。所以慎重的对待时-空矛盾在今天仍有意义。


16. Act in haste and repent at leisure. Program in haste and debug forever. 
匆忙行事,不悔难求,草率程序,调试不休。


       遇到问题便急急忙忙的进行编码,并不是个好习惯。程序设计并不只是编码。相关的辅助工作做好时,编码是很容易完成的。软件工程中提出的"软件生命周期"就是一个很好的借鉴,无论你面对的是何种规模的程序都有借鉴价值:


1>准确而完整的分析问题,一定要仔细地确定所有必要的用户接口。 
2>建立一个原型,并在其上实验,直到所有的设计书确认。 
3>设计算法,尽量使用数据结构提供的工具或其它算法的现成函数。 
4>确定算法的正确性,或使算法足够简单以至于其正确性是不言自明的。 
5>通过分析算法,得出其需求,并保证其与设计书相吻合。 
6>用合适的程序设计语言进行编码。 
7>用精心挑选的测试数据对程序进行测试。 
8>对所有的子程序细化、重复上述步骤,直到软件完成并且功能完善。 
9>如果需要,优化代码以提高其性能。 
10>维护程序,使其满足不断改变的用户需求。


17. Starting afresh is usually easier than patching an old program. 
从头重做通常比修补一个旧程序要容易得多。


       修补一个旧程序是困难的,首先你要看懂它,找到当时创造时的思路。而如果程序没有良好的注释和编程风格那这第一步就很难完成。就算你看懂,接下来的麻烦也会令你棘手。当你修正了一个bug,或许会产生更多的bug。也就是说程序中的bug常常不会因为你的努力而减少。以此为主题讥讽程序员的笑话比Windows补丁更新得还快。所以,一旦你要改变10%以上的代码时,你应该重写它。


18. Always plan to build a prototype and throw it away. You'll do so whether you plan to or not. 
无论计划与否,你总是先建立一个原型,然后再将其抛弃。


        工程上模型经常被用于在施工前确定方案的可靠性。程序设计亦应如此。因为一个程序,在其能够运行之前,要知道设计的哪一部分有问题或是哪些特征应该改变以满足用户的需求几乎是不可能的。所以避免一项大型工程重写的有效方法是从一开始就写两个版本。一个是将要丢掉的试验品,称为原型;另一个是交付使用的版本。原型对程序设计十分有用,它可以使工程早期用户于设计者的交流更为轻松,从而减少误会,使设计满足每个人的要求。程序员可以使用现成的输入、输出、排序等其它一些普通的模块来构建原型,这些模块通过尽可能少的代码组合起来,以完成某些预期的任务。这种任务是有限但完整有用的,它为程序员和用户改变某些程序特征提供了一种有效的实验手段。


19. Practice information hiding: Separate the application of data structures from their implementation. 
使用信息隐藏:将数据结构的应用与它的执行分开考虑。


       按照自顶向下,逐步细化的原则,我们在探索运算步骤时首先应该考虑算法顶层的运算步骤,然后再考虑底层的运算步骤。所谓顶层的运算是指定义在数据模型级上的运算步骤或叫宏观运算。它是算法的主要组成部分,其上的数据和操作都可以暂时不关心其实现,具有抽象性质。而底层运算步骤是指顶层抽象的运算的具体实现。它们依赖于数据模型结构,依赖于数据模型结构具体表示,可以理解为微观运算。它是顶层运算的细化,是为顶层运算服务的。将二者分开考虑是分治思想的一个体现,便于我们集中力量,提高效率。为实现这一目标,必须对二者的接口进行一次抽象,让底层只能通过这个接口为顶层服务,顶层也只能通过这个接口调用底层的运算。而这个接口就是《数据结构》课程研究的重点:ADT4。


20. Once your data are fully structured, your algorithms should almost write themselves. 
数据善置,算法自成。


       一个图如果给出点-点关系,要用邻接矩阵储存,如果给出点-边关系则应该使用邻接表。要对一个表进行排序,如果规模不太大,一般的插入排序足以解决问题,而如果规模较大,则应考虑快速排序、归并排序,甚至外部排序。所以算法的选择往往取决于数据存储方式,规模等等。算法从另一个角度看就是一系列对数据操作的集合。前人在算法上已经作了不少工作,数据结构定了,相应或类似的算法自然会找到。所以,遇到问题能够全面的认识并建立合理的模型,那么你已成功了一大半。


[注释] 
1. 匈牙利表示法:一种给变量命名的方法。其内容非常简单,在给变量命名时,变量名以一个或者多个小写字母开始,表示变量的数据类型,其余部分由能描述变量的含义的单词组成,且每个单词首字母大写。如:iSum, chStudentName等。匈牙利表示法能够帮助程序员及早发现并避免代码中的错误.由于变量名既描述了变量的作用,又描述了数据类型,这就比较容易避免产生数据类型不匹配的错误。 
2. Garbage in, garbage out:"输入垃圾,输出的也是垃?quot;通常指程序没有很好的对非法的数据(一般情况下是超界)进行检测和报错,从而导致程序对错误的数据进行计算而得出错误的结果。 
3. 由于下一级的函数还没有完成,所以测试时使用了一种名为stub的技术。简单的说就是用一些与子函数的函数名相同但无任何功能的函数代替原函数,它的作用只是读入数据并输出数据,以使该级的测试顺利进行。最简单的stub就是空函数。 
4. ADT:抽象数据类型。 
5. 还有一点要补充:有些书上把子程序(Subprogram)和模块(Module)视为相同的概念,这是不妥的。我认为子程序是指形式上独立,且具有单一功能的程序单元结构,像C,C++中的函数,Pascal中的函数和过程,ForTran中的函数和子程序;而模块是指功能上独立的程序单元结构。一个模块可以包括若干个功能统一的子程序。




[参考资料及推荐读物] 
1.Robert L. Kruse, Clovis L. Tondo & Bruce P. Leung 《Data Structures & Program Design in C》 
这本书与一般的数据结构教材的不同之处是,书中介绍了不少程序设计方法和软件工程方面的内容,以及程序设计风格、性能、测试的一些技巧。同时书中还给出了大量的实例,使你能够不断巩固所学的内容。本文的所有二十条箴言全部取自这本书的前两章,在注释的过程中也参考了其中不少内容。本书的唯一缺憾是,数据结构部分没有能够走出传统教材的陈旧模式,同国内的大量数据结构教材的章节安排如出一辙。尽管如此这还是一本不可多得的好书,特别对于初学者来说,其浅显易懂的讲授,会使你对数据结构有一个清晰的认识。 
2. Brain w. Kernighan & Rob Pike 《The Practice of Programming》 (中译本:裘宗燕译 《程序设计实践》) 
这是一本程序设计方法技巧领域内的一本经典之作。是作者多年的程序设计的经验之谈。现在市面上讲授程序语言、数据结构、编译环境的书真是太多太滥了,程序设计方法技巧方面的书籍却鲜见,而这本书会告诉你如何在最短的时间内写出通用、健壮、移植性好、错误更少的程序。用译者的话说:"它的翻译填补了国内目前这方面书籍的空白"一点不为过。另外本书的翻译方面也值得称道,译者能够准确的把握作者的意图,并且准确的表述出来。无论你是初学者还是编程高手,都能从中学到不少东西。 
3. Steve Maguire 《Writing Clean Code ──Microsoft Techniques for Developing Bug-free C Programs》 
(中译本:姜静波 佟金荣译 《编程精粹 ── Microsoft 编写优质无错C程序秘诀》) 
本书出版的比较早,但至今仍经久不衰,常常成为论坛中谈论的主题。本书的作者是Microsoft资深程序员,曾经参与了Excel的开发。他以许多排错、测试的准则来组织各章的内容,中间穿插许多作者自己亲身经验,使内容显得轻松,有趣。如果你能够找到这本书,一定要读一读。


4. 傅清祥 王晓东 《算法与数据结构》 
这是国人编写的一本出色的教材,其涵盖的知识面非常广,不管你刚接触数据结构还是想在这方面有所提高,这本书都是一个不错的选择。 
5. Geoffrey James 《The Tao Of Programming》 (中译本:郭海 郭涛译《编程之道》) 
本书出自美国的一位善于进行哲学性思考、有十多年工作经验的程序设计师之手。 
6. 潘锦平 施小英 姚天昉 《软件开发技术》 
这是一本历史悠久的教材。书中对软件工程进行了简明的介绍。如果你想对软件工程或其中的某个部分有一个简单的了解,那么这本书是适合你的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值