
选自neilkakkar
作者:Neil Kakkar
机器之心编译
参加:陈韵莹、一鸣
本文是彭博社的一位开发者所写的文章,介绍了从一位资深工程师搭档的身上学到的一些开发经历。
曩昔一年中,我坐在一位资深的软件工程师周围,能够细心地调查他是怎样作业的。咱们两人常常一起编程,使得这项调查更为简单。此外,在团队文明中,从背面窥视写代码的人并不令人恶感。以下是我所学到的:
编写代码
怎样命名
我首要着手的是 React UI。咱们有一个首要组件来放置其他一切的组件。我喜爱在代码里加点幽默感,因而我想要将它命名为 GodComponent。当进入代码查看环境的时分,我才了解为什么命名这么难。
在计算机科学里有两个难题:内存不足、命名、以及差一(off-by-one)过错。
——Leon Bambrick
我每个命名的代码段都有躲藏含义在里面。GodComponent 是一切我不用操心去寻觅适宜方位来寄存那些废物的当地,它能够包容一切东西。假如我早早把它命名为 LayoutComponent,之后的我就会发现它所做的便是分配 layout,没有状况。
我发现命名好的另一个优点是:假如它看起来太长了,就像 LayoutComponent 包含了许多事务逻辑层,我就知道是时分要重构了,由于事务逻辑层并不归于这儿。假如是以 GodComponent 命名,这儿的事务逻辑层也不会和其他有所区别。
命名你的集群?以在服务器上运转的服务称号来命名更好,直到用它们来运转其他服务停止。咱们终究以团队的姓名来命名服务器。
在函数上也是相同的道理。doEverything() 是一个糟糕的姓名,会有许多难以预料的成果。假如这个函数能够做一切作业,那么在测验函数某个特定部分时将变得十分困难。由于不论这个函数有多大,你都不会觉得古怪,究竟这个函数应该做一切的作业。这时分就需求改名、重构了。
有含义的命名也有不太好的一面。假如姓名的表意太强,成果掩盖了一些功用上的细微差别怎样办?例如:当你在 SQLAlchemy 中调用 session.close() 时,这只会封闭会话但不会封闭底层数据库的衔接。
在这种状况下,能够以 x,y,z 来命名而不是 count(),close(),insertIntoDB(),这样可防止为其赋予隐性含义并强制开发人员细心查看它所履行的操作。
前史代码和下一名开发者
你曾否看过一些代码,觉得它们很古怪?这些代码为什么这么做呢?它们的完结一点都不合理。
我曾担任过留传代码库。代码中有比方「当 Mohammad 发现状况时撤销注释代码」这类的注释。这是在做什么?谁是 Mohammad?
在这儿能够做下人物转化——幻想下一个人来看我的代码,他们是否会觉得古怪?
同行查看能够某种程度上处理代码注释这个问题。这让我想到了上下文的概念:留意我团队正处的上下文方位。
假如我忘掉了这部分代码,之后又回到了代码作业上,没有注释的话我不能从头创立上下文,我或许只会想:「为什么他们要这么写?这没有任何含义……哦,等等,是我写的。」
这儿便是开发文档和注释该呈现的当地。
文档和注释
文档和注释有助于保护上下文和共享常识。
正如李在《怎样构建好软件》中所说,「软件的首要价值不是编写它的代码,而是编写它的人所堆集的常识。」
比方说,咱们有个好像没有人用过的、面向随机客户端的 API 终端。由于这些原因,我就应该把它删去吗?究竟这是一个技能负担。
假如说,在某个特定国家,有 10 名记者会一年一次将他们的报导发送到这个终端,怎样办?你怎样测验它?假如没有开发文档(那时就没有)就不能测验。所以咱们没有测验。咱们删去了那个终端。过了几个月后,到了一年中发送的时刻,由于这个终端现已不存在了,10 名记者也就无法发送这 10 份重要报告。
尽管了解产品的人现已离开了团队,可是现在代码中有注释解说终端的效果。
据我所知,文档是每个团队都在尽力的东西。不仅仅是代码的文档,还有关于代码的流程。
自傲地删掉废物代码
我曩昔很不喜爱删去废物代码或过期的代码。我以为曩昔写的代码都是崇高的。我的主意是:「他们写这些代码的时分必定有一些主意。」这是传统和文明与榜首性准则之间的磕碰,与删去一年一次的终端发作的事相同。我在那里学到了具体的一课。
我测验根据已有代码进行作业,可是资深工程师会测验处理掉它——悉数删去。一个永久无法抵达的 if 声明?一个不应该调用的函数?是的,都消失了。
至于我呢?我只会把我的函数写在最上面。我没有削减这些技能负担,反而添加了代码的杂乱程度,以及误导他人的或许。下一个人将作业凑集起来会更困难。
现在我遭到的启示是:有一些代码你或许不了解,也有一些代码你知道永久不会用。删去那些你永久都不会用的代码,当心那些你不了解的代码。
代码查看
代码查看对学习来说十分有用。这是你写代码和其他人写代码时进行的外部反应循环。
两种完结有什么区别呢?一种办法比另一种好吗?每次代码查看时我都问自己:「他们为什么这样做?「。每逢我找不到适宜的答案时,我就会去和他们谈谈。
在榜首个月后,我开端在搭档的代码中找到过错(就像他们对我代码做的相同)。同行查看对我来说变得更风趣了——这是我等待的游戏——一个进步我代码认识的游戏。
我的启示是:在了解代码怎样完结前不要同意它。
测验
我十分喜爱测验,以至于假如没有测验就将代码写入代码库我会感到十分不舒服。
假如整个应用程序只做一件事(就像我一切的校园项目),那么手动测验是能够的。可是假如该应用程序可完结 100 种不同的功用,那该怎样办呢?我不想花半个小时来测验一切的功用,况且有时分还会忘掉一些需求测验的当地。
所以就呈现了自动化测验。
我以为测验是一种文档,是对代码假定的文档。测验会告知我(或我之前的人)他们料想代码是怎样作业的,以及他们预期哪里会犯错。
所以,当写测验时,我会记住:
记载怎样运用测验时用到的类/函数/体系。
记载我所想到的会犯错的当地。
在大多数状况下,以上的结论是在我在测验而不是完结的进程中想到的。
以下是我在 Google 卫生间小休时学到的比方:
我在 #2 中遗漏了一些东西,那里是 bug 呈现的当地;
所以每逢发现 bug 时,保证修正 bug 的代码也有相应的测验(称为回归测验),用于记载信息:这儿或许呈现另一种过错。
仅仅编写这些测验并不能进步我代码的质量,而编写代码却能够。可是我从阅览测验代码中取得了写更好代码的直觉。
可是,并不只需这一种测验,这便是为什么有安置环境测验的原因。
你能够有完美的测验单元,可是假如没有体系测验,就会呈现以下的状况:
这相同适用于现已测验好的代码:假如你机器上没有你需求的库,你会溃散。
为了测验你需求:
有一台你用于开发的机器;
有一台你用于测验的机器;
最终,有一台你安置的机器(请不要用与开发程序运用同一台)。
假如测验和安置机器之间的环境不匹配,你就遇到麻烦了。所以这儿就呈现了安置环境。
咱们先有本地开发环境,在我的机器上是 docker;
然后有服务器上的开发环境,机器上装置了一系列的库(和开发工具),咱们在装置了代码的机器上进行开发。其他相关依靠的测验都能够在这儿进行;
接下来是 beta/stage 环境,它与出产环境彻底相同;
最终是出产环境,它是代码运转和服务于实践客户的机器上的环境。
这儿的主意是测验捕获单元和体系测验无法捕获的过错。例如,恳求体系和呼应体系之间的 API 不匹配。个人项目与小公司的状况大不相同。不是每个人都有资源来树立自己的设备。可是,这个主意仍适用于像 AWS 和 AZURE 这样的云供货商。
你能够为开发和出产设置分隔的集群。AWS ECS 运用 docker 镜像来安置,所以即便跨环境作业也会相对平稳。扎手的一点是其他 AWS 服务之间的集成。你是否能够在正确的环境中调用正确的终端呢?
你乃至能够更进一步:下载其他 AWS 服务的备用容器镜像并运用 docker-compose 来装备本地完好的环境。它会加快反应循环。
规划
为什么我要将规划放到写代码和测验的后边呢?规划本应该在榜首位,可是假如我没有在环境中写代码和测验,我或许会不拿手规划一个遵从环境特性的体系。
在规划体系时,有许多作业需求考虑:
运用编号是多少?
有多少用户?预期添加是多少?(即需求运用多少数据行)
未来或许呈现的问题是什么?
我需求把它转成一个名为「需求搜集」的合理清单。这个进程有点与灵活性的准则相悖——在开端体系开发之前,你能够规划多少部分呢?可是这是一种平衡——你需求挑选什么时分做什么。
当然仅仅搜集需求并不是一切需求考虑的作业。我以为,在规划中包含了开发的进程也是值得去做的。例如:
本地开发怎样运作?
怎样打包和安置?
怎样进行端对端的测验?
怎样对新的服务进行压力测验?
怎样办理秘要信息?
CI/CD 集成?
咱们最近为 BNEF 开发了一个新的查找体系。做这件事真的很棒。我开端规划本地开发,学习 DPKG(打包和安置)和企图处理安置秘要信息的问题。
谁会想到对产品中的秘要信息进行安置会变得如此扎手呢?
你不能将这些信息存到代码中,由于这样任何人都能看得到。
把它们作为环境变量?这是一个好主意。但你怎样把它们放在那里?(每次机器发动时拜访 PROD 机器来填充环境变量是一件苦楚的作业)
安置为秘要文件?文件从哪里来呢?怎样进行填充呢?
并且咱们不想进行手动操作。
最终咱们运用了一个有人物拜访操控的数据库(只需咱们的机器能够与数据库对话)。咱们的代码在发动时从这个数据库中获取隐秘数据。这个能在开发、测验和产品之间很好地仿制——在各自的数据库中都有秘要。
相同的,关于像 AWS 这样的云供货商,这或许十分不同。你不用考虑太多秘要。获取你人物账户,在用户界面中输入秘要数据,在需求的时分你的代码会找到它们。它简化了许多时刻,这十分酷,而我很快乐有经历领会这种简易性。
规划时考虑保护需求
规划体系是件令人兴奋的事。保护体系呢?就没那么风趣了。
我在保护进程中遇到了这个问题:体系为什么会降级,以及怎样降级?
有两个原因能够回答为什么体系也会有降级的时分:
首要,体系不应当舍弃旧的东西,而是在已有的基础上添加更多功用。体系更新倾向于添加而不是删去。
其次,带着终究方针来规划。一个进化到做不应做的作业的体系和一个从零来规划做相同作业的体系相同,没有用。这是一种体系的后退。因而需求对体系进行降级。
现在我知道至少三种下降降级机率的办法:
将事务逻辑和基础设施分隔:通常是对基础设施降级——当运用量添加、结构过期、呈现零日缝隙等状况下;
环绕体系保护树立流程。对旧的和新的组件都运用相同的更新。这能够防止组件之间呈现差异,坚持整个代码「现代化」;
保证一向修剪你不想要的/旧的东西。
安置
将功用进行绑缚安置仍是逐一安置呢?假如答案是将功用绑缚在一起,则会呈现问题。
接下来要问的问题是:为什么想要把功用进行绑缚呢?
安置是否花费过多时刻?
代码查看是否简单进行?
不论是什么原因,这是需求修正的流程瓶颈。
绑缚功用安置至少有两个问题
假如一个功用中有 bug,将阻碍另一个功用履行;
添加全体犯错的危险。
然后,不管你挑选什么安置进程,你总是期望你的机器像一头牛而不是像宠物相同。它们并不宝贵。你知道每台机器上运转的是什么,以及怎样在死机的状况下从头创立它们。当一台机器死机时,你不会心慌意乱,你只需求发动一台新机器。你像牛相同放养它们,而不是像宠物相同养着他们。
程序犯错的时分
当作业犯错时,并且必定会有出问题的时分,黄金法则是将对客户的影响最小化。
当作业出了过失,我天然倾向于从速处理 bug。事实证明,这并不是最抱负的处理方案。与其修正哪里错了,即便仅仅「修正一行」,所做的榜首件事应该是回滚版别。回到之前的作业状况,这是让客户恢复作业最快的办法。
过了这个时分,才应该看看哪里出了问题并修正那些 bug。
在你的集群中呈现一台「垮掉」的机器也应当是相同的做法——在企图找出机器出了什么问题之前,先把它停了,并符号它不可用。
首要找 bug 这种天性会引导我走上处理 bug 的绵长旅途,反而偏离了让客户先恢复作业这一抱负的方针状况。有时分,我觉得它没有作业的原因是由于写的代码有问题,而细心阅览每一行代码后会堕入紊乱,像是一种深度优先查找。
之后,我的启示是,首要开端广度优先查找,然后再深度优先查找,去除最顶端的节点。能否用已有的资源承认:
机器发动了吗?
是否装置了正确的代码?
装备是否正确?
,像代码中的路由是否正确?
形式版别是否正确?
然后进入代码。
在某次犯错的问题上,咱们以为机器上没有正确装置 nginx,但成果是装备被设置为了 false。
当然,我不需求总是这样做。有时分过错信息现已足以削减需求查找代码的区域。并且当我无法处理这个问题时,我测验并继续修正代码以将问题降到最低。修正的次数越少,我就能越快地处理实践问题。
可是我现在仍是会记载花了 1 个多小时来处理的 bug:遗漏了什么?这通常是一些我忘掉查看的愚笨过错,比方像设置路由、保证形式版别和服务版别匹配等。这是了解运用的技能仓库的另一步,并且只需经历会告知我为什么体系无法运转。
监控
这是我曾经从未想曩昔做的事。说句公道话,在全职编码之前,我从没保护过体系。我仅仅树立它们,运用 1 个星期后然后进行下一项作业。
有两个体系,一个有杰出的监控,另一个并不那么好。我逐步十分喜爱监控。假如我不知道 bug 在哪我就不能修正过错。其间一种最糟糕的感觉是从客户那里知道有 bug。
「我做了什么?!我乃至不知道我的体系出了什么问题?」
我以为监控由 3 个部分组成——日志、衡量标准和警报。
日志
以代码中进行日记记载就像人写日志相同,是一个进化的进程。
你要找到你或许需求监控的东西,日志记载下来,运转体系。一段时刻后,你会发现你没有满足信息来处理的 bug。这是增强日志记载的好时机——你的代码少了些什么?
我想你会凭直觉地知道什么东西很重要需求记载,可是在咱们的服务器中我和资深软件工程师所记载的东西有许多不同。我以为只需恳求-相应日志就满足了,可是他会有更多的记载内容,比方查询履行时刻、代码进行的一些特定的内部调用,以及何时转储日志。一切都现已处理了。
几乎不或许在没有日志的状况下进行调试——假如你不知道体系的状况,你怎样从头创立它呢?
衡量标准和惊爆
衡量标准能够源于日志,也能够独立于日志(例如向 AWS CloudWatch 和 Grafana 发送时刻)。你能够决议你的衡量目标并在代码运转时发送数字。
警报是把一切东西整合到一个的强壮监控体系的粘合剂。假如一个衡量标准是当时产品中运转的机器数量,当这个数字降到 50% 时,这是一个很好的警报——你知道有什么犯错了。
失利计数高于某个阈值时?是的,又一个警报。
这儿暗示了另一个需求养成的习气。当你修正 bug 时,你不仅仅重视怎样修正 bug,而是你为什么不早点发现它呢?是否有安置警报?怎样能够更好地监控来防止相似的问题?
我还不知道怎样监控 UI。即便吧组件测验到位,也还不足以了解犯错的状况。这些过错通常是由客户来告知咱们的——这看起来不太对劲。
总结
在曩昔的一年里,我学到了许多东西。当我对这篇文章进行回忆时,我能够更好地领会到我的生长。期望你也能够从这儿得到一些东西!
本文为机器之心编译,转载请联络本大众号取得授权。
------------------------------------------------
