作者
NeilKakkar
译者
王艳妮,责编
屠敏
以下为译文:
一年前,我开始了在彭博社的全职工作。我那时就想象着要写这篇文章了。我想象着自己脑中会充满各种各样的想法,而时机成熟时,可以将其诉诸笔端。仅仅一个月以后,我就意识到了这件事并不容易:我总是在慢慢忘记我已经学到的东西。它们要么被完全内化,以至于我的大脑让我以为我本来就知道这些,要么就被我渐渐淡忘了。
这是我开始写“人类日志”的原因之一。每一天,当我遇到一个有趣的情况时,我都会记录下来。我很幸运的能坐在一位高级软件工程师旁边,这样我可以仔细观察他在做什么,以及这与我的做法有何不同。我们经常组队编程,这使得观察他更容易了。此外,在我的团队文化中,在别人写代码的时候站在后面看并不是什么不好的事情。每当我感觉到有趣的事情正在发生时,我就会转过去看看。由于经常凑过去看,我总是知道事情发生的前因后果。
我有一年的时间都坐在一位高级软件工程师旁边工作。以下是我学到的一些东西。
写代码
如何命名
我接手的第一样东西就是ReactUI。我们有一个主要组件,它容纳了其他所有组件。我喜欢在代码中加入一点幽默感,我想把它命名为GodComponent。在codereview的时候,我才明白为什么命名是一件很难的事情。
计算机科学有两个难点:缓存失效,给变量命名,以及差一错误。-LeonBambrick
我经手的每一段代码都带有隐喻意。GodComponent?那时用来盛放所有那些我不知道该放到哪里的的烂代码的。它包罗万象。如果我将一个变量命名为LayoutComponent,未来我会知道,它所做的只是规划布局,而不涉及任何状态。
我发现的另一个好处是:如果它看起来太大了,就像包含大量业务逻辑的LayoutComponent一样,我知道是时候重构了,因为业务逻辑不应当属于那部分。而如果使用GodComponent这个名称,那对里面的业务逻辑就不会产生任何影响。
命名你的集群?根据在它上面运行服务来命名是个好主意,可是你以后还可能会在上面运行其他东西。最终,我们是用团队名称来命名的。
对于函数来说也是一样。doEverything()是一个可怕的名字,这会产生很多后果。如果这个函数可以完成所有操作,那么测试这个函数的特定部分就会变得特别难。无论这个函数有多大,你都不会觉得奇怪,因为毕竟这个函数就是要做所有事情的。所以需要换个函数名,重构。
有意义的命名也有不好的一面。如果名称太有意义并隐藏一些歧义怎么办?例如,在SQLAlchemy中调用session.close()时,closingsessions不会关闭基础数据库连接。(我本应先读手册(RTFM,ReadTheFuckingManual)并防止该bug——关于这一点在debug部分详细展开)
在这种情况下,将名称视为x,y,z而不是count(),close(),insertIntoDB()可以防止赋予它们隐含意义——并迫使我仔细检查它们正在做什么。
从来没想到,关于命名我要说的东西居然不能用一句话就概括完。
旧代码和下一个开发者
你有没有看过一些代码并觉得很奇怪?那些开发者为什么这样做?这完全说不通啊。
我有幸曾经使用过遗留代码库。其中有类似这样的注释,“在与穆罕默德一起解决了这个问题以后,注释就删掉了。”你在做什么?谁是穆罕默德?
我可以在这里做一个角色转换——想想以后来接手我代码的人们——他们会不会发现它很奇怪。Peerreview部分解决了这个问题。这让我意识到了环境的重要性:要时刻记得我的团队正在工作的环境是什么样的。
如果我忘记了代码,稍后又看到它,而无法重新回想起当时的环境时,我会说:“到底为什么他们会这样做?这讲不通......哦等等,这是我自己写的。”
这就是文档和代码注释发挥作用的地方了。
文档和代码注释
它们有助于保留环境(上下文,语境),以及分享知识。
正如Li在“如何建立良好的软件”中所说的那样,“软件的主要价值不在于生成的代码,而在于产生它的人所积累的知识。”
“软件的主要价值不在于产生的代码,而在于产生它的人所积累的知识。”——Li
我们有一个面向客户的API终端,似乎没有人使用过。我们只是删除它吗?毕竟,这是技术负债。
如果我告诉你,每年在特定国家/地区,10名记者会将他们的报告发送到该终端,该怎么办?你要如何测试?如果没有文档(现实中确实没有),我们就没办法。所以,我们没有那么做。我们直接删除了该端点。几个月以后那个一年一度的时刻到了。十名记者无法发送10份重要报告,因为终端不再存在了。
拥有关于这个产品的知识的人离开了团队。当然,现在代码中有一些注释解释了端点的用途。
据我所知,文档是一个每个团队都在努力解决的问题。不仅仅是代码文档,还有代码周围的流程。
我们还没有找到一个完美的解决方案。
我很喜欢Antirez对不同类型的有价值的代码注释的详细分类。
原子提交
如果你必须回到之前的步骤(是的你会的。详见测试部分),这个提交作为一个单元是否合适?
在删除烂代码的时候有自信
删除烂代码或过时的代码会使我感到非常不舒服。我认为多年之前被写下的代码是神圣的。我的想法是“当他们写下这些东西时,他们肯定是考虑到一些事情的。”这是传统和文化与第一原则思维方式之间的较量。删除一年一次的终端也是如此。我在这方面得到了太多具体的教训。
我会试着从周围解决代码,而高级工程师则会试着从中间解决。删除所有内容。一个永远不会运行的if语句?一个不应该调用的函数?是的,一切都没了。我?我只会在最上面写下我自己的函数而已。我没有减少技术债务。如果我做了什么的话,我也只是增加了代码复杂性和给他人的误导而已。对下一个人来说把这些代码功能拼凑到一起会更艰难。
我现在使用的启发式是:现在有的代码你无法理解,而且你知道有些代码是你永远也不会用到的。删除那些你永远不会用到的代码,并对那些你不理解的代码保持谨慎的态度。
CodeReviews
Codereview是非常棒的学习途径。这是一个外部反馈循环,反映了你现在和将来会怎么写代码。差别在哪里?有一种方式比另一种更好吗?我在每次codereview时都会问自己这个问题:“为什么他们那样做?”。每当我找不到合适的答案时,我都会和他们谈谈。
在第一个月之后,我开始在我的队友代码中发现一些错误(就像他们曾经为我做的那样)。这太疯狂了。同行评论对我来说变得更加有趣了——变成了我期待的一场游戏——一场改善我的代码感的游戏。
我的启发是:在我了解代码如何工作之前不要批准代码。
我的Github数据
测试
我非常喜欢测试,以至于如果没有测试,在代码库中写代码会使我感到很不舒服。
如果你的整个应用程序只做一件事(就像我所有的课设一样),那么手动测试仍然可行。我以前就是这么做的。但是当应用程序能做种不同的事情时会发生什么?我不想花整整半小时来逐项测试,而且有时我会遗忘真正需要测试的那一个东西。那是一场噩梦。
这时测试和测试自动化登场了。
我把测试当做是文档。这是我对代码预期效果的文档。测试告诉我,我(或我之前的人)如何期望代码来工作,以及他们认为事情会出错的地方。
所以,当我现在编写测试时,我会记住这一点:
演示如何使用我正在测试的类/函数/系统。展示出所有我认为可能会出错的内容。上述的一个必然结果是,在大多数情况下,我测试的是行为,而不是实现。(点击此处查看我在Google浴室休息期间听来的一个例子: