程序开发理念

有些代码的正确运行似乎是偶然的,因为周围其他代码的结构排除了可能导致代码接收到错误输入并失效的情况。我不喜欢这种情况。虽然从技术上讲,代码可能没有错误,但重组其他代码现在却很困难,也很危险。

 💬 69 条评论 |   | 

这么多年过去了,世界上仍然存在着初级开发人员,这似乎令人惊讶。

几周前,我们公司举行了一次座谈会,邀请资深开发人员(包括我在内)每人用五分钟左右的时间谈谈我们个人的软件开发理念。这样做的目的是让我们与更多的初级开发人员分享我们多年的经验。

会后,我觉得把自己的想法写出来,并补充一些细节,可能会很有价值。于是,我们就开始了。

这份清单有点杂,并不打算详尽探讨我开发软件的方式。此外,如果你已经是一名资深开发人员,那么显然你可能已经熟悉了其中的一些内容。或者不同意!软件开发是一个著名的主观领域。评论区见。

不惜一切代价,避免出现从头开始重写看起来很有吸引力的情况

一般来说,人们都很清楚,从头开始重写可能是一个既诱人又极其危险的前景。说到从头开始重写,标准的建议是 “千万不要”。但我想退一步说。

当从头开始重写看起来是个好主意时,本可避免的错误已经犯下了。这种情况在很远的地方就能看到,你可以而且必须积极避免。

需要注意的警示信号:复合技术债务。对代码进行看似简单的修改越来越困难。难以记录/评论代码。新开发人员入职困难。知道代码库特定区域如何实际工作的人越来越少。无人理解的 Bug。

每一次都必须与复杂性作斗争。在扩展(新功能)和整合两个阶段之间交替进行。

当然,从头开始重写也是可行的。它甚至可能比另一种选择(坚持使用现有的技术债务累累的代码沼泽)更好。同样,也可能两种选择都行不通–项目注定要失败,而你只是在选择它的死亡方式。问题的关键在于,这种情况存在固有风险……但情况本身是可以避免的,风险也是可以避免的。

争取在 50% 的可用时间内完成 90% 的工作

在软件开发领域有一句著名的格言–实际上,仔细想想,它可能起源于软件开发领域之外–它是这样说的:”工作的前 90%需要花费 50%的时间、

前 90% 的工作需要花费 90% 的时间。最后 10%的工作需要另外 90% 的时间。

这句话既有趣又绝对准确。理解了这一点,就完全有可能纠正它。

编写一次代码并使其运行需要一定的时间。一旦你完成了这一步,你需要明白你已经完成了一半。将代码打磨到合适的连贯性和可维护性水平、妥善处理边缘情况和故障情况、单元测试、集成测试、可用性测试/演示、“最后一刻 ”的功能更改、性能、可维护性、文档……所有这些都需要花费大量的额外时间,而这些也是你工作的一部分。

理论上,这些事情中有很多是可以省略的。但在实践中,当你跳过这些事情时,你最终会得到一个低劣的、不完整的功能。而且没有人会在事后回来 “正确 ”地完成这项工作。总有更多的工作要做。这样做三四次,你就会得到一个低劣的产品。

此外,编写代码本身也会遇到意想不到的障碍。建议尽快发现这些障碍。

如果结果神奇地证明你不需要计划中的额外时间呢?很好,是时候实施一些流程改进了!或者偿还一些技术债务(见上文)!

将好的做法自动化

有时,一个项目中的开发人员都应该开始做或停止做某件事。有新的最佳实践。我们需要在所有地方统一使用一种新工具;在每个源文件中加入一个新的强制头文件;每个人都必须运行一项检查;我们集体决定不使用一种方法(内部方法或第三方 API)。当这种情况发生时,有两种方法可以让整个开发者群体改变其行为:

  1. 社会化。当面告诉每个人,一次一个,或在 scrum 或团队会议上。发送电子邮件。将新指南添加到维基、软件仓库 README 或拉动请求模板中。反复提醒大家阅读文档。手动审核每个人的更改,以防疏漏,永远如此。确保你永远不会忘记!添加核对表,尝试培训每个人正确执行这些核对表。提高强制同行评审的级别。再次提醒大家。再提醒
  2. 实现自动化。

增加一个自动测试,如果不遵守准则,测试就会失败。或者,如果我们不能一次解决所有问题,那就增加一个棘轮装置。如果没有做正确的事,就快速失败,并发出礼貌而有指导性的警告,或者更好的办法是自动修复问题。总之,机械地执行最佳实践。

自动化并不是完美的解决方案,也不是万能的解决方案,更不是普遍适用的解决方案。有很多较软的要求和抽象的技术要求是无法自动化的,引入过多的任意规则也有可能变得非常苛刻,而积极主动的开发人员通常可以通过各种手段规避自动化。但是,如果你发现自己一遍又一遍地告诉别人:“你忘了做 X,请记住一定要做 X”,也许是时候将 X 自动化了。

思考病态数据

没有人会关心黄金路径。边缘案例是我们的全部工作。想想事情可能失败的方式。想想有哪些方法可以让事情失败。代码应该处理每一种可能性。

如果请求失败了,或者永远停滞不前,或者一小时内每秒只发回一个字节怎么办?如果显示的表有一百万行怎么办?十亿行?如果名称中有斜线,或有尾部空白,或有一百万字节长呢?我不相信你说的,你能证明那个字符串不可能是空的!

通常有一种更简单的写法

如果你的时间预算合理(见上文),你就有时间回头看看是否能做得更好。比如国际象棋中的一句老话:“当你看到一步好棋的时候,就去寻找更好的”。还有另一句难以找到出处的话:“很抱歉写了一封这么长的信,但我没有时间写一封短的”。

编写可测试的代码

这意味着定义明确的接口和最小的副作用。事实证明,难以测试的代码很可能没有正确封装。

代码仅具有可证明的正确性是不够的,还应该具有明显的、可视的、微不足道的正确性。

有些代码的正确运行似乎是偶然的,因为周围其他代码的结构排除了可能导致代码接收到错误输入并失效的情况。我不喜欢这种情况。虽然从技术上讲,代码可能没有错误,但重组其他代码现在却很困难,也很危险。

对于安全问题或理论上不存在的安全问题来说,尤其如此。这个内部函数的所有调用者现在都是可信的,这并不重要。

在这个列表中,我还有一个问题,但现在想不起来了。

本文文字及图片出自 Developer philosophy

共有 69 条讨论

  1. 对我来说,软件开发有很多方面,但其内循环的主要特点是两种交替模式: 尝试和学习。

    我认为最大的隐性假设是,我们希望其他人也能像我们一样尝试和学习。

    有经验的开发人员会编写代码来提高自己的理解能力,他们会创建测试来提高自己的理解能力,他们会做研究,他们会进行大量的学习,而所有这些都很少在站立或拉动请求中被提及。这些工作从来不会出现在计划或预算会议上。

    我看到初级开发人员总是被困在交付生产就绪的代码上–这是我们想要的工作成果;但这并不是软件开发的实际工作。很多初级开发人员都不愿意做那些没有提交到源代码控制的丢弃性工作–他们觉得外面有人知道答案,他们只需要找到他们就可以了。在很长一段时间里,这种想法是行得通的–他们可以找到某个人提供他们需要的答案,他们可以找到某个高级开发人员,后者会瞥一眼代码,然后毫不费力地修复它。

    没有开发经验或开发经验极少的经理会加剧这种情况,他们会把工作成果和跟踪的工作项目与生产软件的实际工作混为一谈。

    (如果你想知道谁是 “初级开发人员”,那就是我,我就是初级开发人员)。

        1. 谢谢你的指点,后面的日志中有很多不错的主题!

    1. RTFM 是已经失传的东西。无论你想使用什么工具、库或语言,最好都能接受你必须阅读文档(或 RFC)的想法。从任何地方粘贴你并不真正理解的代码并不是你掌握某样东西的方法,而是你在试图扩展某个分区时把虚拟机弄坏的方法。这样的例子不胜枚举。

      但现在告诉人们 “RTFM ”而不是给出答案是很不礼貌的。此外,教程应该花更多时间向观看者/读者展示如何获取他们提炼出来的信息。

      我们的朋友 k8s 就是一个简单的例子:大多数教程只会给你一些通用的部署 yaml。从不解释(或只是表面上解释)这些标签和选择器。尤其是为什么要让 spec.selector 与 spec.template.labels 匹配。教程应该首先链接到部署文档 https://kubernetes.io/docs/reference/kubernetes-api/workload… 在那里你会找到选择器规范的链接 https://kubernetes.io/docs/reference/kubernetes-api/common-d… 解释它是如何匹配标签的,在部署页面你还会看到你的 spec.template 实际上是 PodTemplate,这就是为什么你希望部署选择器与这些 Pod 标签相匹配。你会发现 90% 的示例都倾向于使用相同的名称,因为它们做的都是简单的事情,但当你尝试做一些更复杂的事情时,突然发现它们并不能帮助你了解哪些名称可以更改,以及更改会带来什么后果。特拉菲克的部分注释名称是有意义的,因此获得了一颗金星。

      1. > 但现在不给出答案,而是告诉人们去研究,这就太无礼了。

        我希望标准答案是链接到相关文档。不完全是 “RTFM”,因为很难找到手册的正确部分。但是,人类之所以能取得今天的成就,是因为有了可扩展的知识传播方式,即说一次/写一次,接受多次。如果因此而倒退,那就太尴尬了。

    2. > 他们能找到人提供所需的答案,他们能找到资深开发人员瞥一眼代码并毫不费力地修复它。

      我经常不厌其烦地指出,当类似的问题属于我的专业领域时,我之所以能如此迅速地解决它,通常是因为我已经犯过无数次同样的错误,并且记得如何解决它。

  2. “我很抱歉写了一封这么长的信,但我没有时间写一封短的。”这句话一般被认为是法国数学家和哲学家布莱斯-帕斯卡尔(Blaise Pascal)说的。帕斯卡尔在 1657 年出版的著作《外省书信》(Lettres Provinciales)中用法语写道:“Je n’ai fait celle-ci plus longue que parce que je n’ai pas eu le loisir de la faire plus courte,”翻译过来就是:”我把这封信写得比平时长,是因为我没有时间把它写短。

  3. 第一次听说 90/50 规则,我已经看到过很多次了。让代码在有限的情况下工作很容易陷入困境,但真正的质量来自于在测试和处理边缘情况上多花时间。往往是那些被忽视的细节会占用比我们预期更多的时间。对于初级开发人员来说,掌握这一点尤为重要,因为你需要明白,当你向 git 发送第一次推送时,你的任务并没有完成。

    我也完全同意自动化最佳实践。一切都依赖人工审核是无法扩展的。设置自动测试来强制执行格式、衬垫和可测试代码,可以创建一个期望明确、副作用最小的代码库。

    1. 我认为解决方案取决于问题的类型。如果你的问题是可靠性,那么增加测试和处理边缘情况(或避免使用那些边缘情况的代码)将会有所帮助。但如果问题是需求不确定,那么更多的测试只会增加你要丢弃的代码量,重构只会变得更加困难。

      问题是,在软件工程中,这两个问题往往是同时出现的–你在提高可靠性的同时,还要随着新功能请求的出现而改变系统的工作方式。这两种解决方案几乎是截然相反的。

      1. 更多的测试有助于以更具体的方式展示不确定的需求。

        在这些情况下应该怎么办?现在我们有了这份清单,它看起来是否不寻常或不一致?

        如果能有更通用的测试(如基于属性的测试)来测试更广泛的语句,那就更棒了。

        我经常回想起这个例子,我曾为一个堆栈中的用户界面框架构建了一个测试库。它对我们很有帮助,因为我们可以写出这样的测试:

        “对于任何用户界面,如果用户向右移动,焦点发生变化,那么当用户向左移动时,焦点会回到原来的项目”。

        然后我们发现了规范中的一个错误,即

        对于构建用户界面的任何一系列应用程序接口调用和任何一系列用户交互,以下情况之一总是为真

        * 用户界面中没有项目

        * 用户界面中没有项目

        能够在这一层次上明确说明问题,使我们能够简明扼要地定义主要行为,从而使它们更容易保持一致。

        1. 我认为上级评论员的观点更像是 “只见树木不见森林 ”式的不确定要求。

          更详细地说,就是不确定你是否真的解决了正确的问题,或者问题是否真的存在。虽然您的建议有助于找出问题所在,但您的建议也会延长将代码呈现在真正的最终用户面前的过程,以确定他们是否按照您所期望的方式使用或采纳代码。它可以让你快速扔掉所有代码,去解决正确的问题。

          另一种说法是,问题空间的未知性越大,你就应该越快地把代码拿到用户面前,推迟边缘情况的发现。

    2. 这句话我听得最多的版本是 “项目的最后 10%需要花费 90% 的时间”。这句话有点不同,但意思是一样的:实现功能的真正工作是处理意外行为和边缘情况。从外观上看,它可能已经完成,但想象和准备每一种可以想象到的结果需要花费大量的时间。

    3. 真正的质量来自于避免边缘情况陷阱。

      有些方法/功能类型会造成边缘情况爆炸。

      例如,我经常一心想避免双向同步或编写迷你解析器。如果有其他更简单的方法,那总是更好。

    4. > 让代码在有限的情况下正常工作是很容易的,但真正的质量来自于在测试和处理边缘情况上多花时间。

      令人沮丧的是,这与大多数软件公司以两周冲刺为单位的工作方式,以及以已完成的单子/已完成的故事点来评判工作效率的方式格格不入。

      你无法花时间做好这件事

  4. 我认为,任何试图提炼辛苦积累的经验和领域意识的做法,最终都会沦为错误的泛泛而谈。

    这并不是说这篇文章不好。它写得很好,教导也很有价值。

    这篇文章是写给那些没有经验的开发人员的,他们在文章中寻找意识形态的处方:不要。

    给自己一点时间。让自己从失败中吸取教训。继续阅读大师们的著作,如《清洁代码》、《SICP》、《有效处理遗留代码》、《软件架构的难点》、《神话中的人月》等……但不要让任何人告诉你如何做好你的工作。

    开发工作归根结底就是管理难以驾驭、不断发展的复杂性,并使其发挥作用。开发是艺术,也是经验。开发需要极大的平和心态。