学习最好的方式,是有个好师傅。他根据你的不同阶段,教导你不同的技能,循序渐进;师傅不单教你练功,还会教你做人,使你内修于心,外化于形。教你的一些道理,你可能当时不太懂,但等你苦练多日,历经曲折,终有一日茅塞顿开,再去学艺做事,事半功倍,大有精进; 有一个位好导师自然是得之我幸的事情,但实际工作中很难得,也许有前辈们偶尔的点拨,有朋友的激励,但最平实可靠的方法还是来自于阅读。
面对这份职业,你应如何开始?
和程序员读的绝大多数应用书籍不同的是,开篇给你讲的就是态度。对待错误、不整洁的代码、团队协作以及学习。
“我的源码让猫给吃了。”
1.我们为我们的能力自豪,但对于我们的缺点---还有我们的无知和我们的错误---我们必须诚实。
谁都会犯错,最好方式是承认自己的错误,并给出可行的弥补方案。而不是找荒唐的借口。
2.不要留着“破窗户”(低劣的设计、错误决策、或上糟糕的代码)不修。发现一个修一个。
而面对不整洁的代码,就犹如破窗户一样。应该发现一个就修一个。这和我们第一期读的《代码整洁之道》异曲同工,坏代码会污染环境,最后会坏掉整个项目。保持整洁的习惯,发现脏代码就要及时纠正。
文中说了一个石头汤的故事,几个士兵用一锅石头,招呼村民各自出料,结果炖出了一锅香喷喷的菜。而映射到生活中,大部分的项目都要经历从0到1的过程,做好它就需要各方出力。虽然每个人都会保卫自己的资源,但人们发现,参与正在发生的成功更容易,让他们瞥见未来,你就能让他们聚集在你周围。让我想起一句话:“如果你想造一艘船,不要鼓励人们去伐木、去分配工作、去发号施令。你应该做的是,教会人们去渴望大海的宽广无边和高深莫测。——圣-埃克苏佩里”
人们需要的是引导,而不是约束。而面对学习,面对日益变迁的技术呢?知识管理就如金融投资
3.知识上的投资总能得到最好的回报。你的知识和经验是你最最重要的职业财富。遗憾的是,他们是有“时效的资产”随着新技术、语言及环境的出现,你的知识会变的过时。
我们喜欢把程序员所知道的关于计算机技术和他们所工作的应用领域的全部事实、以及他们的所有经验视为他们的知识资产。管理知识资产和管理金融资产非常相似:
1)严肃的投资者定期投资----作为习惯。
--定期为你的知识资产投资。
2)多元化是长期成功的关键
--掌握的技术越多,越能进行调整。
3)聪明的投资者在保守的资产和高风险、高回报的投资之间平衡他们的资产。
-- 把所有金钱买入可能突然崩盘的高风险股票不是一个好主意,也不要把所有的技术鸡蛋放在一个篮子里。
4)投资者设法低买高卖,以获得最大回报。
-- java刚出现时学习它可能有风险,但对于已经步入改领域顶尖者,已经取得了非常大的回报。
5)应周期性的重新评估和平衡资产。
-- 重温学过的东西。
作者给出三点建议:
1)每年至少学习一种新语言
2)每季度阅读一本技术书籍
3)也要阅读非技术书籍
有哪些可以实践?
思想上有了一定的认识,接下来怎么做呢?
1.DRY(don't repeat yourself)原则:系统中的每一项知识都必须具有单一、无歧义、权威的表示。
避免重复的主要原因是要减少维护的成本。文中的重复不仅只代码上的重复,还包括多余的注释、多余的设计(属性);鼓励多阅读别人的源码与文档,不管是正式的还是非正式的,去学习,同样也不要因为别人阅读你的代码而苦恼。
2.如果组件是相互隔离的,改变其中之一而不必担心其余的组件。编写正交的系统可以提高生产率和降低风险。
正交性在计算机中表示某种不相依赖性和解耦性。如果一个事物发生变化而不影响其他事物,那么这些事物就是正交的。正交的系统更容易测试,能促进复用;正交性的另一个有趣的变体就是面向方面编程,也就是大家熟悉的(Aspect-Origin Programming)AOP;在项目团队中,如果团队的组织有许多重叠,每一次改动都会需要整个团队开一次会,他们中的所有人都会受影响。这也是一个正交问题。
3.原型设计的目的就是回答一系列问题,与投入的使用的产品相比,它们的开发要便宜的多,快捷的多。原型制作是一种学习经验。其价值不在于产生的代码,而在于所学到的经验教训。
比如汽车制造商也许会制造一个粘土模型用于测试空气风洞实验,做一个轻木模型测试工艺。原型和曳光弹的区别是,原型代码在使用完可以丢掉。
4.太简单的语言也许容易解析,但却可能晦涩难懂,所以建议先就采用更复杂、可读性更好的语言。最初的努力将在降低支持与维护费用方面得到许多倍的回报。
越底层的语言效率越高,但也是越不容易理解。不光性能,时间是一个昂贵的成本。
工欲善其事必先利其器
1.工具放大你的才干。你的工具越好,你掌握的越熟练,你的生产力越高。
比起了解多种开发工具(编辑器),你应该有一种很熟悉的,快捷键,命令行,会使用第三方插件等等。
2.工具是手的延伸,建议最好精通一种编辑器,好的编辑器应该是可配置、可扩展、可编程、自动缩进、类IDE
你可能想起那宇宙第一IDE,让你麒麟臂隐隐发作。
3.如果你使用GUI完成所有的任务,你就会错过你环境的某些能力。GUI的好处是所见即所得,缺点就是所见即全部所得。
不要太依赖于界面操作,记住一些常用的命令,非常有助于提高效率。
4.源码控制的诸多好处之一,它是一个巨大的撤销键。好的源码控制系统可以跟踪变动,谁改动了这行代码?版本之间的区别?哪个文件改动的最频繁。还能管理开发树种的分支,比如有稳定版本,新功能版本,预发布版本。
编码不只是一个人事情,也不只是当下的事情,熟用一种源码管理技术(git/svn)来记录你的代码。
5.要修正问题,而不是发出指责;要找到问题的根源,而不是问题的特定表现
我们都曾会遇到类似这样的对话:A:“xx出问题了”,B:"我电脑上没问题啊"... ,bug是你的过错还是别人的过错,并不是真的很有关系。它仍然是你的问题。调试的时候可能遇到抵赖、推诿、撇脚的借口、甚至是无动于衷,这就回到了第一章第一节的态度问题,要接受事实:调试就是要解决问题。调试需要思考的问题
1)正在报告的问题是底层bug的直接结果,还是只是症状?
2)bug的位置在哪?是你代码的问题,还是调用的组件有问题。
3)你如何向同事详细解释这个问题?
4)如果可疑代码通过了单元测试,测试是否足够完整?
5)造成bug的这个条件是否还存在系统的其他地方?
6.编写能编写代码的代码
木匠面临重复制造一样东西的任务时,他们会取巧,制作夹具或模板,这样就可以反复制作某样工件。夹具带走了复杂性,降低了出错的机会,从而让工匠更自由的专注质量问题。同样,合适的是你可以使用代码生成器增加你的威力。
更上一层楼
1.与计算机系统打交道很困难,与人打交道更困难,我们花费在弄清楚人们的交往问题上的时间更长;确保双方坦率的最佳方案就是合约。合约规定了你的权利和责任,也规定对方的权利和责任。此外还有关任何一方没有遵守合约的后果的约定。
按合约设计,用文档记载程序的声明,并进行校验软件中的每一个函数或方法都会做某件事情,开始和结束都需要满足一定的条件,继承和多态是面向对象语言的基石,是合约可以真正闪耀的领域。
2.有许多时候,让程序崩溃是你的最佳选择,而不是绕过继续执行、甚至把坏数据写入到某个重要的数据库。
死程序不说谎。JAVA或者C#的异常如果没有捕捉,就会渗透到程序顶部,直到程序终止并显示堆栈信息。死程序带来的危害要比带有疾患的程序小得多。
3.异常很少应该作为程序的正常流程的一部分使用;异常应该保留给意外事件。
例如,如果你用代码试图读取一个文件,而文件不存在,应该引发异常吗? 这要看实际情况,如果文件应该在那里(重要配置文件),那么异常就应该发生--某件意外之事发生了。另一方面如果你不清楚文件是否存在,那么你找不到这个文件就不算异常情况,返回错误提示就是合适的。
4.只要在编程,我们都要管理资源:内存、事务、线程、文件、定时器--所有数量有限的事物。许多开发者对资源的分配和解除没有一个计划。
写代码要有始有终:比如文件的读取,每一次的打开都有对应的关闭。对于一次不止需要一个资源的程序,要有与资源分配次序相反的次序解除资源分配;不同的地方引用同一组资源时要按照同样的顺序(比如A申请了resource1,正申请resource2,而B申请了resource2,正申请resource1,那么这两个进程会永远的等下去);在异常处理中,确保程序退出前要释放资源。JAVA或者C#中的finally块使得我们在异常发生时总能做一些处理;另外对资源的使用情况进行检查也是个好注意。
5.创建灵活代码的一个关键概念是数据模型(model)与该模型的视图(view即表现)的分离。
有许多不必要依赖关系的系统非常难维护(而且非常昂贵),往往高度不稳定。要使模块直接的耦合减至最少。但解耦也是有代价的,在实践中有时候需要编写大量包装方法,只是把请求转发给被委托者,这些方法会带来运行时代价。所以也会有折中,通过反其道而行之,使模块紧耦合来换取性能的改进。所以不要为了解耦而解耦,要平衡。
6.大多数人的思考方式都是线性的,先做A再做B,这样就带来了时间上的耦合,我们需要考虑并发,并解除任何时间上或次序上的依赖,这样就能获得灵活性。
在设计时就考虑了并发,可以更容易的满足可伸缩性或性能需求。
7.元数据是关于数据的数据。是任何对应用进行描述的数据
Asp.net mvc中的模型元数据(modelmetadata)就是一个很好的例子.元数据是用于做高度可配置的系统,诸如算法、数据库产品、中间件技术和用户界面风格等作为配置选项,而不是通过集成或工程实现。
当你编码时...
工作如果只是机械的把设计转换成可执行语句,这种态度是许多程序丑陋、低效、结构糟糕、不可维护和完全错误的最大的一个原因。
1.学会估算自己算法的时间,并找到合适的算法。比如集合很小时,直接的插入排序的性能将和快速排序一样好,而你编码和调试时间将更少。
根据算法的复杂度可以估算开销的时间。比如一些常见算法,简单循环:很可能就是O(n) 时间随着n线性增加;嵌套循环:如果在循环中嵌套了另外的循环,算法的复杂度就是O(m*n),也就是O(n^2);二分法:你的算法在每次循环的时候把事物一分为二,那么复杂度就是对数型O(log(n));分而治之:划分输入,并独立的在两个部分上进行处理,然后把结果组合起来的算法很可能是O(nln(n));组合:只要算法考虑到事物的排列,其运行时间可能失去控制。如果涉及到阶乘,5!=120。
2.重写、重做和重新架构代码合起来,称为重构。
出现下面的特征,你就应该考虑重构代码:
1)重复,你发现违反了DRY原则
2)非正交的设计(一个模块改动会影响另外的模块)。
3)过时的知识,事情变了,需求转移了,你对问题的理解加深了。代码需要跟上这些变化。
4)性能。为改善性能,你必须把功能从系统的一个区域移到另一个区域。
3.测试你的软件,否则你的用户就得测试。测试是技术,但更是文化。不管用什么语言,尽量让这样的文化渗入到项目中。
编写易于测试的代码,并不要依赖于测试人员来测试。代码交出去或提交之前要确保它是ok的。
谈谈项目和需求
你是否有过你的项目注定要失败的感觉,也许你现在可以建议结束它,并且为出资人省下一些钱。
1.挖掘需求的时候,要找出用户为何做特定事的原因、而不只是他们目前做这件事的方式。你的开发必须解决他们的商业问题,而不只是满足他们陈述的需求。
2.成为产品的用户。与用户一样工作,一样思考。
我们很容易被吸进“只是再增加一个特性”的巨大漩涡,往往需求方变了又变。但我们工作的最终目的是把事情做成,而不只是把代码敲完。要理解需求,理解用户,才会做出好的产品。
3.不要在盒子外面思考,而是要找到盒子。
有时候项目设计没有头绪,或者是发觉代码难以编写,或者问题看起来无法解决。而这里的‘盒子’指的是各种约束和条件的边界,而解开谜题的关键就在于确定加给你的各种约束,并且确定你拥有的自由度,这样你才会在其中找到解决方案。
4.不要做形式方法的奴隶。
我们喜欢有些技术和方法,但我们相信,盲目地采用任何技术,而不把它放进你的开发实践和能力环境中,这样的处理方法肯定会让你失望。
记住,注重实效
1.质量是一个团队问题,最勤勉的开发者如果派到不在乎质量的团队里,他也很难保持修正琐碎问题的热情。团队作为一个整体,不应该容忍破窗户(小小的,无人修正的不完美),必须为产品的质量负责。
产品的质量就是团队的招牌。
2.人工流程不能保证一致性,也无法保证可重复性。
推动团队内部的自动化,比如项目编译是一件应该可靠、可重复地进行的琐碎工作,.net下可以使用ccnet。写程序的不要让程序所累。善用工具或者自己开发工具,采用自动化方案。
3.寻找bug就像是结网捕鱼,我们用纤细的小网(单元测试)捕捉小鱼,用粗大的网(集成测试)捕捉吃人的鲨鱼。有时候鱼会设法逃跑,所以为了抓住项目池塘里游动的,越来越狡猾的鱼,要补上任何发现的漏洞
这个比喻好。测试分为三个方面,测试什么、怎样测试以及何时测试。
测试
4.设法将文档制作成在线发布的形式,这样让读者可以了解到哪些内容是最新的、哪些是更改过的。
类似于javaDoc和DOC++这样的工具,可以根据源码生成API级别的文档。文档和代码是同一底层模型的不同视图,不要让文档变成二等公民。.net中可以用sandcastle根据xml注释生成文档,也可以打造成在线的方式。文档做好了,是一个前人栽树后人乘凉的举措。
5.项目的成功是由它在多大的程度上满足了用户的期望来衡量的。用户一开始就会带着对他们所需东西的想象来到你面前,那可能不完整、不一致或者是在技术上不可能做到。但就像过圣诞节的小孩一样,他们投入了一些感情,你不能简单的忽视它。
后面的章节俨然已经上升到项目经理的视角,虽然只是作为团队中的一员,拥有这些意识才是好的团队意识,而不是简单我很好说话就代表别人能和我合作愉快。
6.鼓励在自己的作品上签名,“这是我编写的,我对自己的工作负责。”
---恢复内容结束---