错误与编程——抛弃 C程序设计 中的谬误与恶习
《品悟C——抛弃 C程序设计 中的谬误与恶习》——前言
- 这是一本什么样的书
这是一本关于C程序设计常见错误的书。书中详尽地剖析、讨论了学习或使用C语言过程中常见的一些错误观念和错误实践。目的是帮助读者更准确地理解并运用C语言。
- 为什么要写这样一本书
因为错误与程序设计一向如影随形,密不可分。只要编写程序,就无法避免错误。这是一条铁律。无论是初出茅庐的新手还是经验丰富的老手,无一例外。从某种意义上来说,软件业的主要工作有三项:制造错误、改正错误和掩饰错误。因此不深入了解和认识错误就不可能真正懂得编程。
孔子说:“未知生,焉知死?”程序设计也是这个道理,不懂得错误就不可能真正理解程序设计语言的要点及编程的真谛。实践表明,人们从错误中学习到的东西往往比从正确中学到的多得多。这就是人们常说的“吃一堑,长一智”。
事实上每个程序员的成长历程都是一部不断认识错误并予以改正的纠错史。每一个优秀的程序员无一不是从无数的错误中脱颖而出的,并且始终要与错误进行不懈地斗争。闪光的思想一向少不得经历错误的磨砺。
从这个角度来看,对程序设计中的错误进行讨论比那些一本正经地正确叙述更有助于程序设计人员水平的提高,本书的目的就在于此。
然而尽管市场上C语言的书籍多得可以车载斗量,但遗憾的是几乎鲜有专门论述C程序设计中错误的书籍,除了Koenig所著的《C陷阱与缺陷》。不过我觉得作者Koenig先生“阐之未尽,我有我的看法”。
实际上Koenig先生并不了解中国国情,他不知道程序设计中的错误其实分为两种,一种是错误,另一种是中国式错误。一句话,他根本无法想象我们是用质量何等奇特的教科书、用何等落后的方式学习C编程。因此他的书只提到了普通的一般性错误,而对形形色色的具有中国特色的错误却只字未提。这是一个巨大的、需要填补的空白(我不知道这算不算学术空白,从某种意义上来说这更像是清除技术污染)。这项工作目前来看还没有人做。既然古代的圣贤曾经教诲过我们应该“当仁不让”,所以现在只好由本书来勉为其难了。
- 错误是怎样“炼”成的
众所周知,所谓编程在本质上无非是用特定的形式语言向计算机描述一个问题的解决方法。在此之前,对这种语言的学习也是一个必不可少的环节。在整个过程中的一系列环节的任何一个环节上都可能会出现错误。
首先,语言的学习过程中会产生许多错误。这些错误不仅是因为学习者自身的原因,同时也是由于教科书方面的原因。
许多虔诚的学习者内心的潜意识里会以为教科书不会有什么错误,他们一向跪在教科书上学习,遇到错误时只是不断地检讨自己。在这个连婴儿奶粉都可能含有三聚氰胺的社会,怀有这种念头实在是too naive了。没有任何理由指望你的教科书比婴儿奶粉更纯净。
教科书同样可能有错,这种“水源”的污染才是最严重的污染。在这种情况下,只查找自身出错的原因是不可能解决问题的。这种先入为主的错误非常难以纠正,而且其影响往往也最为深远。例如,很多人在成为职业程序员之后也还不清楚
a+=a-=a*a
这个表达式是不正确的。
对语言不够熟悉是初学者犯错误的另一个主要原因。通常表现为代码里不是缺点儿什么就是多点儿什么。这种错误是一种最初级的错误,连编译器能够指出,因而比较容易得到纠正,所以多半不足为虑。
学习一种语言,本质上就是为了学习一种新的思维方式。将平时不严谨的自然语言思维习惯带入程序经常会导致对程序设计语言的误解,例如把“x不等于1或2”错误地表达为
x!=1||x!=2
甚至我们所熟悉的严谨的数学语言,由于语境发生了变化,也会在程序中带来错误。例如,很多人都犯过把“==”(“等于”)写成数学中的“=”(“赋值”,在数学领域表示“等于”)这种错误。这种错误一般需要通过长期的自觉训练来纠正。
往往被初学者所忽视的是不良编程习惯的问题,他们一心只想着写正确的代码,而从不考虑如何正确地写出正确的代码。这往往会导致许多低级错误一犯再犯,比如一再出现括号不成对这样的问题。
缺乏良好的编程习惯是写不好程序的,任何一个工人都懂得:良好的产品质量需要由合理的工艺来保证。不懂得正确地编写程序就几乎不可能写出正确的程序。
初学者忽视不良习惯的另一个主要原因是,这些编程陋习有时并不能在短期内显示出其恶果,比如“int a,b,c;”这种烂得不能再烂的变量名。实际上良好编程习惯的意义不仅在于避免错误,还在于它意味着少犯错误,或者在出现错误时很容易检查到并修正,这些长远的利益在短时间内一般是体会不到的。
良好的习惯要靠自觉培养,但是如果教科书没告诉你这些,那是教科书的问题。因为初学者不可能在短时间内完全靠自己的悟性领会前人几十年积累下来的编程规范方面的经验。顺便说一句,有些教科书的代码风格本身就极滥无比,对初学者误导很大。
仅仅熟悉语言元素对于编程来说是远远不够的,因为每一个程序的目标都是为了解决问题。而从问题到程序要经历问题的提出、问题的分析到问题的解决,最后落实为代码,在这一过程中每一个步骤都可能会出现错误。
正确地提出问题是解决问题的先决条件,错误的问题是不可能解决的。例如,要求“输出1~n*n的自然数构成的魔方阵”就是一个错误的问题,因为2阶的魔方阵根本不存在。求解这样的问题,可以说是未解先错。
问题提出模糊不清的要求也是一种错误,例如“有三个数a、b、c,要求按大小顺序把它们输出”,这里的三个数究竟是什么数,是模糊不清的,在不同的情况下代码截然不同。要求应该明确。要求不清是提出者的错误,但擅自猜测问题的要求则是程序员的错误。
编写求解错误问题或不严谨问题的程序有害无益,而且贻害无穷,这会使程序员在不知不觉中丧失必要的职业严谨,对错误的需求渐渐变得麻木不仁。要知道,在真实的开发中,因为软件需求错误而导致的软件错误占到错误总数的一半以上,而且那些错误的需求往往不那么清楚明显、容易察觉。不具备审视问题或要求是否合理的习惯和能力,在真正的软件开发中就如同盲人骑瞎马,不可能不掉到沟里。
初学者中很少有人意识到问题本身可能就是错误的。很多初学者常常不管三七二十一地解决错误的问题。这对他们的逻辑思维能力是一种巨大的慢性戕害。
即使正确地提出了问题,也还可能被程序设计者所误解。错误地理解问题的要求,同样是错误产生的一个重要原因。
分析问题的过程中最容易产生逻辑错误,或者对问题分析得不全面导致的顾此失彼。这种不全面的逻辑漏洞往往比完全的逻辑错误更难发现,因为这时程序的运行有可能会“显得”是正确的。
即使没有前面提到的那些错误,距离写出优秀的代码还有很长的路。写代码和写文章一样,需要精心地进行布局谋篇,用术语来说就是进行设计。
设计不仅包括算法设计还包括对数据结构的设计。在“算法——程序的灵魂”这种片面观点的误导下,很多人轻视数据结构的设计,冥思苦想所谓的算法。实际上算法和数据结构是密不可分的,那些艰深晦涩的糟糕算法,大多是因为垃圾的数据结构设计。
有些人写程序毫无设计意识,上来就开始匆忙写代码,东一榔头西一棒槌,写到哪算哪,写出的代码笨拙无比、僵硬造作、逻辑混乱、啰唆重复或者效率低下。这样的代码即使能输出结果,也是不合格的丑陋代码。
难道程序长得丑陋也是一种错误吗?是的,作为一种艺术,程序有自己的美学标准。从微观上看,这种美感表现为充分体现语言的优美和风格的简洁;从宏观来看则表现为一种层次清晰和条理分明。这种标准并不出于纯粹的审美,也有其功用价值——不容易出错,发生错误容易改正。而要达到这一目标,程序设计实践过程离不开正确的哲学指导,这种哲学的一个核心理念就是结构化程序设计思想。
至此,尽管还有很多种类错误没有谈到,但是已经不难得出结论,程序设计错误很多。需要始终对错误保持警惕。
- 应该如何对待程序设计中的错误
首先,不要惧怕错误。错误尽管繁多,但只要了解认识错误就可以改正、避免错误。最可怕的是对错误的无知与鸵鸟般的无视。认识错误是改正错误的前提。
其次,了解错误越早越好,了解得越多越好。了解得越早进步得越快,了解得越多进步越大。
第三,错误也是一种财富。既然从错误中积累经验是提高程序设计水平的必经之路,那么从他人的错误中吸取教训则是一种提高程序设计水平的捷径,因为所谓捷径无非就是少走弯路或不走弯路而已。
- 本书的主要内容及特点
本书汇集了大量C语言的错误和编程陋习,逐个予以详细讨论,这些错误主要取自国内发行量和影响力较大的C语言教科书及其他一些非教科书C语言书籍以及网络上比较流行的观点,主要包括□□□□□(为节约篇幅,此处删去286个字),也有一些则是C语言本身的一些不易理解的特点以及学习者不具备良好的编程习惯所导致的错误。应该说本书所举出的反面例子非常具有代表性。
本书分为上下两部,上部“形而下学”讨论代码中的常见错误和不良风格,下部“形而上学”讨论的是很多人对C语言持有的错误观点和认识。
书中各个小节基本上是互相独立的,因此读这本书并不需要循序渐进,可以翻到哪页读哪页。
- 什么人应该及什么人不应该看这本书
对于使用国内教科书学习C语言的人来说,无论是初学者还是职业程序员,这本书应该是很有价值的参考读物。因为本书的内容恰恰是教科书不讲的内容或恰恰是教科书讲错了的内容。
如果是为了应付什么等级考试之类的话,就不需要浪费钱财了。看了此书你可能会发现那些考试本身也有很多错误。
- 阅读指南
(此处删略)
虽然为写这本书花了近三年的时间,但最后完稿时,我发现这段时间依然显得非常短促。因此尽管这是一本讨论错误的书,但是毋庸讳言,它本身可能也免不了存在错误。
您若在阅读时发现任何错误或不妥之处,欢迎与作者联系并通知作者:pmerofc@126.com。这里先谢了!
本书的勘误将发布在作者的博客:http://blog.chinaunix.net/uid/22996974.html和http://www.cnblogs.com/pmer/。
- 致谢
本书的写作过程中得到了很多人的帮助和支持,在此作者表示深深的感谢。
特别感谢starwing83网友的一贯支持和帮助,他对程序设计语言的深刻理解和丰富的实践经验不但让笔者受益匪浅,而且也使本书在许多方面得到了提高和充实。本书的第11章问题28、第18章问题10和第19章问题1为starwing83网友所撰写。
特别感谢OwnWaterloo网友的一贯支持和帮助,他对标准的准确理解和对程序设计深刻的洞悉解开了笔者的很多疑惑,使作者在很多问题上都茅塞顿开。第5章“问题19”为OwnWaterloo网友所撰写。
特别感谢幻の上帝网友,在多年的共同探讨中,他热情而无私地给予作者很多支持、帮助和启迪。本书的很多观点受惠于幻の上帝网友的启迪,他还为本书收集了很多资料,为本书做了许多翔实的考证工作,并纠正了作者的很多错误。
特别感谢诸多网友在长期的讨论中所给予的道义支持、精神鼓励和技术帮助,他们是:变异老鼠、shan_ghost、蔡万钊、狗气球、狗蛋、三月二十七、tempname2、x5miao、madoldman、walleeee、ChiyuT、davelv、huangzhenfan、花瓣雪、一介村夫、wait_rabbit、MMMX、rover12421、良化纲领_、assiss、gccer、8pm、supermegaboy、A.com、noword2k、hahajerry007、ztz0223、Ray001、fera、unixlinuxsys、cokeboL、koolcoy、sh19871122、群雄逐鹿中原、fender0107401、jerryz920、hellioncu、blueheavenljn、x2、lylesong、supersuper8、gaara99、Kabie、pkkj、rossini23、wolfkin、milujite、weixuejun、cnhbdu、yesBSD、kouu、chinesedragon、water_wf、gz80、toniz、eminem112、sunjiakuang、airjordanforce、yumaofsj、yug1129、campuspuzzle、www1862、zxrjkl、peijue、jhzhu_snps、x75yan、zhaohongjian000、tinysniper、xxwpk007、geel、字母二十六、奶茶dsk、abc976031617、rain_fish、amarant、财版、没本、grand508、star1983653、bill15、makeit、liexusong、pandaiam、txg531、kingwolf520、梅川内依酷、zylthinking、bukkake、wuxb45、hobbs136、cjaizss、greensnow、jhui66、luojiannx、耐心学习、xiaobenniao514、L_kernel、geruihai、erlangs、A13433758072、CUXXXCU、ip200、Hongqiyaodao、dglwx、jhinux、xwxfirst、timesu、srdgame、septem776、liupingforarm、kellenforever、192redwolf、cobras、small_bee、gyarenas、jack4010、startn、rainysky、jimmyixy、litanhe、rubyish、Demon—Hunter、wuliming_sc、bigxu、xfoucs、时尚农民、yangxue1206、hukb_cu、jnbxzl0200157、tianshx、sukora、ux400、craneflyfly、autoasm、时间看来、Jokday、asuka2001、namiii、jeung、btdm123、lilery、wangzhen11aaa、nketc、KanonInD、ybh37、dahan16、digdeep126、keytounix、光明-使者、gtv、SoforthHe、djsxut、oilgeo、呆呆的等候救赎、hbmhalley、china_ssl、solu、dajiangyou77、wl85125771、魔兽_LOVER、0xC1988、inzahgi、武林萌猪、lilinly225、wgm001、royalzhang、千年老狼、musezh2、gooderfeng、HAL9000、Jack_Jack、大石头、万仓一黍、Ivony、愚溪、nscboy、minvt、underuwings、Artech、ini_always、小彬、xujif、Leon Sharp、刘博平、linyilong、浩然正气89、backag、happycat1988、ygcao、rhs、Zlinux、小_金_鱼、泡泡腾、SongSharp、szwe、eeeyes、+v、linxr、andreas、冠吸柏汁霆疯、九原山人、火禾、luotong、Programmer K、hachihe、rockyoung、czcz1024、Edgar Wang、magicDict、hoodlum1980、空明流转、随风浪迹天涯、不死鸟之魂、木耳、木野狐(Neil Chen)、散客游、sunriseyuen、chasefornone、飞浪、glshader、Credo、wcut、houqidian、Parry、xiekun605746、哥哥.Net、Virus-BeautyCode、庄金峰、我想我是风、Mien Ng、soap、greenhand2008、Homer_Simpson、zsea、鎏、ctou45、shamo0303、gussing、eflay、王小兵、l23cy、winter-cn、Lee.Kevin、阳光明媚Ryan、地狱门神、richardzeng、pulihe、寒霭、cncolder、陈玉国、是否存在、key yao、codebumb、☆凯子、darklx、草珊瑚、鹤冲天、Jake Lin、Lithium、干拔三分、leizisdu、zzuxiaolei、BitSky、Jeffrey Zhao、桀骜的灵魂、海南.胡勇、sm11e、我写的不是代码 是寂寞、lzyzizi、肖璟、嗷嗷、木+头、zy498420、№完成、五星、Simon-Zhu、BeckFun、Tony Zhou、lxlylm、陈梓瀚(vczh)、zdd、xchat、frank_hust、漂浮的雨、六芒星、MagicHu、天方、深蓝流沙、viperchaos、swfc_qinmm、潇湘雨歇、riccc、Jeff Wong、wincss、reavics、weiwelcome0、Muse、lx458004975、镜美如、volcanol、路过秋天、徐少侠、南京.王清培、诺贝尔、蔺燕梅……这个名单可能有所遗漏,在此向被遗漏的网友表示歉意。
特别感谢ChinaUnix论坛,在这个论坛的《以其昏昏使人昭昭》帖子中我完成了这本书的主要素材的积累。这个论坛良好的技术氛围使得本书素材的积累得以顺利进行。
本书在写作过程中参阅了大量网上技术资料,从中得到了很多启发,郑重向这些作者表示谢意。
一位朋友根据《以其昏昏使人昭昭》帖子整理了一份文档,给本书的写作带来了极大的便利和帮助,在此深深表示感谢。