一个足球评论员可能并不会踢足球,却并不妨碍在解说比赛时对某某球星的技艺评头论足。同样我也绝不敢以高明的程序员自居,而只是以类似足球评论员的角度来阐述我对程序员的理解。这样,大家也许就不以我为鄙薄狂妄了。这是我必须首先声明的。
什么是程序员
按照Wikipedia的定义,程序员又称为计算机程序员(Computer Programmer)、开发者(Developer)、编码者(Coder)或计算机工程师(Computer Engineer),和网络上广泛流传的码农或程序猿同义。我无意于也不能够为程序员给出一个精确的定义,这里,只是利用程序员的语言做一个简单描述。不是故弄玄虚,不过博取读者诸君一笑。
程序员是彻头彻尾的脑力工作者(Mind Worker),怠于思考者绝对不能成为好的程序员。有鉴于此,类Programmer天生的就应该是Thinker的子类。就程序员所使用的思考技巧而言,Thinker的具体内涵包括逻辑(Logic)和数学(Mathematics)。作为程序员,不一定非要达到逻辑或数学领域的专业水准,而是必须具有逻辑和数学的基本素养。逻辑用来推理,数学用来培养逻辑。另外,数学还有助于程序员训练另外两项必不可少的思考的技能,分析和抽象。下文还要展开讨论。程序员的工具是编程语言,日常活动和主要工作包括设计(design)、建模(model)、编码(code)、调试(debug)、重构(refactor)、沟通(communicate)、学习(learn)和思考(think)。
有关程序员有一个流传甚广的误解,认为做程序员门槛低,没什么技术含量。即使没有学过计算机的课程如离散数学、数据结构、算法等,也可以写程序。写几行程序当然算不得什么,但要修炼成有一定思想境界的一流程序员,却殊非易事。这就如同会做饭的人很多,但真正的烹饪大师却并不常见。所谓码农者,乃是程序员的自我吐槽,岂足深信耶?所以,作为程序员要有持续进阶的强烈的进取心,断不可妄自菲薄,自怨自艾。
程序员的思维艺术
漫长的学生生涯中,我遇到的最好的数学老师是高中时的刘传禹老师。他上课时讲过这样一段话,当面对一个数学问题,一要想的明白,二要算的准确,三要写的清楚。直到今天,这句话对于我的程序员生涯也具有很强的现实意义,能不能想的明白其实是考量一个程序员成败的至关重要的因素。金庸的武侠小说中有一个普遍的规律,那就是武功必定以内力为根基。
比如张君宝与昆仑三绝何足道在少林寺的那场经典之战中,张君宝能够“以少林拳中最平淡无奇的拳招,化解了最繁复的敌招”,始终不落下风,所恃者不过内力之浑厚尔。另外的著名战例还包括少林寺小和尚虚竹VS.吐蕃国师鸠摩智以及聚贤庄萧峰VS.玄难。内力达到登峰造极空前绝后的第一高手莫过于少林寺的扫地僧。也许风清扬是一个例外,好在我们讨论的是一般规律,所以就顾不得他了。计算机编程所特有的思维(Thinking)就是程序员的”内力“,思维能力不济,功能再强大的编程语言也无用武之地。所以,我在这里特别强调程序员的思维艺术。
程序员的思维有一个专业术语,叫做计算思维(Computational Thinking)。计算思维是按照计算机科学的基本概念和方法,用来理解需求、设计系统、实现编程、解决问题的思维方法。简而言之,计算思维就是程序员或计算机科学家是如何思考的。当然,计算机科学的理论知识如数理逻辑、离散数学、数据结构、算法以及面向对象是计算思维的必要条件。计算思维有一系列的智力工具,不能一一尽述,仅列举关键的几项如下:
抽象思维(abstract thought)。给定一个问题,抽象就是去掉纷繁芜杂的与计算无关的部分,用规约(Reduction)的方法还原到问题的本质。所谓本质即把原来的问题转换为一个或几个可以使用计算机描述并解决的问题,进一步讲也就是转换为在算法上可计算的(algorithmically computable)一个或几个问题,更准确更理论化更上档次的描述是转换为邱奇-图灵论题(TChurch-Turing thesis)可计算的可数个问题。图灵机(Turing Machine)和λ演算(Lambda calculus)本身就是对可计算性(Computability)的漂亮的抽象,可以作为抽象思维的经典案例来揣摩学习。一般在实际工作中,常常需要把问题的实体对象根据需求表示为各种数据结构如树、堆、栈等,而业务逻辑(Business Logic)过程表示为各种算法如排序和查找等。
表示(Presentation)是解决问题的第一步,也是关键的一步。在程序员的实践中,我们都有很深的体会,一旦问题被准确的无歧义表示出来了,解决方案就烘云托月般地呈现出来了。这就是“数据即代码,代码即数据”的道理。抽象思维也广泛用于数学家的工作。面对一个困难的问题,数学家们常从两个方向开展研究。
一方面,从特殊情况入手,推广到更一般的情况;另一方面,将一个一般问题具体化成几种特殊情况。两个方向的结果最终汇聚在一起,就找到了问题的答案。我想这可能是论语中“我叩其两端而竭焉”的一个最好注解。而从特殊到一般就是不断抽象的过程。我们用一个具体的例子加以说明,有一个著名的六度分隔理论(Six Degrees of Separation)讲的是世界上任意两个人都可以通过最多另外6个人相互认识,如果要验证这一理论,怎么做呢?
我们可以借助一个图(graph)来表示人与人之间的关系,每个人用图中的一个节点表示,如果A和B认识,那么在代表他们的节点之间有一条边连接。那么现在的问题就转换为检查这个图的直径是否大于6。考虑到世界人口众多,且有生老病死,图的规模必然超大,并且是动态的不断变化的,算出它的直径仍需要更多的简化。这里就到此为止了。
逻辑推理(Reasoning)。逻辑推理对于程序员的重要性不言而喻。与其说逻辑推理用于程序新功能的开发,毋宁说更多的应用在程序调试修改BUG的过程中。程序调试有点类似于Sherlock Holmes侦破案件的过程。和Dr. Wason比较起来,Holmes的推理优于常人的地方有两点:第一,在观察现场或听取来访者叙述时,他能够得到更多的的数据,尤其是一些别人容易忽略的关键的细节,这得益于他对犯罪领域知识的丰富积累,知道什么才是更重要的数据;第二,根据得到的数据,他能够联想到更多的可能的结论,这得益于他大量的案例储存。有了这两点,就能够通过一环套一环的推理链逐渐缩小侦察范围,最终认清犯罪事实。
程序调试也是如此,首先必须掌握程序实际的执行过程的细节。然后从问题出发,分别朝着产生的原因和导致的后果前后两个方向推理。逐渐定位问题的范围,最终找到问题的根源和解决的方案。我们比Sherlock Holmes幸运的是可以借助于调试工具来了解程序运行的过程,所以一个不能使用调试工具的程序真是令程序员感到无比沮丧,只能通过trace信息来跟踪程序运行的过程。如果不知道程序运行的过程,推理就只能靠猜,那么修改BUG是非常危险的,很容易导致回退(Regression)的错误,因为这种情况下如同瞎子摸象,根本不知道自己在做什么。另外,Sherlock Holmes还多次表达过这样的观点,案子越是离奇,越容易解决,因为Singularity is almost invariable a clue。
对程序员来讲,也不必担心奇怪的问题,奇怪本身就是线索。关键看对程序运行细节的了解程度和逻辑推理的技术水平。
分析(Analysis)。分析是上文提到的数学家所用思维方式中从一般到若干特殊情况的过程。面对一个问题,如果一下子描述不清楚或者表示不出来,可以先找出满足问题条件的几种特殊情况。通过仔细检查这几种特殊情况,求同存异,找出他们共同的规律或模式,并对这些模式或规律加以验证,就可以找出描述或表示问题的方法。这就是猜测加验证(guess-and-verify)的过程。项目需求分析时常见的应用案例分析(Use Case Analysis)方法,就是用一个个具体的使用案例将模糊的项目需求生动的表达出来。
分解(Decomposing)。把一个大问题分解为几个小问题,或者把一个复杂的过程分解为几个子过程,当然有助于问题的解决。这也是程序员常用的手段,如算法策咯中的分而治之(Divide-and-Conquer)和合并排序就是这方面的例子。
递归(Recursion)。对于初学编程的人,递归可能是一个比较诡异的较难掌握的概念。但是一个程序员如果不懂递归,很难再称之为程序员。因为很多稍微复杂的算法他都不可能理解,如回溯和动态规划,甚至于树的遍历。递归常常可以用简单的方法非常优雅的表达复杂的算法。
另外,有关计算思维的特有方法还有并行、异步/同步、模拟/近似、优化、分层、封装、解耦等等。程序员的思维艺术即计算思维不是一天两天短时间可以形成的,需要在实践中慢慢琢磨,不断提升,且永无止境。
程序员的技艺境界
程序员的思维艺术融化到到对编程语言的使用上,最终形成程序员的技艺。因此,编程语言之于程序员,就如同青龙偃月刀之于关羽,如意金箍棒之于孙悟空。离开了青龙偃月刀和如意金箍棒,关羽和孙悟空的战斗力就无从谈起。所以,脱离编程语言来讨论程序员的技艺也无异于缘木求鱼,自欺欺人。结合编程语言,程序员的技艺有四个境界,从低到高分别是:
初窥门径。编程语言的初学者,如同小儿咿呀学语,也许可以写一个类似于“Hello World”这样的程序,但对语言的所有东西都是一知半解,不可能应用于实际的项目中。这是我们很容易就可以达到的级别。有些人初窥门径之后,往里面看看,感觉不容易,就放弃了。
登堂入室。对编程语言所共有的基本表达方式有了一定的了解,如变量、赋值、循环、选择等。可以用在一般的项目中,但是写出来的代码看起来滞涩笨拙,很难做出高质量的程序。这个时候,程序员很容易产生自满的情绪,以为完全掌握了这种编程语言,编程也不过如此。如果陷入这种自满情绪中不能自拔,就失去了进一步进阶的机会。
熟能生巧。掌握了编程语言特有的功能,并能驾轻就熟,灵活使用。因此,写出的代码更加的精炼易懂,常常使用简单的方法表达较为复杂的算法。这是一个成熟的程序员的水平,也是我们大多数程序员所能追求的目标。
妙不可言。 这是传说中神龙见首不见尾大师级的境界。柏杨在《中国人史纲》描述李白的才华称,李白写诗时,对汉语的使用就像魔术师手中翻转的手帕一样,神鬼莫测。如同李白作诗一样,我想这个境界的程序员对编程的各种精微之处了如指掌,能够将编程语言的各种功能特性发挥到极致,且恰到好处。运用之妙,存乎一心。并且往往能够别出机杼,奇思妙想,层出不穷。写出的程序优雅、高效、别致。这是我们一般程序员可望不可即的。
程序员的精神素质
开放。在以往的工作中,曾经遇到过这样的程序员,自以为掌握了某些核心的、关键的技术或技能,却不愿意和别人共享,处心积虑的保护着他的“地盘”,担心别人染指他的工作。也遇到过这样的组织,几个被信任的程序员把持着产品的所谓关键模块,其他人莫想参与,即便再有才华,也只能扮演跑龙套的角色。这让我想起《三国演义》中诸葛亮舌战群儒的情节,在回答江东首席谋士张召的诘难时,诸葛亮将儒生分为君子之儒和小人之儒。这里不妨将这样的程序员称为“小”程序员吧。程序员的技艺根植于计算思维中,没有所谓的不传的绝招或秘笈。交流和实践是程序员持续进阶的必要且有效途径。固步自封和抱残守缺是程序员的大忌,完全是作茧自缚,毫无出息。
严谨缜密。在软件开发中,任何事情在逻辑上原因和结果都是清晰明了的,不存在任何意义上的说不清道不明的神秘主义。程序员也是软件工程师,讨论问题时,当然应该使用工程师的语言,即用数据而不是猜测,用逻辑而不是臆断,来表达自己观点。有两种情况可能造成自己表述时似是而非,模棱两可:第一,数据掌握的不够;第二,没有“想的很明白”。例如,当我们讨论性能(Performance)时,一定要用响应时间(Response Time)或吞吐量(Throughput)这样有意义的参数,而不只是泛泛的讲“这系统咋这么慢啊”,“计算机在干什么呢,等的时间太长了”,“简直受不了这样的程序了”。用户可以这样抱怨,而程序员则不可。同样当我们讲到系统开销时,要用CPU占用率、内存这样定量的参数。因此,一个脑筋清楚的程序员不会把这样的话挂在嘴边,“太神奇了,不知道为什么”,“弄不清楚是否可以解决这个问题”,“先这样吧,以后再说”。一般地讲,智能和非智能并没有清晰的界限,因为我们并不知道如何严格地定义智能。然而,有了图灵—邱奇论题,可计算的和不可计算的确实有明确定义的界限,也就是说,计算机可以解决的问题和不可以解决的问题是泾渭分明的,且是可以区分的。对于一个问题,能够解决就是能够解决,不能解决就是不能解决,不至于难以确定是否可以解决。所以,所有以上这些说法都不应该是程序员使用的语言,程序员就是要把一切都弄得清清楚楚,不放过任何潜在的问题。
完美主义。我不了解完美主义的真实意义,也不大拿得准完美主义是褒义词和贬义词。我用这个词是为了强调程序员要坚持追求工作的完美。写代码时是要有洁癖,不允许有任何瑕疵,这样的代码才可能正确、易读、高效、简单、优雅。对一项任务,不仅仅是做完就算了,还应该仔细想想是否是否可以做的再好一点。对遇到的问题,即使看似解决了,也要从头至尾完全弄明白,不能似是而非,不求甚解。
面对变化。变化意味着在新的征程上,要面对许多未知的东西,加之对安定状态下的安乐窝(Comfortable Zone)的眷恋,让我们有着或多或少的畏惧和抗拒。我认为这些都是人之常情,无可厚非。不幸的是,对程序员来说,变化就是家常便饭,如新的项目、新的应用领域、新的编程语言、新的技术架构、开发过程中新的问题、新的功能等,可以说不变的只有变化。其实,好逸恶劳是畏惧变化的根源。只有克服“懒”的思想,强迫走出自己的安乐窝,对新的事物充满好奇心和求知欲,才能适应永远的变化。
程序员的价值
有的公司把程序员看作和水电、机器一样的冷冰冰资源,做项目计划时,一些项目经理以为只要给项目分配足够的资源(包括程序员、水电、机器)并加以正确的管控,项目就可以预期的顺利完成。就好像做东北乱炖,只要把各种食材往锅里一丢,开火等着就万事大吉了。但是,程序员首先是有血有肉的人,绝不等同于毫无感情的机器。一个有雄心的公司要不断提升产品的竞争力,什么是竞争力?就是把产品做的好到不能再好,天下第一,谁与争锋?产品向好的每一步都需要借助于程序员创造力和想象力,这才是程序员的价值之所在。没有程序员愿意把最宝贵的创造力和想象力奉献给只把自己看作资源的公司。所以,聪明的管理者会想方设法把程序员这种创造力和想象力激发出来。
全部0条评论
快来发表一下你的评论吧 !