程序员必备的30条防御式编程思想原则 - 滴滴技术

在分析若干线上问题之后,会发现很多问题并不是非常罕见的难题,而是由一个个较为明显的小错误积累而来,为什么会犯小错误?或许是因为侥幸心理,或许是因为只看到了局部的利益。从某一个例子来看,确实会有“偶因一着错,便为人上人”的侥幸,但是统计大量案例可以发现,大部分情况并不会有正面的结果。

本文汇编我在需求评审、编码、项目上线几个阶段总结的原则,这些原则虽然不能直接解决问题,但是可以尽可能地优化每一步行为,从防御式编程思维方式、行为习惯层面尽量避免错误的发生。

交流沟通时的原则

不同场景,使用不同的沟通工具

通过IM(即时通讯工具)打字是常见的沟通方式,成本低,适合简单问题的沟通以及团队内各种信息的通知。但如果一个复杂的问题,打字3分钟还说不清楚、无任何进展,应该立刻换一种更高效的沟通方式,比如语音或面谈。

语音沟通相比文字沟通,有了语气,可以避免纯文字语气生硬的问题,而且在单位时间内有更多的信息输出量,效率更高。有时会看到两个人在群里一人一句,讨论了5分钟,结果发现说的不是一件事情。如果及时换用语音,可能1分钟就发现问题所在了。

见面沟通相比语音,增加了表情,能更好地表达强烈的诉求。特别是遇到非常紧急的问题时,直接见面不失为最好的方法。有时候都出线上问题了,还在等别人通过IM回复,可能会影响最佳的止损时间。

避免确认偏误,反对的观点可能会带来更多启示


确认偏误,指的是只听“好”消息——即能印证自己观念的消息,而对“坏”消息赶尽杀绝——拒绝接受与自己观念不同的消息、想法。这种现象会导致自己无法接受一个长远有益的反对意见。

这种情况在大数据时代看信息流的时候会更明显,经过精准的算法,在有一个观点、喜欢某类观点的时候。精准的推荐算法,只为持续推荐给自己认同观点的信息。不会有反面的信息来纠正错误的观点。这样强化错误的观点,会让自己处于信息茧房之中无法自拨。

这是因为大脑的结构非常复杂,由理性的我和感性的我组成,对于别人的反对意见,感性的我会默认把别人的反对意见当成攻击。

所以我们就要训练感性的我,对于别人的观点,要仔细思考,再做行动。反对的观点,可能道出了自己想法中潜在的风险,甚至是一个更好方案的启蒙。通过训练主动辩证思考的能力,区分“听起来舒服的观点”与”对自己长远有利的观点“。

举例论证,只能作为决策的参考意见**无论是自己思考问题,还是讨论问题的时候,经常会出现以下两种观点。

  • 上一个项目,我使用了技术方案A,效果非常好,所以这个项目也使用技术方案A吧。
  • 我有一个同学,遇到这样的问题,他就是这样解决的,所以我们也可以这样做。

把一次偶然的成功,当成必然的结论的现象。既可能出现在讨论中,也可能出现在自己的经验中。这些经验可以当成决策的一个参考案例,但不能直接当成必然的结论。科学的方法应该根据不同的场景,不同背景,做大规模的统计,这样才能根据不同场景做更好的决策。

创意择优,自己调整权重再做决策


无论是技术方案的讨论,还是一个有争论问题的交流,大家都会从自己的角度有不同的观点。每个人都有自己更关注的点,所处位置不同,关注点则不同。如果需要自己做决策,那么在讨论的过程中,要理解每一个想法背后的原因,这样才能明白其底层逻辑,从自己的角度判断其权重。综合考虑不同角度的想法,这样才能站在自己的角度,更全面地考虑、决策问题。

李开复在选择毕业论文课题时,就涉及风险与稳定两类课题的决择。

  • 如果选择深度学习,可能会延期毕业,风险高,但是如果成功了则一举成名。
  • 如果选择普通的课题,能按时毕业,无风险,但是不会取得非常大的成就。

对于不同人,承受的风险是不同的,那么他们的选择也会不同,当然了,其长远收益也不同。

打字沟通时,重要的内容单独发一条消息**对于大段的文字内容,对方不一定会仔细看每一句。重要的问题,单独发一句,不和其他内容合并在一起。

实例1:在排查一个问题时,一句话问了2个问题,对方回答“是”。我根据对方的结论,继续排查问题。排查了一天,我感觉问题B的情况不应该是B1,再次找对方确认,结果对方说他只看到我说的前一句,并没有看到后一句"问题B的情况是B1吧?"。

实例2:有一次项目上线之后,我给合作方发了一句话以表示感谢,对方回了我一句:“需要我配合排查什么问题?”

会议纪要,在跨团队合作时更能凸显价值


会议主持人应即时记录会议结论与待办事项,否则会出现几天、几周之后,大家印象中的结论不同而导致冲突,甚至出现遗忘重要待办事项的情况。

对于内部团队,出现这种问题并不会有太大的问题,毕竟大家长期合作,一点点失误也会被包容,但不能因为经常被包容而没有这个习惯。因为在更大的共同体,公司横向团队的合作,甚至与外部公司合作的时候,这个问题带来的负面影响就会成倍地放大。

对于一些定制化功能的三方供应商来说,他们会对接很多团队,他们对外部沟通时,并不是研发,而是其他岗位的同学,比如销售或商务。他们在对接时难免会因为理解偏差而作出错误的回答,如果根据错误的结论,设计出一个不合理的技术方案,等到项目上线前期才发现问题,在没有会议纪要的情况下,很难证明是对方说错了,还是自己理解错了。

需求评审与排期阶段的原则

人人都是产品经理,需求决定方向


软件开发,是为了满足用户的需求,解决实际问题,创造价值,并不仅仅是为了被安排的一个个需求任务。研发不仅要按照产品经理的需求写代码,也要思考与批判其合理性。只有身处一名用户的角度,仔细分析产品的合理性,才能写出更有价值的代码,而不仅仅是把写代码当成一种体力劳动的交换。

技术是手段,而非目的

需求分析与系统设计是两个递进的步骤。

在需求分析阶段,不应该考虑详细的技术细节,否则就会出现“因为技术太困难,所以这个需求做不了”或“这个工作量太大了,改一下需求吧,这样工作量就减少很多”的现象,导致一个优秀想法的流产。刚开始就从技术的角度,而不是从用户真实需求的角度来实现一个更有价值的产品。产品的价值应该根据用户的需求来决定,而不是技术的难易。当然并不是不考虑技术实现的成本,而是应该有先后顺序,确定需求之后再确认技术实现的成本,毕竟即使技术成本很高,其ROI也可能更高。

虽然有些需求,用某些技术方案,成本非常低。几个小时就做完一个需求,但如果是无意义的需求与无意义的技术方案,做再多又有什么用呢?

简化步骤的产品,有更大价值对于需要用户交互的产品,用户操作成本越低,时间成本越低,价值越高。这就需要在考虑产品的时候能在保证功能不变的前提下,尽量多的删除冗余功能,而不是增加功能,虽然增加功能更容易。

跨团队合作,内部排期全部对齐之后再与外部沟通从个人,到团队,到整个公司,再到外部公司,共同体逐渐增大,沟通成本也会逐渐增加。对于一些需要统一结论的问题,比如项目的排期,应该根据共同体的规模从小到大逐步确定。避免和外部团队确定了一个项目上线的日期,结果发现内部时间无法匹配,后续再与外部团队调整。一方面沟通成本会很大,另一方面也暴露了内部流程的混乱。

倒排排期需求,注意节假日,并保证联调时间有时候难免会遇到非常紧急的需求,需要倒排排期。如果一定要压缩时间,那就压缩开发时间,要保证联调时间足够。因为联调才能更快地暴露问题。提前暴露问题,才能尽早地协调资源,解决问题。


开始联调的日期尽量不要定在大型节假日前1天,特别是涉及多团队的大项目,因为一个人休假导致无法联调,最终导致整个流程都无法及时开始联调。

不要给合作方评估工作量


有涉及和兄弟团队,甚至外部公司合作的时候,可能会遇到需要对方增加或修改一个功能的情况。从自己的角度看,这个功能只需要几分钟就能完成,但只要自己没有改代码的权限,就不要从自己的视角给对方评估工作量。

一方面每个人对每个功能的熟悉程度不同,对方可能刚接手代码,也不是非常熟悉。另一方面每个团队都有自己的流程,完成整个功能的修改,边际成本并不低。因为不仅仅需要研发修改代码,满足当前功能,还要考虑拓展性与稳定性,甚至测试等团队的协作。

开发阶段的原则

=======

完善的文档,是拥有长远战略眼光的表现

把可能涉及项目的整个团队当成一个共同体,而不仅仅是自己一个人。相比于自己写一次文档的局部视野,可以从更全局的视野来看长远收益。对于多团队合作的需求,如果不记录,看似一个不重要的参数,可能会涉及大量团队的沟通与协作的成本。虽然在维护他人项目的时候,可以看代码或注释来了解整个项目。但是一些复杂的项目,较复杂的逻辑,很难从代码中看出来整体结构,这时候文档的重要性就显示出来了。

文档的维护确实需要成本,但这是一个短期的成本。长期来看,有了文档,可以避免以后更多的沟通交流成本,避免花更多时间看一个陌生的逻辑的时间。相比编码完成之后推倒重来,文档的修改成本并不高。

面对短期与长期的天平的两端,你愿意把砝码放在哪一端?人生是一场马拉松,虽然不写文档会有短期内的轻松。但是从长远角度来说,目光长远,从更宽广的视角,拥有长远战略眼光,其实更重要。

另外,有人认为文档确实应该写,但是仅限于复杂的项目,简单的项目没必要写,其实简单的项目正是训练习惯的一个好机会。如果从来没有写文档的习惯,那么在Own大型项目的时候,就会发现非常不习惯写文档,在时间紧张,功能复杂,涉及团队非常多的项目时,重压之下,动作就容易变形。想要保证动作连贯行云流水,就要在平时养成好习惯。

维护文档的同时,也要维护文档索引


如果是单独一个项目的文档,其结构比较单一,即使迭代多个版本,其整体结构也比较清晰。但如果是一个团队大家一起记录,对于一个新主题页面的添加,不同的人会添加到不同的层级,下次在查找的时候非常困难。此时索引的重要性就很明显了,即文档的添加,不仅仅是单独一篇文档的添加,还需要有相应索引的维护。

这其实涉及到现代电子文档存储的一个问题,种类多,分类灵活,层级变幻无穷。随着团队规模的扩大,项目的增加,无序状态会持续导致熵加。想要更整洁,索引必不可少。

编码时间占比应低于30%

一个项目从需求评审、技术方案调研、书写文档、编写代码、测试、上线。纯粹编码的时间占比并不大。特别是在设计完美,一气呵成写完代码的情况下。如果编码时间占比很大,大概率是因为在编码的过程中写了一半、突然不知道后面怎么写了,或者写完之后,才发现设计有严重缺陷,需要推倒重来。

代码需要别人能看懂,而不仅仅能正常运行
上学时期,一个课程设计的项目,只要能正常运行即可。至于老师能不能看懂我们变量命名是什么意思,并不重要。因为这个项目,以后再也不会有人关注了。但是在企业项目中,一个项目不仅仅是要自己维护,还涉及到团队内其他同学来维护。因此代码的要求就不仅仅是能运行了,还要能让别人看懂。

先写注释,再写代码


注释不是补出来的,而是在编码的时候就应该写好的。

代码和注释是密不可分的统一体,类似知行合一的思想,知和行是密切结合在一起的,两者不可分割。几秒钟打几个字写一些注释,会为以后查看节省更多的时间。有人可能说我能看懂就行,以后看不懂也是别人看不懂,和我没关系。即使只从自己的角度来看,这个还真不一定,不信可以看看自己2个月之前写的代码。况且从更大的共同体利益来看,写注释并不是为了自己,而是为了整体团体的利益。如果刚开始不习惯写代码的同时写注释,可以在写代码之前先写注释。

外交无小事,对外的接口,变量的更改,要尽早同步

对外的接口协议,要慎重,确定之后不应该修改了。但是如果因为早期考虑不全面,必须修改,那就尽早修改。虽然自己全局搜索替换成本很低,但是如果涉及合作方的修改,特别是在测试后期,沟通成本可能要再高一个量级。周知上下游,越早改,大家的相对成本越低。

模块、函数遵循单一目的的原则


每个函数只做一件事,每个模块功能尽量单一。每个模块对外暴露相应的接口,做到“高内聚,低耦合”。说起来似乎很简单,但是如果在需求第一个版本就写出高耦合的代码,就会导致后续维护不可控,出现一个类上千行的现象。后期想要再改,成本呈指数级上升。每个函数,尽量不超过30行。每个类,尽量不超过900行。

命名要表里如一,见名知意


变量是代码中最小的单位,命名的好坏与可读性好坏有较大关系。比如命名是keyString,看意思应该是key值的字符串,如果在使用的时候用的是value,这会给别人阅读造成很大的障碍。

编码:不要使用特殊技巧


非常复杂的炫技代码,其价值在可读性面前不值一提。如果能开源,那也算是秀出风采,否则就是自嗨。

Commit message 虽不起眼,但蕴藏大信息


Commit message可以当成一个索引,不仅方便回顾迭代的流程,还方便回滚。每一个commit message,都应该和模块、函数的单一目的类似,尽量只有一个功能的修改。并且commit message最好有固定的格式,比如[分支名][修改的内容]。想要更详细的话,可以再加上[代码类型],区分是功能,还是bug的修复。甚至可以再加上[bugId],可以链接到相应的bug空间。这种习惯在集成,回滚时就体会到价值。

代码评审,是输入、输出之后的反馈,是进步最快的方式之一


代码评审:别人发现我的错误时,应该心存感激,而不是试图辩解。正如同健身的时候,先看书中的理论,再实践,最后找教练反馈。有时候我们认为自己做的动作是对的,但是在输出之后找教练反馈,可以纠正很多不注意的细节导致的错误。代码评审也是如此,有时候认为自己写得完美无缺,但实际可能漏洞百出。

If else平衡,失败的数据也需要


对于一些逻辑的处理,输出成功结果进行透传。虽然只需要处理成功的情况,但是失败的情况也要处理。失败的数据虽然暂时没用,但可以统计失败率,以及分析问题。

测试阶段的原则

=======

涉及用户操作的产品功能,交叉测试更容易发现问题

每个用户都有不同的操作习惯,对于涉及用户操作的产品功能,如果一直是同一名QA测试,可能因为其固定的操作习惯,导致不容易发现一些隐藏的bug。这个时候尽可量多的“用户”操作,从不同的操作习惯来体验,则更容易发现问题。如果能进一步扩大范围,不同岗位的同学一起使用、体验、吐槽,则能更早地发现真实用户的体验问题。


无法解决的困难问题,试试研究的思路有时候会遇到一个很困难的问题,一天都解决不了。主要是两种情况:

  • 思路错了。
  • 未知的知识太多了。

如果是思路错了,这个时候不妨以退为退,想一想为什么要开发这个功能,甚至退回到需求层面,是不是可以有其他更简单的解决思路。从更高的视野来看,可以避免陷入局部细节而无法自拨。

如果是未知的知识太多,可以不直接解决这个问题,而是带着研究的想法,把相关的知识全部学一遍,有可能这个困难就迎刃而解了。如果时间有限,可以问别人,并做好记录,后续再亲自总结,抓住解决问题所带来的成长机会。

上线之后的原则

=======

以正合,以奇胜


几乎没有靠运气的出奇制胜,只有以拥有足够正面实力为前提,并且有一些额外的力量,才能取得最终的胜利,在做项目时也是如此。

要想更快,更高效地完成一个项目,离不开日常的积累。项目上线不是结束,而是成长的开始。一个项目可以提供很多经验与素材,当成自己输入、输出的一种反馈,找到不足之处,避免下次犯同样的错误。

在公司光环之下成长,而不是单打独斗

闲暇时间干什么,找个副业?短期来看,确实能有一些额外的收入,但长远来看,即使多出来30%的收入,并不会有本质的变化,无非是能更换一些更高档的电子产品、服饰、日用品。个人的力量太弱小,单打独斗不如在公司的光环下,更高效完成项目的前提下获得个人的成长。

一方面是因为公司的平台有更多资源,另外基于公司项目的成长,边际交付成本较低。基于现有的项目,深挖与之相关的底层原理,这样才能更快的积累与成长。在做简单项目的时候,底层原理的作用不明显,但对于非常复杂的项目,需要底层原理支撑的时候,自己积累沉淀的技术就会发挥出明显的效果。

面对线上问题,焦虑毫无意义,重要的是寻找当下解决方案

面对线上问题,担忧过去或焦虑未来都没有用。关注当下,冷静积极地寻求解决方案。失败既能化为动力,驱动个人成长,也能毁掉自己,这取决于我们如何应对失败。能打败自己的不是任何人与事,而是自己。出了线上问题,有两件事情要做:

  • 针对现状,如何解决。
  • 复盘分析原因,避免下次犯同样的错误。

针对"线上已出现了事故"的现状,需要立即寻找解决方案,这是遇到线上事故时优先级最高的事情。止损非常重要,特别是损失在不停的扩大时,此时每一步操作都非常重要,如果问题非常复杂,记得多喊人。把老板、经验丰富的同学都拉到群里,毕竟在慌忙之中,自己考虑的可能不全面。如果大家有事都没看到消息,就直接打电话。

分析原因的时候,如果是复杂、涉及非常多团队合作的项目,其实有非常多的原因,从不同的角度分析会有不同的结果。在复盘会上,要尽量多的表达客观的实际情况,而不是害怕,特别是对于内向的同学。避免在会上不说话,会后又感到后悔,该表达的时候不要吝啬表达。

从自己的角度来说,并不是“只要这个线上问题,和我无关”,我就不用关注了。这类问题正是非常好的反面教材,即使和自己无关,也可以成为避免下次自己犯错的素材。

比如本文的各个原则,有些和我没有关系,但是在见到这些问题的时候,我做了总结,避免自己也犯同样的错误。最坏的情况是自己犯了严重的错误,导致较大的损失。面对痛苦的经验,是逃避,还是积极应对,取决于自己的态度。不要被痛苦所击败,而要将痛苦作为反思的素材。痛苦+反思 = 成长。

谋事在人,成事在天

将付出的努力与结果进行课题分离,它们之间的关系类似追求的目标与附属品。有时候我们付出了很多,但是结果却非常悲伤,这个时候就要用上阿德勒的课题分离了。付出是我们能掌控的,但结果不是,所以要把付出的努力与结果分离。

从另一个角度,也可以从目标与附属品的角度来考虑。如果我们追求的是结果,那一旦失败确实会非常悲伤。但如果结果是附属品,而过程是追求的目标,那其实结果如何已经不重要了。

互动有礼欢迎大家在评论区与我们聊一聊“你有哪些在工作中总结出的原则,优化日常行为呢?”,点赞量最高的3位朋友将会收到滴滴定制电脑包,8月29日晚9点开奖。