我最近在一篇文章提到,工程师应该怎样避免使用大量的库、包以及其他依赖关系。我建议的另一种方案是,如果你没有达到重用第三方代码的阈值时,那么你就可以自己编写代码。
在本文中,我将讨论一个在重用和从头开始编写之间的抉择。这些技术能为你提供良好的综合优势。你将会听到我提到的一些在不同编程语言/环境下的例子。但是,这篇文章的层次足够高,而且说实话,你在什么环境下工作,并不重要。
我们一再要求她把字写得小一点。她浪费了那么多纸张。
四种编码技术设想你所处的情况是这样的,你希望你的软件能够完成当前无法完成的任务。比如,你也许想要一份 PDF 格式的报告,方便打印。但是你的软件并不会输出 PDF。作为一名工程师,你要做的工作就是解决这个问题。
下图展示了四种不同的技术来添加这个功能。我并没有提到“这四种技术”。当然,我肯定还有更多的技术,包括镖靶和猴子。
为什么这么多决定都要权衡利弊呢?这真的很让人恼火。
为了确保我上面的图表给人留下正确的印象,我给了它两把斧子——一把是努力,一把是控制。
另一方面,这家伙的两把斧子也让人印象深刻。
所有这四种技术都是在努力和控制之间的权衡。
你可以通过重用快速地获取大量的功能,然后以软件膨胀、黑盒调试、安全修补和耦合升级的形式遭受缺乏控制的痛苦。在重用的情况下,你不一定会面临这些问题,但是这样做的风险会增大。在你的项目中添加的每一行代码就好像是一张彩票,会“赢得”一个非常糟糕的问题。
或者在这张图的另一端,你从头开始编写所有的代码,你将会对进入项目的新代码进行完全的控制。不过,你可以花些无聊的时间去把所有的东西都按你的想法去编写,而你却成了编程的天后。
在我以前的一篇文章《多编写,少重用》(Write More, Reuse Less)中,我已经详细地阐述了重用和编写之间的权衡。
我将会深入探讨两种中间技术(复制和重写)。
复制有许多可以复制代码的好地方。如果你要完成的任务可以用一句话来完成,而且代码不超过 100 行即可实现,那么,你只需把问题输入到搜索引擎,就能轻松地找到。
与 DuckDuckGo 相比,谷歌最大的优势在于:“DuckDuckGo”作为动词,在会话中会让人感到难堪。
用这种方式来搜索一些简单的代码任务的问题,你将很快就能找到代码的天堂。
我从 StackOverflow、W3Schools、MDN、Unity Answers 和各种我懒得记住的地方复制了很多代码。我总是先从搜索引擎开始,在那里提出问题。通常情况下,我会先从我工作的语言/平台开始,然后再提问。在我敲代码的时候,看一下自动完成的内容往往很有帮助。
GitHub Copilot从复制人类答案的机器人那里复制你的答案。
如果你更愿意相信由复杂的人工智能为你编写的代码片段,可以试试 GitHub Copilot。这是一个集成到 IDE 中的插件。基本上,你并不需要在搜索引擎中输入你想要的东西,而是将该文本输入一个略微详细的源码注释即可。然后,实现的源代码就会自动填充在下面。
是真的!我不是在开玩笑。请看一些关于它的视频。
视频:https://youtu.be/FHwnrYm0mNc
超越代码片段如果你想要为更大的功能部分复制代码,那么只要:
我刚才提出的一种做法,不是很傻就是很疯狂。我真心希望你能想到下面那个问题。别让我跟你扯淡。
“什么时候从第三方项目中复制会比直接导入更好?”如果你仅仅想将一个依赖关系的所有未改变的文件复制到你的项目中,那么我将会发现这种复制方法的缺陷。你复制的源码不会轻易地被更新到依赖关系的新版本。这就意味着你会错过 Bug 修复、新功能和安全补丁。
尽管在一些情况下,对某个特定版本的代码进行快照非常有价值,但是你可以通过构建清单(例如 Java 中的 pom.xml,Node 中的 package.json)使用固定版本来完成同样的事情。而且如果你决定要升级的话,那么你可以轻易地更新一个固定版本。
另外一个潜在的复制理由是,你可能需要对项目的源码进行修改。如果你要进行增量更改,你可以最好这样做:1. 从原项目中创建一个复刻项目;或者 2. 把你的更改贡献给原项目。这样可以让你在以后更容易从原项目中收到修改。
有三种不同的方式可以让你在你的项目中修改他人的代码。最上面的一种做法是不好的。
这也许是你大规模、全面地修改你的代码库。你准备在以后的合并项目中,把一切后路都堵死。那真是太好了。这往往是有正当理由的。但是我在本文中所提供的定义是,对源码的大规模结构性的修改属于“重写”技术,下面我会详细讨论。
我唯一能想到的一个很好的理由就是,将第三方项目的代码复制(而非重写)到你的项目中:你只需在这个项目中得到一些源码即可。
而这种理由出现的频率超出了你的想象。大型 Node 包,如 Lodash 和 TurfJS,都是非常聪明的,它们会提供子集包,这些子集包只是为了你需要的特定功能在函数级颗粒度来导入。不过,在野外中也有很多臃肿的怪物。
Nodelerfish 需要你的爱。但不要让它进入你的项目中。
你也许只需要使用 50 行代码就可以完成的库 API。但是单体导入的库,可能会拉来数十万行代码,特别是那些具有横向依赖关系。在这种情况下,将一个子集复制下来,而非将其导入到整个该死的烂摊子中,这完全是很合理的。
开源许可证我不是一个律师。既然我不是律师,我就会把我在开源许可方面的实践告诉你。你可以咨询顶级律师团队,以达成你自己的决策。
笔者作为非律师,是如何做的:
如果我把源码复制到我的项目中,我把它和导入代码一样对待,并遵循所有的许可条款。在开源软件许可的条款中,一般都是指分发源码或者从许可源码中构建的行为。如果复制的代码是我发布版本的一部分,那么它也算作分发。对于我使用的确切的开源许可证,我也非常谨慎,例如,GPL 可能会要求我使用 GPL 许可证来许可我自己的代码库,而 MIT 许可证几乎不要求我做任何事情。
我还喜欢将复制的代码归入“第三方”目录(例如 Github 上的例子),然后在文档的顶部添加注释,这样就可以保证所有的内容都是一致的。
他们不认识我,但 Matt Daly 和 Chris Anderson 是我的代码兄弟。
重写的优点你知道你正在复制的代码里有什么吗?它是适合你的代码吗?
你能够而且应当对其他人的代码进行判断。或许不要对这段代码做出无情的评论。但是,请务必为自己的利益,私下评估第三方代码。
重写代码片段如果是 200 行以内的代码片段,或者是复制的代码,我一定会逐行重写代码,这样可以了解到这些代码是如何工作的,并且做一些改动,使之更适合我的项目。这是为数不多的几次在其他方面进行毫无意义的、风格化的编辑,是有价值的。
我要举一个具体的例子来说明。现在,我要从互联网上某个地方随机找出一个代码片段,然后进行重写。
左边的代码是原来的。右边的代码是我重写的。
我重写的代码片段是由 StackOverflow 上一位名叫“Mark”的人发布的。他的代码通过对每一个点与它的左右邻居进行平均,使折线图中参差不齐的高峰和低谷变得平滑。
概括地说,在重写代码中,我做了以下的改动:
我编写代码的风格并不比 Mark 的好。它只不过是碰巧以正确的方式触动了我的大脑。这并不足以让 Mark 的代码发生改变。下面是重写他的代码片段的真实益处:
所以,这种浅重写是一种很好的方式,可以把别人的代码导入到你的项目中。有些问题是可以避免的。你可以根据你的用例和其他需求对代码进行调整。另外,你还可以在学习新的算法和实践中,成长为一名工程师。
重写的许可考虑因素笔者作为非律师,是如何做的:
更深入的重写和修剪我想,完全的逐行重写应该可以免除几乎所有开源软件许可的法律义务。但是我始终坚持着开源的理念,例如,在源码的注释中引用原作,或者为原项目提供帮助。
有时候,你希望导入多个文档或数千行的源码,并做大量的改动,以让新代码适合你的项目。尤其是,修剪掉你实际上不需要的东西,是很好的做法。
下面是一个简单的复制和修剪的方法:
你的 IDE 的选择和配置应该能够很好地支持你完成这项任务,包括提示、语法高亮和通知功能,这些功能可以向你显示:
如果你的 IDE 没有为你对这些进行适配,可以花点时间去做更好的设置。
git commit 和 git checkout 可以让你设定一个很好的状态,在你因过度修剪而破坏一些东西的时候,能够恢复到之前的状态。这是一张很好的安全网,可以使你的工作速度更快。
我都能听见你想说什么了……
“可是,要重写代码的话,实在是太费事了!”我不是说你一定要重写。只有在一些情况下,如果你这么做,就能得到很好的回报。我来告诉你一个真实的案例,我重写了一个第三方项目,并且从中获益良多。
我先从依赖树开始,如下所示:
在花了半天时间完成重写之后,我去掉了 5 个依赖关系的需求,最后得到了如下结果:
有一个名为“microphone-stream”的 NPM 包,我在 Web 应用中使用它来发送从麦克风捕获的样本缓冲区到语音识别包(Cieran O'Reilly 的 vosk-browser,如果你有兴趣的话)中的一个接口。
我最初是在“让它工作”的开发阶段使用 microphone-stream。它包含在一个示例 Web 应用项目中,我已经将其复制到我的项目中。microphone-stream 运行得很好,直到我升级了一个构建工具(Webpack),这破坏了 readable-stream 的构建,这是一个更高级别的依赖包。我研究了对这两个第三方库中任何一个可能的 PR 修复。不过,向仓库提交一个好的修改需要花费好几天时间。我由于种种理由而拒绝了其他的变通方案。
通过查看 microphone-stream 的代码,我意识到我并不需要该库的核心功能:一个 Node.js 风格的流接口。因此,我认真地重写了那些我真正需要的那部分代码,把那些我不想要的东西删除掉。
一路走来,我在源码中发现了这样的宝藏:
还有这个:
我很感激地将这些想法合并到我重写的代码中。
作者 Nathan Friedly 在这样的源码注释中阐述了他的思考过程。也许他拯救了我,可能让我以后不用再找漏洞来修复了。从这个角度来看,重写比从头开始编写要好。你可以“捕捉”到别人来之不易的知识。
因此,对我来说,这是一个明显的案例,重写,而非重用,可以节约我的时间,并且让我得到更好的结果。重写也比从头开始编写要好,因为这样可以让我了解其他工程师的真实经验,否则我可能会错过这些经验。
复制和重写——试试吧!
这是好东西。这算不上作弊。
只要遵循开源许可的条款,和你的工程师伙伴成为好邻居吧。
你不希望复制或重写所有的东西。但是要学习辨别哪些情况值得你这么做。
并享受与其他人工作中的联系。那些数以百万计的项目,都是由伟大的头脑构建的。
作者介绍:Erik Hermansen,博主,撰写关于工程、技术,以及人机共同构成的系统的文章。
原文链接:https://levelup.gitconnected.com/copying-other-peoples-code-is-very-cool-717e8a72aa3b