如何更好地编程(或者这就是你的代码的问题所在)
你有没有收到过代码审查报告,说你的代码“有错”?我在一家独角兽初创公司工作时就遇到过很多次这种情况,而且很多时候我都不明白到底哪里出了问题。如果有人说你的代码有错,却无法解释原因💢,或者更糟的是,他们根本不愿意解释,这真的很烦人。
曾经有一段时间,我痴迷于理解好😇代码和坏😈代码之间的区别。本文旨在与大家分享我在探索优秀代码和糟糕代码之间的区别的过程中所学到的知识。希望我能够帮助大家理解一些概念,而这些概念是一些资深代码审查员无法用专业术语向我解释清楚的,因为我的资深开发人员沟通能力很差。
代码审查反馈的怪癖
关于代码审查反馈,有很多话要说,我将精简一些比较重要的部分,以便更好地理解我们正在讨论的主题。我想在这里告诉你的是,代码审查反馈经常涉及沟通问题💬,而我们作为开发人员往往会遇到这种问题。
虽然也有一些例外,但很多专业开发者并不擅长社交。我们大部分时间都坐在电脑前,这很正常。事实上,你之所以成为一名开发者,很可能是因为你性格内向,更喜欢在这些设备前度过更多时间,而不是与人打交道👀。如果你性格外向,坚持住,还有机会教你如何更好地编程😅。
我们的工作是与计算机沟通,尤其是指挥计算机执行特定任务。我们不像医生、律师或几乎任何其他职业那样与人互动,我们写的代码越多,与人的互动就越少(除非你进行结对编程)。更糟糕的是,在疫情期间,我们比以往任何时候都更加孤立😷。所以,与其他职业的专业人士相比,我们的个人沟通能力很可能处于平均水平或低于平均水平。
好了,话虽如此,让我们来探讨一下代码审查。
技术术语
在我工作中多次代码审查中,一位同事提出了类似“你没有使用技术“X””之类的意见——被拒绝了——我当时就想,等等,我怎么知道大家一致同意使用技术“X”。在与 CTO 讨论了我使用的技术“Y”之后,它被接受了,因为“X”技术的利弊权衡不值得花时间去修改它💵。
之后,我去找了做评审的人,问他关于X技巧的问题。他告诉我类似“用谷歌”之类的。然后我意识到,从事这些非常抽象的工作,并且不得不与那些不愿表达自己想法的人打交道,这很不公平🤔。
我竭尽所能去学习“X”技术,但最终发现我根本不需要用它。为什么开发者要这么做?为什么我们要避免讨论如何编程?什么方法有效,什么方法无效?“A”在哪些方面比“B”更好?“B”在哪些方面比“A”更好?为什么我们要诉诸权威,或者用“我们就是这样做的”或者“我们的“X”语言前辈说我们应该这样做”或者“这种方法和“X”框架的语法类似”之类的话来掩盖我们的论点?🤨
避免冲突
我们人类常常会带有偏见,因此我们需要基于证据做出决策。我毫不怀疑,许多数据驱动型决策公司之所以成功,是因为他们不会听从管理者的直觉,而是倾听客户的意见,更重要的是,他们重视客户的数据。
我们总是自以为很擅长自己的工作,往往不愿与他人分享自己的工作成果,也不愿评审他人的工作。但要知道,任何有意义的事情都离不开团队合作。所以,团队合作与代码编写同等重要,甚至更重要,因为即使你编写了一段超级优化的代码片段,如果它无法维护,那也毫无意义。总有一天,有人会删除它,并创建另一个具有新特性的版本。
分享想法,避免使用技术术语,与团队进行真诚的交流,并在提供反馈时直截了当,这是维护代码库健康发展、使其可扩展的良好模式。不要被紧急事务所迷惑,而这些事务会让您远离真正重要的事情。务必花时间解释您在代码中做了什么,更重要的是,始终愿意倾听他人对他们和您代码的想法💡。
我不喜欢你这样做
有时,代码审查人员会忘记分享审查结果背后的原因。我见过有人拒绝代码,理由是:“代码能用,但我不喜欢你的实现方式。” 代码审查的客观性🎯至关重要。如果你要拒绝别人的工作,请不要这样做,至少要礼貌地解释一下原因。否则,你的代码库将难以为继。如果有一天你需要代码审查方面的帮助,你需要立即培训一名团队成员,让他们了解你的代码审查流程、注意事项、代码风格指南和规则。
我知道这种情况听起来很傻,不过,我确实发现有些项目里的人认为,只有他们自己才能批准拉取请求,就能保住自己的饭碗🤢。此外,他们会拒绝任何试图改变他们认为正确的代码风格的尝试,除非他们确实有客观理由这样做。
这是代码可维护性的反模式,即所谓的“单点故障”,应该避免,因为它对代码库非常危险。我见过一个极端的例子。我曾与一位这样的代码所有者“独裁者”共事。他破坏了项目的 git 仓库,并通过使代码无法再次访问而窃取了😈客户端。
大多数情况下,当你认为代码需要修改时,正确的代码审查反馈方式是先从好的方面开始,然后再转向不好的地方,比如没有遵循代码风格指南或没有使用良好的实践。总之,也存在程序员彻底失败的情况❌。在这种情况下,我的建议是尽可能客观直接,不要批评作者,而只专注于批评代码本身。
神话般的优秀审阅者高级开发人员
我们需要强调的是,在某些情况下,代码审查者其实是非常睿智的良师益友。你可以从他们身上学到很多东西,不仅仅是知识,有时他们甚至会教你如何教学。他们会给你一些关于如何修复代码🛠️的建议,以及一些关于如何改进代码的想法,并指出在特定代码片段中你应该注意哪些权衡利弊。
重视这类资深人士,因为他们能为团队带来稳定性。他们使团队更具可扩展性,这意味着你可以更轻松地向团队添加更多人员。此外,👉良好的代码审查还能提高整体代码质量👈,改善团队成员之间的沟通,建立信任,展现尊重、透明和谦逊。
关于好代码和坏代码
好与坏是主观概念,通常强调的是一套特定的价值观,而这些价值观因项目而异,甚至因团队而异。当有人告诉你“你的代码很糟糕”时,这并非放之四海而皆准的真理,你应该这样想:他/她想告诉你的是,“你的代码不是他/她认为的好代码”。🙃
你应该始终问自己,为什么你的代码没有达到预期。你需要根据团队的价值观调整代码,如果这些价值观已经明确,则需要鼓励大家讨论,找出正确的做法,否则以后会变得一团糟🛣️。
你的代码和价值观应该保持一致。如果不能做到这一点,我们就需要制定原则来解决这种差异,因为没有一套流程、风格指南或一套惯例能够涵盖所有可能的编程情况。每当我们需要做出决策,明确哪些事情对公司或项目至关重要时,原则📜都能帮助我们。而原则可以基于价值观制定。
例如,有些情况下速度比可读性更重要,这种情况不太可能发生,但确实存在。然而,我发现大多数公司并不了解自己的价值观,也不理解自己为什么要这样做。他们没有原则,大多数时候都在即兴发挥或依赖软件工程师的经验。这种做法有时会有所帮助,但你应该避免“即兴驱动开发”。这只会酿成灾难💥。
既然我们已经讨论了代码的优点、缺点、原则和价值观,那么我们可以更广泛地讨论代码的优点和缺点。
怎么了
我们知道“坏代码”并非普遍存在🌎。然而,“优质”代码通常具备一些坏代码所不具备的普遍特征,而这些特征往往正是你的代码存在问题的原因。通常情况下,你不会在在线课程,甚至在大学计算机科学课程中听到这些概念的明确提及。
专业开发人员通过阅读编程书籍📚以及从经验中学习不同公司使用的概念和方法,来了解这些概念。认真对待软件业务的人会对这些概念感兴趣。以下列表概述了我认为我们专业程序员认为的一组被广泛接受的、能够调节代码质量的特征:
如果你想成为一名真正的专业开发者(一个自称是开发者的人),你需要理解上面列出的概念。这些概念使你能够与其他开发者进行深入的交流,从而与他们合作编写高质量的代码🏆。
为什么是错的
好吧,有人告诉你你的代码“错了”,但为什么呢?这意味着(大多数情况下)你在上一节列出的一个或多个特征上失败了❌。
你没有在合适的场合应用一个众所周知的设计模式,该模式可以使你的代码更具可预测性🧩,而且你无法解释为什么做出这个决定。审阅者根据他/她的经验🧙♂️发现了你逻辑中的一个缺陷,并认为这在以后会成为一个问题。你没有遵循 SOLID 🦉 原则,这些原则可以指导你创建灵活且可扩展的类。你的工作存在代码异味🦨,而且你无法解释其原因,这意味着你正在尝试将需要重构的代码推送到生产环境。你没有遵循团队📑的风格指南或代码约定。您的审阅者有偏见,他/她认为他/她可以做得更好,但他/她只是在虚张声势,实际上,他/她不喜欢您的解决方案,可能是因为他/她没有意识到与实际问题相关的权衡:在这种情况下,您可以说“请您再看一眼并关注......”。
要想成为一名成功的软件工程师,你需要具备解释代码的技能。你需要捍卫每个决策背后的理由🛡️。否则,你的代码会更频繁地被拒绝。这个话题本身就很复杂,我可能需要另写一篇文章来专门讨论它,但现在就先这样吧。
如果有人拒绝您的代码,而您认为您的代码是正确的,请表达您尝试的意图,然后询问如果这些其他数字方法似乎不够好,为什么该特定方法是错误的,表明您评估了不同解决方案之间的权衡,并表明您研究了🔬有关您正在尝试解决的问题的细节。
这些是我在团队协作时发现的,在代码审查中收到负面反馈的主要原因。既然已经解决了“是什么”和“为什么”,现在我可以教你在创建新代码和扩展现有代码时要记住的一个细节,这将提升或至少保持你的代码库质量💪。
如何编写好的(通用)代码
如前所述,高质量的代码有几个方面,但是我经常发现各种组织中的开发人员都会犯这个错误,这会增加代码库成本的可维护性。
我们可以做一个练习来探讨这个主题,假设我们正在一个项目中工作,我会解释代码库的创建情况和相关的决策。特别是,我们将探讨代码的编写方式如何影响代码库的可读性,所有这些都将用一个关于盒子的故事来讲述🔲。
曾几何时
一位名叫 Bill 🧒 的初级开发者刚从大学毕业,就开始四处投递简历。在同一座城市的另一边,有一个叫 Terry 👨 的家伙。Terry 对编程一窍不通。他唯一知道的就是自己想开发一款应用或 Web 应用。通常,当他向别人解释自己的想法时,他会说自己正在打造“insert_any_words_here”版的 Uber/Spotify/Facebook。
有一天,特里意识到他需要开始寻找软件开发人员来实现这个伟大的原创想法——意大利汤食谱版Spotify,或者类似这样奇葩的东西。他决心利用祖父👴的财富💰让自己更加富有🤑。
特里开始问他的朋友,他一直在找开发人员,想雇佣他们,但一直没找到。后来他想起他的一个朋友做着和电脑相关的生意,生意很成功。他的朋友肯开了一家电脑商店🖥️。“当然了,他肯定知道我在哪里能找到开发人员”,他心想。他去了肯的电脑商店,问了那里的人,似乎没人知道肯是谁。他打了个电话,肯告诉他,肯通常不会在店里露面。肯每年只在他的店里露面两三次。
最后,特里问了肯一个大问题:“我在哪里可以找到开发人员?”🕵️ 肯回答说:“我不知道,我唯一需要程序员的时候,就是我最终花钱用程序创建网页的时候。” 接下来,特里开始用谷歌搜索,找到了一些自由职业者的网站。
他想聘请本市最优秀的开发人员。很快,他意识到聘请软件开发专家的费用很高。然而,他坚持自己的想法,需要寻找替代方案。最终,他花钱在当地报纸和分类广告上刊登了招聘广告📰。
两天后,急需找工作的比尔看到了特里的广告。比尔打电话给特里,最终他们见面了。比尔和另外两个刚毕业的同事一起,开始创建🌈Spotitaliansoup🦄平台。
项目启动
大家都很开心🤗,尤其是 Terry。他正在搭建一个平台,价格似乎只有一位资深软件工程师自由职业者给出的最初估价的十分之一。
与此同时,Bill 和 Boyz👬 开始觉得,编写📝生产代码其实并不难。每个人都在根据自己认为更高效的方式进行编码。整个代码库缺乏一致性,他们甚至使用多种工具来完成同一项工作。他们没有时间表,唯一能控制项目工期的,是 Terry 决定给每位开发人员最多支付 6 个月的薪水。
☣️ 28天后 ☣️
有一天,Bill 从零开始创建 soupImporter 模块,并决定💡创建一个名为 soupFoo 的函数作为该模块的一部分。该函数接收一个参数,然后读取给定的 soups 文件列表,接着收集所有文本。之后,它会进行一些转换,例如将标题大写或用默认数据填充空数据。最后,将处理后的数据输出到 JS 对象中。这大约需要 15 行代码。
这里你在带有方框的图表中看到了 soupFoo 逻辑!!!:
这个项目接下来的每一天都是反复试验🐘的集合。
☣️☣️☣️ 28周后 ☣️☣️☣️
Terry 的个人截止日期快到了,Terry 发现他们没什么可发布的😟。当然,他们有一些不错的原型和系统部分,可以完成预期的功能,但没有一个能形成最小可行产品的雏形。
突然间,Terry 开始频繁地问 Bill 和 Boyz:“为什么花了这么长时间?”😠 他们每个人都告诉 Terry 自己的现状,却无法给出准确的估算,因为他们根本不知道自己在做什么。Terry 开始觉得,如果继续这样下去,成本会比预期的要高。
他开始在互联网上寻找如何简化问题,因为此时他已经花费了大量的时间和金钱💸,他了解到他需要一名项目经理,他阅读了有关使用 API 来减少生成内容时间的信息。
最终,他发现了一个可以按需提供意大利汤食谱🍲的API。他雇佣了朋友的表妹凯伦👩。她学的是艺术,但正在找工作。这个项目的时机恰到好处。会议结束后,特里向大家介绍了凯伦,她是他们的新项目经理。她的工作是让开发人员专注于重要任务。
Karen 的首要任务是管理意大利汤 API 的集成。她和 Bill 讨论了这个任务。Bill 告诉她,实现可以立即完成。他知道可以在 soupFoo 函数中实现意大利汤 API 的使用。
第二天,Bill 在实现 API 的时候,突然想到 🤔 可以在 soupFoo 逻辑中加入一个 if/else 语句,并传入一个新的标志位作为参数 👿 ,这样他就能知道什么时候要从 soup API 读取汤配方,还是要读取他们自己的汤配方。他知道这个方案不太好,但确实能加快实现速度。
这里有带有 boxesss 的图表中的 soupFoo logic v2:
这是 Bill 在这个项目中的最后一次提交 😔 。他和一位大学朋友找到了一份更好的工作。Terry 和 Bill 兄弟俩对此完全没有预料到。Terry 迅速聘请了另一位刚毕业的初级员工 Bob 🧑🎓 。希望 Bob 和 Bill 兄弟俩能在圣诞节前按时完成项目。
18 个月后,1,543 次提交……
18个月过去了,已经没人记得鲍勃、比尔、凯伦和孩子们了🌵。公司一直在招新人,也有人辞职,特里的家人建议他结束这个项目。但特里不想让人觉得他是个傻瓜😡。他继续推进这个项目,投入更多资金💸。
有一天,一位名叫 Mark 🤓 的项目经理要求新来的 Tim 复现并修复他们在端到端测试中发现的一个 bug。这个 bug 与应用程序读取汤菜谱的方式有关。
当 Tim 用 boooooxxxesssss 检查 soupFoo 函数版本 17 图表的代码时,他发现了以下情况:
现在想象一下可怜的 Tim 😐。他需要找出哪些框与报告的 Bug 相关。他开始计算需要测试多少个案例,因为他没有更多关于 Bug 发生方式和时间的信息。他唯一知道的是,这个函数出了大问题👻。这种情况加剧了他的冒名顶替综合症😢,接下来发生的事情就是 Tim 通知 Terry 他要辞职。
最终幕🎭
终于,Terry 忍无可忍了🤬,他决定聘请一位专家来完成这个项目。现在,钱已经不重要了,重要的是因为浪费了这么多钱,最终在亲朋好友面前显得很蠢🤪。在谷歌上搜索了🕵🏽♂️一段时间后,他找到了 Steve,一位拥有 20 多年经验的软件工程师🦄专家,在一个自由职业平台上获得了满分。
经过几次会议和协商后,Steve 向 Terry 提交了一份带有时间表的计划📉📅。该计划包含以下任务:
1) 记录现有代码📄
2) 确定代码优先级🆙
3) 根据优先级创建测试🟩🟥
4) 重构需要重构的代码🧩
5) 记录新代码📄✖️2️⃣
6) 创建新测试🟩🟥✖️2️⃣
有一天,在进行重构里程碑时,Steve 🦄 发现了我们挚爱的 soupFoo 🧟 函数。他想起了 2000 年代后期看过的一个电视节目 📺 里的一句台词。在这种情况下,与其说是批评计算机的糟糕外形,不如说是代码质量低劣 🔫 。
从项目一开始到整个开发过程中,Steve 都做了需要做的事情🦉。他确定了 MVP (YAGNI) 所需的所有功能,删除了重复行为 (DRY) 👍,并简化了逻辑 (KISS) 👍。
经过 3 个月的工作,他创建了 12 个函数来划分所有 soupFoo 行为🌈:
Steve 对他抽象所有功能的能力感到惊讶。他对自己的工作成果🎖️非常满意。他完成了原定时间表内的所有工作。之后,Terry😃 可以邀请家人和朋友参加他的🌈Spotitaliansoup🦄平台的 Alpha 测试了。
几周后,特里获得了第一批订阅用户,但他意识到收入增长速度会非常缓慢😓,因为没有多少人喜欢意大利汤。不过,他问史蒂夫,他们能否调整平台,让它不再发行意大利汤,而是发行电子书📚。由于平台设计灵活,这确实可行。这个项目的收入回报对他来说更好,至少可以收回他的成本。
随着岁月的流逝,🌈Spotitaliansoup🦄平台的许多贡献者都羞愧地回忆起那几个月的工作😳。Terry 学到了很多东西,他经常问自己:“如果我没有找到 Steve🐴 会发生什么?”
尤达悖论
故事结束了✋,现在我想在本节中讨论一个与代码质量相关的话题。这就是当你作为软件工程师达到更高的代码质量水平🏆时会发生什么。
我非常幸运🧐能与像史蒂夫这样拥有 20 多年经验的软件工程师共事。在编写高质量代码的更高层次上,我发现了一个有趣的规律。这些工程师给出的最明智的建议大多是这样的:
“你应该知道如何重构,这样你就不需要再重构了”
“你应该知道如何使用设计模式,这样你就永远不需要使用那些设计模式”
每次他们说这些话,我都看到其他同事说这简直是胡扯😕。但我相信这些专家没有撒谎,这些建议传达了编写高质量代码所需的深刻智慧,但要耐心等待,因为其中有一个陷阱。他们真正想说的是:
你需要获得智慧🧙♂️,发现软件工程师所遵循的原则、技术和方法所定义的限制内的自由
这意味着你不应该盲目遵循所有推荐的原则、技巧和方法🤖。最好了解每种资源的“是什么”、“如何”、“为什么”、“何时”和“在哪里”🤔。
别误会,我并不是说你不应该遵循这些原则、技巧和方法。你肯定会经历一个阶段,会盲目地遵循它们,因为这是一个过程,但这并不意味着它会永远保持不变🕰️。
要达到这种精通水平,您需要经历漫长的旅程✈️阅读数十本书🏝️,花费数千小时编程🏞️,花费数百小时与其他程序员一起工作🗻。
我相信这就是尤达在《星球大战》电影中想要教给卢克·天行者的东西。
“你必须忘记你所学到的东西”
——尤达
我坚信,这应该是任何一位真正渴望达到顶尖水平的软件工程师的目标🏅。没有捷径,任何说这很容易的人都是在撒谎,真正需要的是努力工作🏋️♀️,根据你在特定时间可以访问的所有数据做出明智的决策,并且你需要自律。
结论
关于代码审查
记住需要审查的代码方面:
- 正确性和理解力📝
- 通用风格(代码约定、风格指南)📔
- 特定于语言的可读性👓
在审查时捍卫你的代码🛡️,如果它是错误的,要求解释。
建议:当代码审阅者提出与原始代码等效的解决方案时,修改已完成的代码会更加昂贵🙅。除非有明显的缺陷,否则应优先考虑原作者的代码。
代码审查应该被视为一个分享知识的机会🧠。代码审查应该与编写代码同等重要,因为代码库属于整个组织,而不是作者,因此需要团队成员来审查你的代码。开发人员应该找到克服代码审查带来的焦虑和压力的方法。每个人都可以通过正确的沟通方式协作,以减轻这个过程的压力。
关于编写更好的代码
我的建议是,你应该一直使用那些框图(用来解释 Spotitaliansoup 的故事)。注意 if/else 语句的使用会产生逻辑分支🎋。如果你发现自己编写的函数重复这种在 if 或 else 语句中嵌套 if/else 的模式,那你就错了。当然,编写任何程序都需要有最低限度的 if/else 语句。
我的建议是,在最坏的情况下,函数应该有一个 if (if/else) / else 或 if / else((if/else)) 比你做错的更深层次的东西❌。
我在这里的主张是,我们应该至少保留 if/else,否则函数的复杂性会呈指数级增长🤯,实际上是函数的 2倍,这不是一种观点,而是一个事实,这意味着测试、记录、调试和阅读代码的成本也会呈指数级增长,当然,有很多方法可以编写糟糕的代码,但这是我经常发现的一种代码,它代表着大量的金钱和时间的浪费,这就是我写这篇文章的原因,因为它很常见。所以,这就是 soupFoo 函数故事的要点,你需要明白,如果你不小心编写的代码,你就会伤害你的代码库、你的团队、你的公司和你的家人,因为如果你不知道如何正确工作,你可能会丢掉工作。
我们应该追求的是代码的稳定性,但如何才能做到这一点呢?编写完整系统的代码是一项相当复杂的任务。我们应该遵循的原则是,我们需要创建不会出错的代码。秘诀🙊为了编写不会出错的代码,你需要编写不会出错的代码,或者至少在短期内不会出错。那么如何做到这一点呢?尽可能避免在 if 语句中求值表达式,如果无法避免,请确保这些表达式的求值之间保持一定的距离。
if 表达式之间的距离越近,其中隐藏的 bug 就越有可能。所以你应该尽量减少 if 表达式之间的距离,并且尽量避免它们彼此靠近。你因为一个隐藏在 if/else 语句中的 bug 🕷️ 浪费了多少时间?它看起来像这样:
if (expression1) {
if (expression2) {
if (expression3) {
if (expression4) {
}
else {
...
}
}
else {
//Your bug was here
...
}
}
else {
...
}
}
最糟糕的是,有时我们很懒惰🦥,我们根本不关心代码的功能,只是简单地在分支功能中添加另一个 if/else 语句,这是错误的。我刚开始工作时就这样做过,所以我在多个项目的代码库中都能识别出这种模式。尽量避免嵌套 if/else。
如果你的代码库中没有这个问题,或者团队里没有人遇到这个问题,那么恭喜你🎉。至少我在这里添加了其他主题。希望你在这里学到了一些东西。
关于追求智慧
我想说的几乎都在这一部分。我只想建议你,你可以追随你的热情,但别忘了做人🧍。你的同事和亲戚不是电脑🤖,你仍然需要与他们互动,你仍然有东西可以向任何人学习。所以,继续教学,更重要的是继续学习。
一般来说
要想创造任何有意义的东西,你需要团队合作。那种只靠自己创办公司的天才程序员🧙♂️简直是天方夜谭。只有加入一支优秀的团队,你才能创造出令人惊叹的东西。
每当有人需要重写你的代码来扩展它时,你作为软件工程师就失败了❌。程序员在编写代码时需要假设它不会被修改,只会被扩展。这并不意味着你的代码应该僵化,它仍然需要灵活,但关键在于,任何更新/扩展你的代码的人都应该找到可以构建的基础🏗️,而不是他们想要破坏的东西。
“高性能代码就像萨拉索塔的豪宅🏘️,十年后就开始倒塌。可读性代码就像屹立百年的古老石头建筑🏛️ 。我无法尊重那些看不出区别的人。”——
弗兰克·安德伍德(Twisted 版本)👌
感谢您的阅读🥺。如果我的英语不好,请多包涵🎩,因为英语不是我的母语。
文章来源:https://dev.to/scroung720/how-to-program-better-or-this-is-what-is-wrong-with-your-code-30gj