程序开发理念
这么多年过去了,世界上仍然存在着初级开发人员,这似乎令人惊讶。
几周前,我们公司举行了一次座谈会,邀请资深开发人员(包括我在内)每人用五分钟左右的时间谈谈我们个人的软件开发理念。这样做的目的是让我们与更多的初级开发人员分享我们多年的经验。
会后,我觉得把自己的想法写出来,并补充一些细节,可能会很有价值。于是,我们就开始了。
这份清单有点杂,并不打算详尽探讨我开发软件的方式。此外,如果你已经是一名资深开发人员,那么显然你可能已经熟悉了其中的一些内容。或者不同意!软件开发是一个著名的主观领域。评论区见。
不惜一切代价,避免出现从头开始重写看起来很有吸引力的情况
一般来说,人们都很清楚,从头开始重写可能是一个既诱人又极其危险的前景。说到从头开始重写,标准的建议是 “千万不要”。但我想退一步说。
当从头开始重写看起来是个好主意时,本可避免的错误已经犯下了。这种情况在很远的地方就能看到,你可以而且必须积极避免。
需要注意的警示信号:复合技术债务。对代码进行看似简单的修改越来越困难。难以记录/评论代码。新开发人员入职困难。知道代码库特定区域如何实际工作的人越来越少。无人理解的 Bug。
每一次都必须与复杂性作斗争。在扩展(新功能)和整合两个阶段之间交替进行。
当然,从头开始重写也是可行的。它甚至可能比另一种选择(坚持使用现有的技术债务累累的代码沼泽)更好。同样,也可能两种选择都行不通–项目注定要失败,而你只是在选择它的死亡方式。问题的关键在于,这种情况存在固有风险……但情况本身是可以避免的,风险也是可以避免的。
争取在 50% 的可用时间内完成 90% 的工作
在软件开发领域有一句著名的格言–实际上,仔细想想,它可能起源于软件开发领域之外–它是这样说的:”工作的前 90%需要花费 50%的时间、
前 90% 的工作需要花费 90% 的时间。最后 10%的工作需要另外 90% 的时间。
这句话既有趣又绝对准确。理解了这一点,就完全有可能纠正它。
编写一次代码并使其运行需要一定的时间。一旦你完成了这一步,你需要明白你已经完成了一半。将代码打磨到合适的连贯性和可维护性水平、妥善处理边缘情况和故障情况、单元测试、集成测试、可用性测试/演示、“最后一刻 ”的功能更改、性能、可维护性、文档……所有这些都需要花费大量的额外时间,而这些也是你工作的一部分。
理论上,这些事情中有很多是可以省略的。但在实践中,当你跳过这些事情时,你最终会得到一个低劣的、不完整的功能。而且没有人会在事后回来 “正确 ”地完成这项工作。总有更多的工作要做。这样做三四次,你就会得到一个低劣的产品。
此外,编写代码本身也会遇到意想不到的障碍。建议尽快发现这些障碍。
如果结果神奇地证明你不需要计划中的额外时间呢?很好,是时候实施一些流程改进了!或者偿还一些技术债务(见上文)!
将好的做法自动化
有时,一个项目中的开发人员都应该开始做或停止做某件事。有新的最佳实践。我们需要在所有地方统一使用一种新工具;在每个源文件中加入一个新的强制头文件;每个人都必须运行一项检查;我们集体决定不使用一种方法(内部方法或第三方 API)。当这种情况发生时,有两种方法可以让整个开发者群体改变其行为:
- 社会化。当面告诉每个人,一次一个,或在 scrum 或团队会议上。发送电子邮件。将新指南添加到维基、软件仓库 README 或拉动请求模板中。反复提醒大家阅读文档。手动审核每个人的更改,以防疏漏,永远如此。确保你永远不会忘记!添加核对表,尝试培训每个人正确执行这些核对表。提高强制同行评审的级别。再次提醒大家。再提醒
- 实现自动化。
增加一个自动测试,如果不遵守准则,测试就会失败。或者,如果我们不能一次解决所有问题,那就增加一个棘轮装置。如果没有做正确的事,就快速失败,并发出礼貌而有指导性的警告,或者更好的办法是自动修复问题。总之,机械地执行最佳实践。
自动化并不是完美的解决方案,也不是万能的解决方案,更不是普遍适用的解决方案。有很多较软的要求和抽象的技术要求是无法自动化的,引入过多的任意规则也有可能变得非常苛刻,而积极主动的开发人员通常可以通过各种手段规避自动化。但是,如果你发现自己一遍又一遍地告诉别人:“你忘了做 X,请记住一定要做 X”,也许是时候将 X 自动化了。
思考病态数据
没有人会关心黄金路径。边缘案例是我们的全部工作。想想事情可能失败的方式。想想有哪些方法可以让事情失败。代码应该处理每一种可能性。
如果请求失败了,或者永远停滞不前,或者一小时内每秒只发回一个字节怎么办?如果显示的表有一百万行怎么办?十亿行?如果名称中有斜线,或有尾部空白,或有一百万字节长呢?我不相信你说的,你能证明那个字符串不可能是空的!
通常有一种更简单的写法
如果你的时间预算合理(见上文),你就有时间回头看看是否能做得更好。比如国际象棋中的一句老话:“当你看到一步好棋的时候,就去寻找更好的”。还有另一句难以找到出处的话:“很抱歉写了一封这么长的信,但我没有时间写一封短的”。
编写可测试的代码
这意味着定义明确的接口和最小的副作用。事实证明,难以测试的代码很可能没有正确封装。
代码仅具有可证明的正确性是不够的,还应该具有明显的、可视的、微不足道的正确性。
有些代码的正确运行似乎是偶然的,因为周围其他代码的结构排除了可能导致代码接收到错误输入并失效的情况。我不喜欢这种情况。虽然从技术上讲,代码可能没有错误,但重组其他代码现在却很困难,也很危险。
对于安全问题或理论上不存在的安全问题来说,尤其如此。这个内部函数的所有调用者现在都是可信的,这并不重要。
在这个列表中,我还有一个问题,但现在想不起来了。
本文文字及图片出自 Developer philosophy
你也许感兴趣的:
- 了解 CSS 是前端开发的精髓
- 为什么我们不能在苹果设备上截取受 DRM 保护的视频画面?
- 滥用 SQLite 处理并发性
- 【外评】一位中国程序员的开源之旅
- 滚动条上的小人
- C++ 的创造者呼吁帮助保护编程语言免受 “严重攻击
- 苹果的软件质量危机 当优质硬件遇上次品软件
- 我最讨厌的 9 个编码问题
- Python 奇特的自引用
- 微软 .NET 10 发布首个预览版
对我来说,软件开发有很多方面,但其内循环的主要特点是两种交替模式: 尝试和学习。
我认为最大的隐性假设是,我们希望其他人也能像我们一样尝试和学习。
有经验的开发人员会编写代码来提高自己的理解能力,他们会创建测试来提高自己的理解能力,他们会做研究,他们会进行大量的学习,而所有这些都很少在站立或拉动请求中被提及。这些工作从来不会出现在计划或预算会议上。
我看到初级开发人员总是被困在交付生产就绪的代码上–这是我们想要的工作成果;但这并不是软件开发的实际工作。很多初级开发人员都不愿意做那些没有提交到源代码控制的丢弃性工作–他们觉得外面有人知道答案,他们只需要找到他们就可以了。在很长一段时间里,这种想法是行得通的–他们可以找到某个人提供他们需要的答案,他们可以找到某个高级开发人员,后者会瞥一眼代码,然后毫不费力地修复它。
没有开发经验或开发经验极少的经理会加剧这种情况,他们会把工作成果和跟踪的工作项目与生产软件的实际工作混为一谈。
(如果你想知道谁是 “初级开发人员”,那就是我,我就是初级开发人员)。
我认为你对软件开发的描述与彼得-瑙尔(Peter Naur)的观点高度一致,即编程即理论构建。
https://pages.cs.wisc.edu/~remzi/Naur.pdf
我喜欢这篇论文出现的时候,因为现在我可以推荐一集非常棒、非常书呆子气的播客的相关内容了:
https://futureofcoding.org/episodes/061.html
谢谢你的指点,后面的日志中有很多不错的主题!
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% 的示例都倾向于使用相同的名称,因为它们做的都是简单的事情,但当你尝试做一些更复杂的事情时,突然发现它们并不能帮助你了解哪些名称可以更改,以及更改会带来什么后果。特拉菲克的部分注释名称是有意义的,因此获得了一颗金星。
> 但现在不给出答案,而是告诉人们去研究,这就太无礼了。
我希望标准答案是链接到相关文档。不完全是 “RTFM”,因为很难找到手册的正确部分。但是,人类之所以能取得今天的成就,是因为有了可扩展的知识传播方式,即说一次/写一次,接受多次。如果因此而倒退,那就太尴尬了。
> 他们能找到人提供所需的答案,他们能找到资深开发人员瞥一眼代码并毫不费力地修复它。
我经常不厌其烦地指出,当类似的问题属于我的专业领域时,我之所以能如此迅速地解决它,通常是因为我已经犯过无数次同样的错误,并且记得如何解决它。
“我很抱歉写了一封这么长的信,但我没有时间写一封短的。”这句话一般被认为是法国数学家和哲学家布莱斯-帕斯卡尔(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,”翻译过来就是:”我把这封信写得比平时长,是因为我没有时间把它写短。
第一次听说 90/50 规则,我已经看到过很多次了。让代码在有限的情况下工作很容易陷入困境,但真正的质量来自于在测试和处理边缘情况上多花时间。往往是那些被忽视的细节会占用比我们预期更多的时间。对于初级开发人员来说,掌握这一点尤为重要,因为你需要明白,当你向 git 发送第一次推送时,你的任务并没有完成。
我也完全同意自动化最佳实践。一切都依赖人工审核是无法扩展的。设置自动测试来强制执行格式、衬垫和可测试代码,可以创建一个期望明确、副作用最小的代码库。
我认为解决方案取决于问题的类型。如果你的问题是可靠性,那么增加测试和处理边缘情况(或避免使用那些边缘情况的代码)将会有所帮助。但如果问题是需求不确定,那么更多的测试只会增加你要丢弃的代码量,重构只会变得更加困难。
问题是,在软件工程中,这两个问题往往是同时出现的–你在提高可靠性的同时,还要随着新功能请求的出现而改变系统的工作方式。这两种解决方案几乎是截然相反的。
更多的测试有助于以更具体的方式展示不确定的需求。
在这些情况下应该怎么办?现在我们有了这份清单,它看起来是否不寻常或不一致?
如果能有更通用的测试(如基于属性的测试)来测试更广泛的语句,那就更棒了。
我经常回想起这个例子,我曾为一个堆栈中的用户界面框架构建了一个测试库。它对我们很有帮助,因为我们可以写出这样的测试:
“对于任何用户界面,如果用户向右移动,焦点发生变化,那么当用户向左移动时,焦点会回到原来的项目”。
然后我们发现了规范中的一个错误,即
对于构建用户界面的任何一系列应用程序接口调用和任何一系列用户交互,以下情况之一总是为真
* 用户界面中没有项目
* 用户界面中没有项目
能够在这一层次上明确说明问题,使我们能够简明扼要地定义主要行为,从而使它们更容易保持一致。
我认为上级评论员的观点更像是 “只见树木不见森林 ”式的不确定要求。
更详细地说,就是不确定你是否真的解决了正确的问题,或者问题是否真的存在。虽然您的建议有助于找出问题所在,但您的建议也会延长将代码呈现在真正的最终用户面前的过程,以确定他们是否按照您所期望的方式使用或采纳代码。它可以让你快速扔掉所有代码,去解决正确的问题。
另一种说法是,问题空间的未知性越大,你就应该越快地把代码拿到用户面前,推迟边缘情况的发现。
这句话我听得最多的版本是 “项目的最后 10%需要花费 90% 的时间”。这句话有点不同,但意思是一样的:实现功能的真正工作是处理意外行为和边缘情况。从外观上看,它可能已经完成,但想象和准备每一种可以想象到的结果需要花费大量的时间。
真正的质量来自于避免边缘情况陷阱。
有些方法/功能类型会造成边缘情况爆炸。
例如,我经常一心想避免双向同步或编写迷你解析器。如果有其他更简单的方法,那总是更好。
> 让代码在有限的情况下正常工作是很容易的,但真正的质量来自于在测试和处理边缘情况上多花时间。
令人沮丧的是,这与大多数软件公司以两周冲刺为单位的工作方式,以及以已完成的单子/已完成的故事点来评判工作效率的方式格格不入。
你无法花时间做好这件事
我认为,任何试图提炼辛苦积累的经验和领域意识的做法,最终都会沦为错误的泛泛而谈。
这并不是说这篇文章不好。它写得很好,教导也很有价值。
这篇文章是写给那些没有经验的开发人员的,他们在文章中寻找意识形态的处方:不要。
给自己一点时间。让自己从失败中吸取教训。继续阅读大师们的著作,如《清洁代码》、《SICP》、《有效处理遗留代码》、《软件架构的难点》、《神话中的人月》等……但不要让任何人告诉你如何做好你的工作。
开发工作归根结底就是管理难以驾驭、不断发展的复杂性,并使其发挥作用。开发是艺术,也是经验。开发需要极大的平和心态。
作者对《清洁代码》(Clean Code https://qntm.org/clean)谈了一些看法
更值得推荐的是奥斯特豪特(Osterhaut)的《软件设计哲学》(A Philosophy of Software Design)。
> 让自己失败并从错误中学习。
根据我的经验,尝试和失败是最好的教学/学习方法。
如果你在职业生涯中善于观察,运气好的话,你会从别人的失败和自己的失败中获得学习的技巧。
(从管理和指导的角度来看,给初级和中级开发人员分配有真正失败风险的任务/项目是很重要的,同时在他们失败时,要避免他们受到指责或打击。在我 30 多年的开发生涯中,与我共事过的所有最优秀的开发人员都有深厚的口述历史功底,在解释为什么某种方法可能并不像表面上看起来那么好时,他们都能从这些战争故事中找到答案。)
只要你不试图把它变成硬性规定,泛泛而谈是可以的。
概括性的说法包含了普遍适用的知识,但我认为大家都明白,这些知识总会有不适用的时候。
太多的 CS 培训(以及以 leet 代码为导向的面试)都在鼓励人们耍小聪明。
编写的代码要易于阅读和理解。这不仅仅是算法和代码注释的问题–变量/函数的命名要具有描述性、格式要一致、不要嵌套太多、设计要模块化,以便忽略无关的复杂性来源,等等。
克尼根定律–调试的难度是编写代码的两倍。因此,如果你尽可能巧妙地编写代码,那么顾名思义,你就没有足够的智慧来调试它。
当然,这种说法只有在你用你的聪明才智去优化并非可读性的东西时才成立。
很多聪明的代码都以为自己是在为可读性而优化,其实是在为聪明本身的感觉而优化。
我觉得作为一个异类,我有必要发表一下自己的看法。至少对我来说,调试(使用源代码)要容易得多,因为你可以在调试链上看到所有的堆栈。在正确调试过程中发现疯狂行为的情况非常罕见,但并非不可能。不过,我对这一规律还是第一次听说。
我认为这取决于你需要调试的东西有多棘手。竞赛条件、间歇性错误(导致进程崩溃而不留痕迹)等等。调试远不止使用调试器这么简单
我喜欢区分 “源于最近代码更改的错误”(容易修复)和 “突然出现的错误”(难以修复)。
“今天不写,明天就无法调试”。
如果把 “简单 ”作为目标,我会有点警惕。有些人读到这句话后,会认为要写更短、更聪明的代码,而不是写更长、更直接的代码,因为后者可能会有一些冗余。(例如,人们经常称赞 FP 语言和类 Lisp 语言的控制流非常灵活,但添加自定义抽象可能会导致代码更短,新人难以理解)。
另一方面,过多的模块化和分解代码也会掩盖程序的行为,即使每个模块都有明确定义的角色并满足其接口要求。(例如,大型 Java 程序可能有数百个类,这些类的方法体很小,但对其他类和接口的调用却很多。如果你看到的是接口调用,但对象却来自几个类之外的某个地方,那么要想知道调用的是哪个实现,那就祝你好运了)。
我认为最终的寓意是 “保持逻辑和数据流相对线性(或至少不超过解决问题所需的复杂程度)。避免将其分割成十几个不同的点,或对其进行疯狂的特技表演,这样只有专家才能理解发生了什么。有些逻辑结构(如动态调度)是不成文的,但这并不意味着它们不会造成心理负担”。
我认为,如果代码对于新手来说难以理解(当然,在一定程度上),这并没有什么问题。大多数程序员都是在类似 C 语言和范式的环境中学习的。使用 FP(类 ML)语言已经是 “难以理解 ”了。
那么问题就变成了:新人头脑中的 “理论 ”与他们在代码库中发挥作用所需的 “理论 ”之间的差距有多大?
例如,使用显式效果(即 “IO”)或类型安全的未来(Futures)编程。简单入门并不会有太大困难,而且它还能为新手建立起效果的一般理论,即使在异步效果之外,这在代码库的许多上下文中可能也会有用,例如使用 `Either` 进行错误处理。
对于新手来说,一切都很难理解。应该帮助新人学习新知识。每一种编程语言对于非编程人员来说都是难以理解的。这不是目标受众。
一个开发人员说 “你只需要学习我们是如何做事的”,另一个开发人员就会说 “我的天啊,为什么这个代码库有自己的令人费解的无所不能的超抽象,而这些超抽象在这个组织之外是不存在的”。(我想到的不是流行 FP 语言的基本概念,而是它们可以用来创建的强大抽象)。不过,如果一家公司发明了自己的语言,那就会很快进入 “差距过大 ”的范围)。
我同意这里有一个范围,但在我看来,熟悉一切的现有开发人员很容易低估新人的知识与自己的知识之间的差距,或者高估需要多少知识。在最糟糕的情况下,你最终会得到脆生生的意大利面 “抽象”,而老一辈的人则会信誓旦旦地说每一样东西都是完全合理和必要的。
(我本人曾在应用程序接口设计中两次遇到过这种高估必要性的情况。在这两个案例中,公共图书馆的作者都发布了一个与旧版本不兼容的新版本,理由是用户确实应该学习并关注图书馆所要解决的问题空间的细节,而更改 API 是迫使他们动手的好方法。但从外部视角来看,对于 99.9% 的用例来说,这些细节并不重要,而且让每个开发人员都意识到整个堆栈中的每个权衡和设计决策也不切实际)。
> 在最糟糕的情况下,你最终会看到脆生生的意大利面 “抽象”,而老一辈的人却在那里信誓旦旦地说每一样东西都是完全合理和必要的。
我在上一家公司就遇到过这种情况。原团队的所有成员都走了,只有我自己和一个兼职开发人员(每周工作半天)。
写新的测试比写修改的时间还长–不是因为我们要写大量的测试,而是因为用来创建所有测试的 “工厂 ”意大利面条代码神类简直就是一场噩梦。
到了后来,我只专注于手工质量保证,每次变更都更快(小团队在这方面帮了大忙)。
而从头开始重写该 repo 现有的 20K LoC 是行不通的,因为我们没有人手或时间。
基本上,我们没有时间处理这些废话。
保持愚蠢。
为了世界上一切美好和神圣的事物,我恳求你们,别再犯傻了(这里指的是一般情况,而不是针对母版)。
傻傻地做,更容易快速做好。
–
我现在想对着浩瀚无垠的网际空间发发牢骚:
整个系统的数据/心理模型都是错的:1:1 关系的东西被分割成不同的表格;工人任务的代码调度分散在多个服务中;我不得不重新设计来解决多个竞赛条件。
没有正确的文档。有的文档要么是错的,要么是太高层次了,基本上没有用。
还有roll-your-auth。
显然,所有这些都是在 “干净代码”(clean code cargo cult)的指导下完成的,因为到处都是 10 行的方法,这意味着要不停地跳来跳去。
> 但添加自定义抽象会导致代码更短,新人难以理解。
我曾在一些地方工作过,那里的领导会抽象出所有可能的东西,这不仅使跟踪流程变得困难,而且几乎是故意混淆视听。当他被问及此事时,他会高唱原则,并说这是健壮代码的源泉。我相信 100 多名客户都会对此表示赞赏。
我很欣赏你关于保持流程正确的评论,我想我还可以补充一点,那就是确保你的域名和边界得到很好的确立和尊重。错误总是会发生的,但如果它们是由域名 “把关 ”的,那么纠正错误的人一定会请你喝啤酒/咖啡。
我在伟大的导师指导下学习时发现了一条很有用的经验法则(很久以前),那就是:看看你的签到(当时是 svn),如果你认为自己在 5 个月后会在 15 分钟内记住它,那就没问题。SVN 也许让我们变得小心翼翼……
我继承了一个地理学博士和一些刚从编码训练营出来的小喽啰写的意大利面条代码。我非常喜欢发现 “聪明 ”的代码,即使是他们认为聪明的代码。
如果是合法的 “聪明 ”代码,只需添加一个链接,说明代码背后的理论来源,对我来说就足够了。
链接)棘轮是个天才!它是一种近乎完美的机制,可以在不付出过多劳动的情况下实现你想要的未来。
https://qntm.org/ratchet
除了普通 grep 之外,还有一个有用的工具可以计算模式出现的次数,那就是 ast-grep [1],它允许使用更复杂的规则。
[1] https://ast-grep.github.io/
谢谢你指出这一点,我非常喜欢这个想法。我要去看看我在工作中能用什么方法。可能有很多
作者引用了这句话:
> 前 90% 的工作需要花费 90% 的时间。最后 10%的工作需要另外 90% 的时间。
当我汇报工作完成情况时,我总是喜欢用具有讽刺意味的说法:
> 我已经完成了 90%,现在只剩下 90%了。
这很有趣,但最重要的是,对于非开发人员来说,它报告了我们工作的现实。做最简单的事情几乎是轻而易举的,但当你必须考虑到异常、错误、可用性、日志、健壮性、安全性等因素时,就会有很多 “意想不到 ”的工作。
这让我想起乔尔-斯波斯基(Joel Spolsky)关于重写的测试与真实文章:https://www.joelonsoftware.com/2000/04/06/things-you-should-…
“当从头开始重写看起来是个好主意时,可避免的错误已经犯下了。”
这就是 devops 的智慧。做好它,一切都会顺利进行。
下面已经有人评论 [0]
> 我认为,任何试图提炼辛苦积累的经验和领域意识的做法,最终都会演变成错误的泛泛而谈。
从头开始重写是一场赌博。Spolksy 关于网景浏览器被 Internet Explorer 抢走先机的经典文章 [1] 必读。
简而言之
1. 你以为你知道如何构建新版本,但你真的不知道。多年来对旧版本的改进、多年来的漏洞修复、多年来的业务知识都在表面之下。
2. 新版本会有新的错误。
3. 当你从头开始重建一切时,你的竞争对手正在改进他们的产品。
0: https://news.ycombinator.com/item?id=42921426
1: https://www.joelonsoftware.com/2000/04/06/things-you-should-…
所有的抽象概念都是漏洞百出的,所有精辟的说法都有一个自相矛盾、同样精辟的反说法。
真正的智慧是知道什么时候该看哪些智慧。
我曾听人说过:“你抱怨的那些‘垃圾’,我称之为‘错误修复’”。
一个成熟的代码库代表了大量的 “部落投资”,这有点像 “部落知识”。
它不容易量化,但却是一件大事,代表着真正意义上的资源投入。扔掉它,就意味着也扔掉了这笔投资。这就是为什么许多大型软件代码库仍然使用 “不符合流行语标准 ”的语言。
同时,我们也不想把好钱扔到坏钱后面,所以经验为我们提供了所需的工具,让我们知道什么时候该 “清仓”。
>> 你以为你知道如何构建新版本,但你真的不知道。多年来对旧版本的改进、多年来的漏洞修复、多年来的业务知识都在表面之下。
这一点一直让我感到困惑。老话说:“如果没坏,就不要修。”但在过去的几十年里,每个人都在使用 JS 框架,并全心全意地相信,只要改用 “X ”框架,就能消除旧版本中的所有问题。
根据我自己的经验,事情从来都不是这样的。当然,你想使用 10 多年前的遗留代码吗?也许不想,但认为每一个闪亮的新东西都能解决你以前遇到的所有问题的想法,肯定会让很多人因为承诺过多而交付不足而被解雇。
我喜欢这份清单。我还想加上
* 优化可读性。
* 错误的代价与引入错误和发现错误之间的时间成正比(NIST 的一些研究):防止错误进入下一阶段(需求->设计->开发->暂存->生产)。
* 赞成 FP 原则,如:数据与逻辑(函数)分离;不变性;显式空;偏好纯函数等。即使在非 FP 语言中也是如此。
* 语言中的强类型规范值得一试。
同为写过《没有抗抑郁剂》的 qntm
还有《魔王》、《精细结构》和一些短篇小说。他们的作品令人难以置信。
另请参阅他们在《清洁代码》上发表的文章:https://qntm.org/clean
哇哦!世界真小。这真是个有趣的故事。
其中有些内容是对我的想法和感受的优雅提炼,读起来让我捧腹大笑。致作者:非常感谢你分享这些智慧。
我觉得唯一难以调和的是,那些认为自己在与 “你已经完成了 90%”作斗争的人和那些认为自己在与 “思考病理数据 ”作斗争的人之间的推拉。从根本上说,我个人认为速度与准备/安全之间的冲突很难解决。
我不是在尖酸刻薄,真的。我有一点感触:
>会议结束后,我觉得把自己的想法写出来,并补充一些细节,可能会很有价值。所以就写到这里了。
参加会议的资深开发人员有没有留下来互相倾听? 你们的想法是否一致,有没有从他们身上学到什么,或者觉得他们说的任何话都应该写下来?
我想我在想,初级开发人员在听完所有发言后会有什么收获,或者从高级开发人员的角度来看,他们应该有什么收获?
这些都是很好的想法,尤其是后 5 点,主要是技术方面的。前两个想法通常会受到开发人员无法控制的因素的影响。例如,开发人员可能会被推到一个鼓励积累技术债务的时间表上,导致不可避免的错误破产和从头开始重写。
我认为每个开发人员都应该努力做正确的事,同时在正确的结果没有出现时也要灵活应对。
看到这个域名,我真希望这是一个关于计算机对其开发人员进行哲学思考的 SF 故事。
正如前面的评论所指出的,我已经认识到 “视情况而定 ”确实是我的经验中最重要的因素。
每一个项目、每一个挑战、每一种人际关系,都是一个独特的环境。有一些全球性的政策、战略和结构适用于大多数情况,但总会有 “例外情况 ”出现,它们会给我们带来惊喜,使我们无法按计划行事。
经验可以帮助我们处理这些异常情况。我们意识到,我们有一个启发式方法库,而不是规则。
“打破常规 “是一件严肃的事情。年轻时,我们往往缺乏对偏差后果的认识,很容易让事情变得更糟。当我们有了经验之后,我们就可以 “一盘棋走到底”,更清楚地预见到我们的决定所带来的结果,并制定长期的过渡策略。
来之不易的真知灼见!期待在这个主题上看到许多 HN 评论。
好吧,这是我见过的最难的挑战。
对我来说呢?也许是这样的
1. 所有规则都有例外–有的地方应该是例外。严格执行规则可能比没有规则要好,但深思熟虑地遵守规则(几乎所有时间)则更好。了解你的规则,知道为什么是规则,知道什么时候例外是合理的。(也许这说明 “规则 ”其实更像是 “准则”)。
规则往往是成双成对的。我是这样想的: 在锡安国家公园,有一条徒步路线叫 “天使登陆”。你要爬上这座山脊。一边是 1000 英尺的落差。另一侧是 500 英尺的落差。山脊也不是很宽 如果你看着一个悬崖,心想 “我得小心点,别从悬崖上掉下去”,而你又退得太远,那么你就会从另一个悬崖上掉下去。
我认为软件工程也可能是这样。你可能会犯不止一个错误。不要忙于避免一个错误,而犯下相反的错误。这需要深思熟虑,而不是盲目循规蹈矩。
2. 一般来说,不要重复自己。在我职业生涯的早期,我和一位同事学会了问自己:”你在每个地方都解决了吗?” 如果只有一处,那就更好了。
但是 常见的情况是,事情一开始是相同的,然后变得略有不同(用一个 “如果 ”覆盖),然后变得更加不同(现在我们有好几个 “如果”),然后变得更加不同。最后,最好还是认定这些实际上是不同的东西,并将它们分开。知道何时这样做是一门艺术,而不是一门科学,但它很重要。
3. 最普遍的问题无法解决。诀窍在于做一件足够简单的事情,让你能够真正完成它,但又足够难,让它真正值得去做。你不能仅仅通过改变你所写的代码行来解决这个问题;问题出在规范层面(尽管它可能影响一个模块或函数的规范,而不仅仅是一个项目)。
4. 4. 现在交付的 “接近完美 ”的代码,很可能比未来一段时间内不确定的绝对完美的代码更好。这取决于不完美的地方有多少,不完美被发现的可能性有多大,以及不完美被发现时的破坏性有多大。接近完美的代码可能对很多人来说都是完全可用的;而尚未发布的代码目前对任何人来说都是不可用的。(请注意: 请注意:这不是马虎的借口!这是不完全追求完美主义的借口)。
5. 你有一个完美的设计?好极了。作为交付设计的一部分,为其提供一份解释/路线图/旅游指南。(如果是电气设计,可以称之为 “操作理论”)。考虑把它放到版本控制系统中,就在代码旁边–比如,在顶层目录中。
6. 所有这些都需要维护。你必须重新审视 “不要重复自己 ”的决定。你必须重新审视哪些是范围内的,哪些是范围外的。你必须重新审视哪些 bug 是可以容忍的,哪些是不能容忍的。你必须更新你的设计文档。花时间做这些工作。否则,你的程序会慢慢变得越来越脆弱。
优化我所做的事情,为团队和决策者提供可选性,一直是我的一大目标。我不希望有一个脆弱的计划,如果它强迫事情按顺序完成,就会使我的努力降到最低。多个独立的变更集可以更快更好地解决问题。
减少耦合是一个相关的概念–如果两件事情可以是两件事情,而不是一个相互关联的毛球,那总是更好。
喜欢这个。我很想看到对许多资深软件从业者的开发者哲学进行某种类型的基础理论定性分析–可以想象会出现哪些共同的主题,但我更好奇的是,哪些部分感觉直观、正确,但却没有得到大多数资深从业者的支持。
> 我还为这份清单准备了一样东西,但现在想不起来了。
写下来吧。这是我要写在清单上的东西。把事情写下来的能力就像是大脑的延伸,是卸载和记录你不断在脑海中构建的模型的一种方式。(还有助于回忆)。
我家的第一条规矩是 “要善良”(不要和 “要友好 ”混淆)。
在编码时,我努力遵守的第一条规则是 “要有同理心”(对他人和未来的自己)。
在实践中,这意味着要重视清晰度而不是聪明度(除非我能做到两者兼顾!),要重视文档化,除非我真的有理由不这样做(即,它是否现实地 “自文档化”),当然也包括对用户的同理心–我是一名前端开发人员,所以我的大部分工作都是用户界面。
也许你读到这里会觉得 “咄咄怪事”,但请相信我,在 20 多年的职业生涯中,这些显然不是显而易见的准则(即使对我自己来说也是如此)。
关于边缘案例的观点与关于自动化良好实践的观点有交集:自动化查找边缘案例!基于属性的测试比你想出所有可能出错的可怕方法要有趣得多,而且它比你更善于发现它们。
我还想补充一点:如果你最终要进行从头开始的重写,请确保当前代码的原作者参与了重写。如果他们没有参与,那就不是重写,只是另一个版本而已。
这篇文章把我的类似经历写得淋漓尽致:
– 我总是在想可能会出什么问题,当然,这都是边缘情况!
– 最后的 10%到底是什么?
“没有人关心黄金路径。边缘案例就是我们的全部工作”。
这显然有些夸张;如果忽略了黄金路径,代码就无法解决它要解决的问题。但没错,编写可靠的代码就是要考虑边缘情况。如果可以,就消除它们;如果不能,就为它们编写代码。
早在 20 世纪 70 年代,当我还是一个阅读计算机杂志的高中生时,我就看到过一项研究(我相信是由 IBM 公司进行的),其中提到在生产代码中,有 70% 或 80% 的代码行是错误处理;只有 20% 或 30% 是 “黄金路径”。即使在当时,我也不确定自己是否看到过实际参考文献,现在当然也无法提供参考文献。
有人知道相关的研究吗?(我从未见过其他相关研究,有人知道吗?)
这几乎可以肯定是在汇编、PL/I 或 Algol 或其他语言中完成的。更现代的语言会改变它吗?例外情况?双轨编程(选项或 Maybes)?
无论确切数字如何,错误案例都是你必须时刻考虑的问题。
也许这也适用于围棋代码?
撇开讽刺不谈,我是一个非常关注边缘情况的人,但对于我写的这种代码来说,这些比率似乎完全是错误的。也许我们应该说 “边角情况”,而不是 “错误情况”。边角情况并不一定是错误。我发现,与问题空间适当 “协调 ”的好算法通常会隐含地正确处理角情况。而那些 “根据测试编码 ”的人在没有真正理解问题空间的情况下拼凑出来的算法,往往不会处理拐角情况,然后开发人员会试图通过添加 if 语句来处理拐角情况。
归根结底,将 70% 或 80% 的思考时间用于处理拐角案例在我看来是完全合理的。将 70% 或 80% 的代码行专门用于角情况可能是一种味道。
这取决于代码的类型。举例来说,如果你正在自动化一个流程,或者正在处理大量的用户输入,那么问题案例就无处不在。或者,如果你正在处理一个复杂的问题空间,其中的事物会以意想不到的方式组成(例如,语言解释器)。
如此富有想象力的作家竟然提出了如此无聊的建议。
有创意的写作和无聊的建议都是因为他很聪明。