如果你是一名科研人员,在研究的过程中需要用到代码,那么你可能不需要像专业码农那样从头到尾一句一句去写完整的,而是可以将网上的一段符合应用场景的现成代码拿过来直接用。
这听起来是不是很简单?然而实际上...
目前,虽然学术界有资源可以用来编写更好的代码,但很少关注使用他人的代码可能遇到的问题,比如:有时候看着别人的代码过于繁琐、晦涩难懂;又或者绕进去,反而忘了自己一开始要干什么;或者添油加醋最后一团乱麻;甚至即便是费了九牛二虎之力搞懂了,deadline却已经在眼前了...
学会使用他人的代码是现代学术生活的重要组成部分。
本文主要在将行业、现有文献和一些科研人员自己的经验结合起来,为学术研究人员提供相对实用的十条规则。
这10条规则可以分为计划、理解、更改和发布类别。
doi.org/10.1371/journal.pcbi.1011031.g002
下面我们就来详细看一下。
01
明确目标
在正式开始动手写之前,先想一想,你真正想要实现的目标是什么?
是需要计算一些东西吗?
还是需要添加一些代码中当前不存在的功能?
不同的目标需要对应不同的方法。
如果需要计算,那么关键的要求是确信计算是正确的;而如果要添加某些功能,那么可能需要更多地理解代码。
试想一下,是不是每次都需要通过浏览整个代码库,理解每一行代码来实现呢?
明确目标会防止你做无用功,提升效率。这是一个有用的练习。
当你建立对代码的理解时,你的目标可能会改变,这会影响到你如何改变以及是否做出改变。
在学习或者写代码的过程中,要记住这个一开始就已明确的目标,一旦发现行动走偏的时候不断把自己拉回来,不断回到问题,从而以轻松高效的方式完成。
02
选择你的代码库
如果要使用别人写好的程序,你需要在网上搜索,选择一个合适的代码库。那么问题来了,所谓的“合适的代码库”怎么选呢?
几个要点:
- 看文档的质量
- 文档的维护情况
- 社区的活跃程度
当然,其他还可以考虑:
代码库的可访问性、代码的可读性。
也可以考虑选择其他熟悉的编程语言。
有时候别人的代码看不懂,自己的又跑不通,很容易把它全部扔掉,然后重新开始。
到底是否需要重新写一遍代码?
重新写一遍代码也是一种选择。
➪ 好处是可以让你更深入地理解代码的功能和运作方式,也就是说可以学到深度知识,这些知识在使用他人代码时很难挖掘到。
一位计算机科学博士说,他们更喜欢编写自己版本的算法来完全理解它们,一旦完成,他们就会使用现有的库。
➪ 主要缺点是复制已经完成的工作,这可能很耗时,也不必要。
当然,可以阅读现有的代码和文档,了解如何构建自己的版本。也可以使用旧代码来测试新代码,并检查是否正常。
同时也别忘了第一条,明确目标,从而确定是不是要重新写一遍。
►►
使用哪种代码库以及是否重新开始可能都是艰难的选择。重要的是,不要急于做决定,花时间了解别人的代码,再考虑该怎么做。
03
阅读文档
阅读文档有什么用?
文档,就像是代码的翻译官,可以帮你理解它们。虽然不需要像看小说一样读完整个文档,但是拿来当参考还是蛮重要的。刚开始接触他人的代码时,简单地浏览一下文档可以帮助你大概了解它们的结构。
文档里有哪些内容?
如果卡在了某个问题上,也可以去文档里找答案。文档通常会包含一些重要、难懂的部分,这些部分我们读了以后会更清楚。即使文档不太好或者太少,也应该去看一看。如果文档实在少得可怜,那么这说明代码可能不太靠谱,不是遵循最佳实践编写的。
文档不仅包括README文件或在线API,还包括代码注释、文档字符串、变量和函数名,以及任何写下来的测试。好的项目通常会有在线文档,里面可能包含“入门教程”,对你上手很有帮助。
如果文档不存在(或不正确),自己加文档
这不仅对你自己很有帮助,而且还能帮助未来其他使用代码的人。文档不一定要多么正式或完美,它就是你对代码的注释。在探索代码时,你可能会记录很多笔记,把它们保存成文档,过个几周或几个月后可以更容易回到代码中学习或修改。
如何加文档?
加文档可以简单地创建一个README文件,该文件通常从较高的层次解释功能,但也可能包括安装步骤或强调如何通过API调用特定函数。另外,你也可以在代码本身中添加或更改注释,解释特定部分是如何工作的。
一个很好的建议是:
想象你下一年回到这段代码时已经忘记了所有内容,这时候你添加文档就相当于在帮助未来的自己。
04
弄清楚怎么跑通它
使用他人代码时的一个基本问题是首先得让它跑的通。这看起来很简单,但实际上却是非常困难的一步。
很多因素导致别人的代码不一定能跑通
不同的代码需要不同的操作系统、编程语言、数据库甚至是硬件,在一台计算机上跑通的代码在另一台计算机可能不行。如果环境不匹配,代码就无法正常运行。
对于旧代码来说,这尤其是个问题——《Nature》最近的一篇文章向研究人员提出了一个问题,即他们是否可以运行10年后的研究代码,发现了与过时的环境以及不完整的文档相关的问题。
通常,运行他人代码的主要问题是使用正确版本的编程语言和代码依赖关系。编程语言和单个软件包经常更新,有时这些更改是在考虑向后兼容性的情况下进行的,因此旧代码在新版本中仍然可以工作,但有时情况并非如此。
除了编程语言和依赖性之外,使用特定的操作系统、安装特定的数据库或图形卡、传感器等硬件,甚至需要软盘驱动器等。
发现跑不通的时候应该怎么办?
——看error提示
为了弄清楚如何运行代码,最好的第一步是简单地尝试在当前环境中按原样运行它。如果不行,计算机可能会发出一个error ,让你知道出了什么问题。
——查看文档
好的文档会清楚地说明预期的环境,包括所需的编程语言和软件包版本。不幸的是,并不是所有的文档都包括这些。
——使用互联网搜索
找出编写代码时软件的最新版本,并从那里往回推。
——创建虚拟环境来运行
当你确定代码需要运行的特定环境时,可以开始在计算机上复制该环境。如果不想更改计算机上的环境,我们可以创建一个虚拟环境来运行代码。虚拟环境允许使用特定版本的编程语言和程序包运行代码。
除此之外,虚拟机可以模拟整个计算机系统(例如,在Mac上运行虚拟Windows实例)。虚拟化有很多选项,最佳选项取决于当前的计算机和要虚拟化的系统。
——目前已有的配置虚拟环境工具
可能是其他人已经制定了运行代码的虚拟化设置。例如:
- Conda有用于虚拟环境的模板,用于编程语言和包版本,这些版本旨在运行特定代码。
- DockerHub包括用于设置整个虚拟机的Docker模板。
Conda和DockerHub都是社区驱动的平台,你也可以贡献自己的虚拟化设置来帮助他人(以及未来的自己)。
——利用调试工具深入挖掘代码
可能你遇到的问题不仅仅是寻找合适的环境。例如,可能不清楚需要什么样的输入。当代码的其他部分出现错误时,某些功能可能会正常工作。混淆这些问题,错误信息本身可能无法为问题提供很好的线索。在这些情况下,你可能需要更深入地挖掘代码的各个部分,并尝试弄清楚它是如何工作的。这可能很耗时,需要耐心和毅力。
调试工具可以在这里提供帮助,一步一步地跟踪代码的执行,并查看正在使用的资源和数据类型。当然,代码的某些部分是有可能被破坏的,可能需要进行更严格的测试或进行更改。
05
测试——它是否符合你的期望
为什么要做测试?
一个警示故事:
2006年,Geoffrey Chang因软件bug撤回了《科学》杂志上的几篇引人注目的论文。在这些论文中,Chang使用了一些代码来计算蛋白质的结构。
不幸的是,这个从另一个实验室继承的代码包含了一个小错误,这意味着由此产生的蛋白质结构是不正确的。如果进行更好的软件测试,这个问题可能会更早发现。
当使用他人的代码时,测试是一个好方法,可以检查代码做了什么(或没做什么),并验证它是否真的有效。
运行现有的测试
通常,一个项目已经有了测试,找到并运行这些测试应该是起点。这些通常可以在一个叫“tests”或类似的文件夹中找到,理想情况下,文档将包括运行这些测试的说明。
运行测试你会进一步熟悉代码库,也是一种衡量他人代码质量的方法。你甚至可能会发现测试失败,在这种情况下,可以考虑进行更改来修复代码。
编写自己的测试
除了现有的测试之外,你可能还想编写自己的测试。一个好的起点是健全性检查,这可以检验代码是否给出了期待的正确答案。
注:健全性检查是一种数据完整性检查方法,用于确保数据的准确性和一致性,是数据管理的重要组成部分。
健全性检查是值得的,不过自动化测试更彻底。这可以简单到通过编写测试来检查函数的输出是否正确,从而自动进行健全性检查。
——单元测试
通常,这是通过使用“assert”语句来实现的,如果输出不是预期的,则该语句会引发错误。这是一种单元测试的形式,它检查软件的特定单元,在这种情况下是一个功能,是否按预期工作。大多数编程语言都包括单元测试功能或库,其中包括断言语句以及快速轻松地运行许多测试的方法。
——功能测试
另一种常见的测试类型是功能测试,可以检查整个软件是否符合期望。
例如,你可能打算从论文中复制一个图形,这将作为对用于生成图形分析的软件的一种测试。功能测试可以是一种很好的方法,可以检查代码是否做了它应该做的事情,而不必太了解代码是如何工作的。
对于单元测试和功能测试,需要一些数据来检查结果
代码库可能已经包括了一些演示数据,这是一个很好的起点,也是代码所需数据格式的一个好例子。你可能需要添加自己的测试数据,这些数据可能是真实数据,例如在复制图形的情况下。
或者可以模拟数据,例如,可以模拟具有已知参数的数据,以检查拟合算法是否给出合理的结果。
对于真实或模拟的数据集,你可能不知道“正确”的答案,因此在可能的情况下,通常使用另一种现有工具来验证结果。
►►
总的来说,需要的测试的深度和类型将取决于你的具体目标。在许多情况下,一些简单的健全性检查就足以验证代码是否按预期工作。当对代码的质量有疑问时,以及当代码的正确性能至关重要时,需要进行更严格的测试。
06
拆解问题,画出来
一种很好的策略:
将问题分解为较小的单元或模块,然后将这些单元放在一起解决复杂的问题。
同样的事情也适用于使用其他人的代码。拆解代码并弄清楚代码的每一部分做什么,这通常比一次完成要容易得多。
这种方法的具体操作之一是:
绘制代码是如何工作的以及各部分是如何交互的。
这不需要完美,可以简单地用纸笔或黑板快速绘制草图。你可能想可视化代码的结构、数据的传递方式、哪些函数调用其他函数等。只要把一些东西写在纸上就可以帮助可视化系统。
doi.org/10.1371/journal.pcbi.1011031.g002
根据项目和目标,你可能会决定通过使用可视化软件设计图形来正式化这些图纸。
可以将其包含在项目的编写中,也可以将其添加到代码库的文档中。对于那些感兴趣的人,你甚至可以使用统一建模语言,这是一种可视化软件系统的通用方法。
07
寻求帮助
研究有时候也是需要合作努力。协作在处理代码时尤其有用,因为每个人在各种编程语言和范式中具有不同的知识和技能水平。利用现有知识是提高自己研究效率的一个很好的策略。
获得帮助的途径有很多:
➤ 谷歌(或其他搜索引擎)
可以依靠搜索引擎来帮助写代码,一项研究发现,开发人员花费了大约20%的时间搜索网络。当涉及到使用他人的代码时,包括项目名称在内的谷歌搜索可以显示在线资源,如代码库、教程、论坛等。谷歌是一个查找代码片段、简短解释和诊断错误的好资源。
-小tips:
一个好的搜索策略是:直接把错误消息复制粘贴到谷歌中。
➤ StackOverflow
StackOverflow是一个有用的资源,经常出现在谷歌搜索结果中。该在线门户允许用户询问和回答特定的编码问题。
一般来说,你的问题已经有人问过并回答过问题。如果你无法通过搜索找到解决方案,那么也可以自己问,基本上会很快得到答案。
也有可能你正在处理的代码库过于模糊,无法直接引用。就算这样,也可以发布代码块或提出更一般的问题并获得帮助。
其他有用的门户网站包括W3Schools和Quora,还有专门研究特定研究领域的论坛,如生物信息学的BioStars。
➤ github
许多项目都会在GitHub上托管代码,这是托管和共享代码库的行业和学术标准。
如果你对代码有问题,比如bug、建议的新功能,或者只是一个问题,那么你可以在GitHub Issues中提出这个问题,项目开发人员会回答或解决这个问题。这是最佳实践,也是许多开发人员提出问题的首选方式。
在提出问题之前,可以浏览你过去的问题,看看是否得到了解决。
➤ 其他项目门户网站
正在进行的软件项目通常有各种在线资源来获得帮助。这可能包括Slack、Gitter、Discord和论坛等平台。这些可以从GitHub页面链接,也可以通过谷歌搜索找到。
➤ 合作者
如果你有合作者,可以问他们。他们可能愿意提供帮助,甚至可能在过去使用过该代码。
➤ 其他研究人员
很有可能其他人以前也使用过该代码。你可以查看引用该软件或相关研究文章的论文。
如果代码托管在GitHub上,可以在存储库页面上检查问题,或者查看它是否已被分叉。或者在GitHub中搜索存储库名称并查找其他版本。
如果运气好的话,可能已经有人熟悉了这个代码库,他们可能会愿意为你提供资源,甚至成为你的合作伙伴。
➤ 原作者
值得与原作者联系,他们应该愿意回答特定的问题,甚至提供一些在网上找不到的文档,或者他们可能也想合作。你用他们的代码,他们也会高兴。如果你们合作起来,说不定他们会为你增加一些新功能或修复一些bug呢。
➤ 研究软件工程师
根据你所在的机构,你可能会获得研究软件工程师团队的帮助。这可以是一般性建议或支持的形式,也可以是他们愿意承担与代码库相关的项目并跟你合作。
08
在改代码之前先思考
当我们想要使用别人的代码时,总是想着要去写代码,改进代码。但是有没有想过:
我们真的需要去改变它吗?
其实,完成任务的最有效方法是:意识到你根本不需要去做它。当然,有时候改变是必要的,但在动手之前,先花点时间思考一下。
改变代码可能会带来很多意想不到的麻烦,特别是当你不太了解代码的运作方式或者理解不到位时,这种后果就更加可能发生。有些你看起来垃圾的代码可能是出于某种重要的原因而写成这样的。一旦你改变了代码,你就改变了代码的本质,不能确定它是按预期的,还是因为你的改变引起的。
关于修改代码,有本书叫《代码修改的艺术(Working Effectively with Legacy Code)》
改代码的目的不是要破坏原有功能,而是尽可能有效地完成改动。所以,我们需要明确改动的目的,只做必要的改动。
比如,你在修复bug时发现一个循环可以优化,但是要忍住不要掉入这种陷阱!这个循环的效率可能并不重要,每一次改动都可能引发严重问题。就像计算机科学家Donald Knuth说的,在大多数情况下,我们不应该过于关注代码的小细节,过早地优化是所有问题的根源。
改代码的最佳实践应该是什么样的?
在你做任何改动之前,最好先写一些单元测试,覆盖现有的功能,以及覆盖你想要添加的新功能。一旦做出改变,你可以运行这些测试来检查是否破坏了任何东西,更改是否生效。这个过程相对简单。
比如,如果你要重构一个特定的函数,你可以先写一些自动化测试,基于当前函数的输出(也就是特征测试)。改了代码后,你可以运行这些测试,确保重构没有改变功能。另外,记得把改过的地方记录在文档中,包括更改的内容,为什么要更改等。
09
使用版本控制
版本控制是备份、共享、协作和跟踪代码的最佳方式。
很多人会通过电子邮件、共享文件夹来传输代码,或者根本不备份,如果你不经常使用版本控制,那么这条很重要。
当我们谈到版本控制时,通常会想到Git和GitHub。GitHub提供免费账户,其中包括无限的代码存储库,可以是私有的,也可以是公共的。
还有其他选择,比如BitBucket。如果代码(或数据)是敏感的,不能发送到GitHub的服务器上,那么还有像GitLab这样的选择,可以让你运行自己的私人的Git服务器。
学习使用Git难不难?有哪些资源推荐?
学习使用Git实际上入门很简单。你不需要成为专家,只要能够推动和拉动更改就足够了。在这里,我们不会重复介绍如何使用Git的指南,已经有很多现成的资源了。
对于初学者建议阅读PLOS文章《使用Git和GitHub进行版本控制的快速介绍》。
对于更感兴趣的人,Pro Git书籍是一个有用的资源,还有许多其他的在线指南和教程。
如果你拿来的代码没有存储在版本控制的存储库中怎么办?
遇到这样的情况,你可以开始将现有代码移动到一个Git存储库中,并创建一个起始提交,将所有文件添加进去。你自己所做的任何更改都可以像往常一样进行跟踪,使用后续的提交。这样做可以确保始终可以将代码恢复到以前的状态,即使引入了错误,也不用担心继承的原始版本代码丢失。
Git还有什么其他功能?
对于高级用户,Git还提供了一系列有用的工具,例如GitHub Actions,可以自动化工作流程的某些部分。这可以包括自动化测试,检查代码更改是否破坏了任何东西,并且项目中的测试是否仍然能通过。
Git还允许轻松对代码进行版本控制,便于你可以将代码库回溯到特定版本或时间,这对于研究的可重复性非常重要。
10
在网上发布代码及相关更改
根据你的目标,你可能已经对代码和/或文档进行了更改。可以考虑分享这些更改。
为什么要在网上发布代码?有什么好处?
——为研究社区做出贡献
很多人可能和你一样,也有着相同的目标,你的更改可以为他们节省大量时间和困难。
——帮助你的职业发展
在线发布代码将建立你的在线个人资料和声誉,这在申请工作或其他机会时非常有帮助。
——优化代码、书写更加规范
可能有其他人帮你的代码库做出贡献和改进,或者只是发现你忽略的bug。
发布代码是一个好习惯。通常,如果我们知道代码将被发布,那就会有意识地写得更加规范,避免那些可能带来问题的懒惰捷径。
在研究项目的同时发布代码的趋势越来越明显,如果你已经养成了这个习惯,就会变得更加容易。
——增加合作机会
其他研究人员看到你的代码,可能会联系你进行合作。发布代码还将使其他人或者你自己在未来更容易地复制你的工作。
有研究人员讲述了这么一个故事:
在他们发布了几年后有人联系他们合作一个类似的项目,这是一个很好的机会,但他们差点拒绝了——因为他们找不到代码,之前写在一台旧笔记本电脑上的,现在已经无法访问了。幸运的是,他们意识到当时已经在GitHub上发布了代码,因此才有可能达成合作。
GitHub是共享代码的标准平台
在研究和工业领域,共享代码的标准是GitHub。如果代码已经作为现有的GitHub存储库发布,那么你可以“fork”创建一个包含更改的存储库副本。根据这些更改的内容,你甚至可以向现有存储库发起“pull request”,将你的更改合并到原始代码库中。
发布更改前需检查软件许可证
如果没有现有的存储库,那么你可以创建一个新的存储库。但是,在发布更改之前,需要注意一点:最好检查一下软件许可证是否允许这样做。
许可证通常在主项目目录中,命名为“LICENSE.txt”或类似的名称。虽然许多研究软件都是在宽松的开源许可证下发布的,但并不是每一个都这样,需要进行检查。即使是开源许可证也可能有一些条件,例如必须注明原作者。
结 语
以上总结了使用他人的代码的一些经验,供大家参考交流。学会用好他人的代码,对于自身代码水平的提升也有帮助。毕竟在接触不同的编码实践、设计模式、工具的过程中,你的编程知识和视野都在拓宽。
希望本文的一些驾驭源代码的技巧和心法,能帮你在科研的路上走得更稳,更加高效地得到想要的结果。当然你也可能有自己的心得体会,欢迎分享。
参考文献
Pilgrim C, Kent P, Hosseini K, Chalstrey E. Ten simple rules for working with other people's code. PLoS Comput Biol. 2023 Apr 20;19(4):e1011031.