成为务实的程序员

本文是阅读《程序员修炼之道》后的一些感想和摘抄,个人觉得其中很多内容非常不错,所以分享出来,期望能和大家共同学习,努力成为一名务实的程序员。作为务实的程序员,你是编程技艺的行家。倾听需求,提供建议;传达信息,做决策。捕捉难以捉摸的需求,找到表达方式,让计算机轻松应对。记录工作,让他人理解;工程化,促进发展。努力在项目中创造奇迹。

”编程是一门技艺。简单地说,就是让计算机做你想让它做的事情(或是你的用户想让它做的事情)。作为一名程序员,你既在倾听,又在献策;既是传译,又行独裁;你试图捕获难以捉摸的需求,并找到一种表达它们的方式,以便仅靠一台机器就可以从容应付。你试着把工作记录成文档,以便他人理解;你试着将工作工程化,这样别人就能在其上有所建树;更重要的是,你试图在项目时钟的滴答声中完成所有这些工作。你每天都在创造小小的奇迹。“

——《程序员修炼之道》

务实(Pragmatic)这个词来自拉丁语 pragmaticus ——“精通业务”,该词又来源于希腊语 πραγματικός,意思是“适合使用”。

  • 早期的采纳者 / 快速的适配者:对技术和技巧有一种直觉,喜欢尝试。当接触到新东西时,你可以快速地掌握它们,并把它们与其他的知识结合起来。你的信心来自经验。
  • 好奇:倾向于问问题。热衷于收集各种细微的事实,坚信它们会影响自己多年后的决策。
  • 批判性的思考者:你在没有得到证实前很少接受既定事实。当同事们说“因为就该这么做”,或者供应商承诺会解决所有问题时,你会闻到挑战的味道。
  • 现实主义:你试图理解所面临的每个问题的本质。这种现实主义让你对事情有多困难、需要用多长时间有一个很好的感知。一个过程应该很难,或是需要点时间才能完成,对这些的深刻理解,给了你坚持下去的毅力。
  • 多面手:你努力熟悉各种技术和环境,并努力跟上最新的进展。虽然目前的工作可能要求你在某个专门领域成为行家,但你总是能够进入新的领域,迎接新的挑战。

当软件中的无序化增加时,程序员会说“软件在腐烂”。有些人可能会用更乐观的术语来称呼它,即技术债。

有很多原因导致软件腐烂。最重要的一个似乎是项目工作中的心理状态,或者说文化。无视一个明显损坏的东西,会强化这样一种观念:看来没有什么是能修好的,也没人在乎,一切都命中注定了。所有的负面情绪会在团队成员间蔓延,变成恶性循环。

不要放任破窗,及时发现及时修复,漠视会加速腐烂的过程。

软件开发中应该遵循的方法:不要只是因为一些东西非常着急,就去造成附带伤害。破窗一扇都嫌太多

不要为了追求更好而损毁了原有已经够好的。

我们做的东西,从用户需求角度出发是否足够好?最好还是留给用户一个机会,让他们亲自参与评判。无视来自用户方面的需求,一味地向程序中堆砌功能,一次又一次地打磨代码,这是很不专业的表现。心浮气躁当然不值得提倡,比如承诺一个无法兑现的时间尺度,然后为了赶上截止日期删减必要的边角工程,这同样是不专业的做法。

如果早点给用户一点东西玩,他们的反馈常常引领你做出更好的最终方案。

知识和经验是你最重要的专业资产。学习新事物的能力是你最重要的战略资产。

关于如何构建自己的知识组合,可以参考以下指导方针:

  • 定期投资:安排一个固定的时间和地点为你的知识组合投资。
  • 多样化:计算机技术变化迅猛——今天的技术热点可能到明天就接近无用(至少不那么受欢迎)。所以,熟悉的技能越多,越能适应变化。
  • 风险管理:分散风险,不要把所有的技术鸡蛋放在一个篮子里。
  • 低买高卖:在一项新技术变得流行之前就开始学习,可能和发现一只被低估的股票一样困难,但是所得到的收获会和此类股票的收益一样好。
  • 重新评估调整:这是一个充满活力的行业。你上个月开始研究的热门技术现在可能已经凉下来了。

对于那些已经构成知识组合的智力资产,获取它们的最佳途径可以参考如下建议:

  1. 每年学习一门新语言:不同语言以不同的方式解决相同的问题。多学习几种不同的解决方法,能帮助自己拓宽思维,避免陷入陈规。
  2. 每月读一本技术书:虽然网络上有大量的短文和偶尔可靠的答案,但深入理解还是需要去读长篇的书。当你掌握了当前正在使用的所有技术后,扩展你的领域,学习一些和你项目不相关的东西
  3. 还要读非技术书:不要忘记方程式中人的那一面,他需要完全不同的技能集。
  4. 上课:在本地大学或网上找一些有趣的课程,或许也能在下一场商业会展或技术会议上找到。
  5. 加入本地的用户组和交流群:不要只是去当听众,要主动参与。独来独往对你的职业生涯是致命的:了解一下公司之外的人都在做什么。
  6. 尝试不同的环境
  7. 与时俱进:关心一下和你当前项目不同的技术,阅读相关的新闻和技术贴。

批判性地思考读到的和听到的东西。

批判性地分析问题,先思考几个问题:

  • 问“五个为什么”:当有了答案后,还要追问至少五个为什么;
  • 谁从中受益:追踪钱的流动更容易理清脉络;
  • 有什么背景:每件事都发生在它自己的背景下,这也是为何“能解决所有问题”的方案通常不存在,而那些兜售“最佳实践”的书或文章实际上经不起推敲。
  • 什么时候在哪里可以工作起来:不要停留在一阶思维下(接下来会发生什么),要进行二阶思考:当它结束后还会发生什么?
  • 为什么是这个问题:是否存在一个基础模型?这个基础模型是怎么工作的?

优秀的设计比糟糕的设计更容易变更。

对代码而言,要顺应变化。因此要信奉 ETC(Easier To Change,更容易变更)原则。ETC 是一种价值观念,不是一条规则。关于培养 ETC 观念,有以下几点意见:

  • 不断强化意识,问自己这么做是否让系统更容易变更。
  • 假设不确定什么形式的改变会发生,你也总是可以回到终极的“容易变更”的道路上;试着让你写的东西可替换。
  • 在工程日志中记下你面临的处境:你有哪些选择,以及关于改变的一些猜测。

我们认为,想要可靠地开发软件,或让开发项目更容易理解和维护,唯一的方法就是遵循下面这条DRY(Don’t repeat yourself,不要重复自己)原则:

在一个系统中,每一处知识都必须单一、明确、权威地表达。

与之相对的不同做法是在两个或更多地方表达相同的东西。如果变更其中一个,就必须记得变更其他的那些。

DRY 不限于编码,DRY 针对的是你对知识意图的复制。它强调的是,在两个地方表达的东西其实是相同的,只是表达的方式可能完全不同。

  • 代码中的重复
  • 文档中的重复:例如注释和代码表达完全相同的意思,但是未来代码变更可能不会同步注释的变更,或者用数据结构表达知识等。
  • 开发人员间的重复
    • 最难检测到且难处理的重复类型,可能发在同一个项目的不同开发人员之间。整块的动能集可能会在不经意间重复,而这种重复或者好多年都并未发现,最终导致了维护问题。
    • 我们认为解决这个问题最好的方法是鼓励开发人员间积极频繁的交流。
    • 指派团队中的一人作为项目只是管理员,他的工作就是促进知识的传播。在源码目录树中设置一个集中的位置,存放工具程序和脚本程序。
    • 你要努力的方向,应该是孕育出一个更容易找到和复用已有事务的环境而不是自己重新编写

定义:对于两个或多个事物,其中一个的改变不影响其他任何一个,则这些事物是正交的。

当系统的组件相互之间高度依赖时,就没有局部修理这回事儿。我们希望设计的组件自成一体:独立自主,有单一的清晰定义的意图。但凡编写正交的系统,就能获得两个主要的收益:提高生产力和降低风险

可以用一个简单的方法可以测试设计的正交性。当你规划好组件后,问问自己:

  • 如果一个特别功能背后的需求发生显著改变,有多少模块会影响?
  • 你的设计与现实世界的变化有多大程度的解耦。 不要依赖那些你无法控制的东西。

当你写下代码时,就有降低软件正交性的风险。你不仅需要盯着正在做的事情,还要监控软件的大环境。有几种技术可以用来保持正交性:

  • 保持代码解耦:编写害羞的代码——模块不会向其他模块透露任何不必要的信息,也不依赖于其他模块的实现。
  • 避免全局数据:只要代码引用全局数据,就会将自己绑定到共享该数据的其他组件上。即使只打算对全局数据进行读操作,也可能引发问题(例如突然需要将代码改为多线程的情形)。一般来说,如果总是显式地将任何需要上下文传递给模块,那么代码会更容易理解和维护。
  • 避免相似的数据:可以看看《设计模式》中的策略模式。

修 Bug 也是评估整个系统正交性的好时机。遇到问题时,评估一下修复行为的局部化程度。只要变了一个模块,还是有很多处变更分散在整个系统里?当你修正了一个地方,是不是就修复了所有问题,还是会神秘地出现其他问题?

使用曳光弹找到目标。

对于我们来说,最初的曳光弹就是,创建一个简单的工程,加一行“hello world!”,并确保其能编译和运行。然后,我们再去找整个应用程序不确定的部分,添加上让它们跑起来的骨架。

使用曳光弹代码的优势

  • 用户可以更早地获得能工作的东西。
  • 开发者构造了一个可以在其中工作的框架。
  • 你有了一个集成平台。
  • 你有可以演示的东西。
  • 你对进度有更好的感觉。

务实的程序员会为自己的错误建立防御机制。

  • 客户和供应商必须就权利和责任达成共识。
  • 我们想要确保在找出 Bug 的过程中不会造成破坏。
  • 为你所做的假设编写主动校验的代码。
  • 只要我们总是坚持走小步,就不会才能够悬崖边掉下去。

与计算机系统打交道很难,与人打交道更是难上加难。而契约规定了你的权利和责任,同时也规定了对方的权利和责任。文档化及主张进行检验是契约式设计的核心。

在编写代码之前,简单列出输入域的范围、边界条件是什么、例程承诺要交付什么——或更重要的是,没有承诺要交付什么——这对编写更好的软件来说,是一个巨大的飞跃。

尽快检测问题的好处之一是,可以尽早崩溃,而崩溃通常是你能做的最好的事。一旦代码发现本来不可能发生的事情已经发生,程序就不再可靠。这一刻开始,它所做的任何事情都是可疑的,所以要尽快终止它。

一个死掉的程序,通常比瘫痪的程序,造成的损害更小。

无论何时,你发现自己在想“当然这是不可能发生的”时,添加代码来检查这一点,最简单的方式就是使用断言。注意这里的断言检查的是不可能发生的事情,普通的错误处理不要使用断言。

大多数情况下,资源使用遵循一个可预测的模式:

  • 分配资源
  • 使用它
  • 然后释放它

把反馈的频率当作速度限制,永远不要进行“太大”的步骤或任务。

当你不得不做下面的事情的时候,你可能陷入了占卜的境地:

  • 估计未来几个月之后的完成日期
  • 为将来的维护或可扩展性预设方案
  • 猜测用户将来的需求
  • 猜测将来有什么技术可用

传统观点认为,一旦项目到了编码阶段,就几乎只剩下一些机械工作:只是把设计翻译成可运行的代码段而已。我们认为这种态度是软件项目失败的最重要原因。编码不是机械工作。务实的程序员会对所有代码进行批判性思考,包括自己的代码。我们不断看到程序和设计的改进空间

倾听自己直觉的方法:

  • 首先,停止正在做的事情。给自己一点时间和空间,让大脑自我组织。远离键盘,停止对代码的思考,做一些暂时不需要动脑筋的事情——散步、吃午饭、和别人聊天,或是先睡一觉。让想法自己从大脑的各个层面渗透出来:对此不用很刻意。最终这些想法可能上升到有意识的水平,这样你就能抓住一个”啊哈“的时刻。

  • 如果这些不起作用,就试着把问题外化。把正在写的代码涂画到纸上,或者向你的同事(最好不是程序员)解释一下怎么回事儿,向橡皮鸭解释下也行。把问题暴露给不同部分的大脑,看看有没有一部分大脑能更好地处理困扰你的问题。

代码需要演化:它不是一个静态的东西。

马丁.弗勒将重构定义为:重组现有代码实体、改变其内部结构而不改变其外部行为的规范式技术。

  • 这项活动是有规范的,不应随意为之。
  • 外部行为不变;现在不是添加功能的时候

当你学到一些东西时,当你比去年、昨天甚至是十分钟前更了解某事时,你会重构。

无论问题是多是少,都有可能促使我们对代码进行重构:

  • 重复:当你发现一处违背DRY原则的地方。
  • 非正交设计
  • 过时的知识:事情变化来,需求偏移了,你对问题的了解更多了——代码也需要成长。
  • 使用:当系统在真实的环境中被真实的人使用时,你会意识到,与以前的认识相比,一些特性现在看来更为重要,反而 “必须拥有” 的特性可能并不重要。
  • 性能:你需要将功能从系统的一个区域移动到另一个区域以提高性能。
  • 通过了测试重构应该是一个小规模活动,需要良好的测试支持。如果你添加了少量代码,并且通过了一个额外的测试,现在就有了一个很好的机会,来深入研究并整理刚刚编写的代码。

尽早重构,经常重构。

重构的核心是重新设计。你或团队中的其他人设计任何东西的时候,都可以根据新的事实、更深的理解、更改的需求等重新设计。但是,如果你执拗地非要将海量的代码统统撕毁,可能会发现,自己所处的境地,比开始时更加糟糕。

重构是一项需要慢慢地、有意地、仔细地进行的活动。马丁.弗勒提供了一些简单技巧,可以用来确保进行重构不至于弊大于利:

  • 不要试图让重构和添加功能同时进行
  • 在重构开始之前,确保有良好的测试
  • 采取简单而慎重的步骤:将字段从一个类移动到另一个类,拆分方法,重命名变量。重构通常设涉及对许多局部进行的修改,这些局部修改最终会导致更大范围的修改。如果保持小步骤,并在每个步骤之后进行测试,就能避免冗长的调试。

本文只是分享了书中的一部自己觉得比较好的、可以实际使用的观点,并非书中所有观点),强烈建议大家读一下本书,相信会有不错的收获。最后贴个大佬对本书的评价,我也是因为刷到这个所以才知道此书的。