我一生都从事研究工作,所以我知道研究人员编写丑陋代码的刻板印象(例如,请参阅此处、此处或此处)。但我想:我们可以解决这个问题,对吗?所以我多次尝试设计好的研究框架。我尝试使用我喜欢阅读的软件工程书籍和博客引入界面并创建漂亮的抽象。
但一次又一次,所有这些努力都徒劳无功。我开发的大多数研究软件从未投入生产(尽管有些已经投入生产)。如果有人告诉我一个简单的事实,那就对我的心理健康大有裨益:研究代码的消亡实际上是应该发生的事情。研究人员一开始就不应该花太多时间来设计它。
专业的软件工程师总是瞧不起那些没有使用最佳软件实践的研究人员。有几篇文章试图提高研究代码的标准(例如这篇很棒的文章和研究代码手册)。但这篇文章却反其道而行之:它讨论了如何不要过度采用最佳软件实践,而只投资于快速探索。它针对的是研究型公司,您的目标是快速尝试许多想法。
公司成功的研究项目有两个阶段:探索和利用。在“探索”中,你想要尝试尽可能多的不同解决方案。在“开发”过程中,您必须强化最佳解决方案并将其变成有用的产品。
两者之间的最佳软件实践有很大不同。这就是为什么公司通常有独立的研究和产品部门。您通常可能阅读的所有有关软件设计的书籍主要是关于第二个“开发”阶段的。在此阶段,您正在为可扩展产品奠定基础。这就是所有设计模式的用武之地:良好的 API、日志记录、错误处理等等。
但在第一个“探索”阶段,你并没有建立永远存在的基础。事实上,如果你的大部分努力都幸存下来,那么你(根据定义)探索得还不够。
这篇文章中的许多做法都是通常成为“技术债务”的例子。这就是不编写干净的、可重用的、抽象的代码所得到的结果。债务总是坏事吗?我们宁愿永远不要获得贷款或抵押贷款,但借钱通常是生活中的一个好策略。为了快速行动并稍后获利而负债是可以的。
同样,如果不承担技术债务,您可能会减慢您的研究速度。好消息是,大多数时候您无需偿还。无论如何,你的大部分研究代码都可能会消失。因此,平均而言,您不会承受所承担的全部技术债务。
许多软件架构和重构技术都是专门针对提高代码可重用性的。代码重用有一些普遍的缺点。但在生产中,众所周知的好处超过了它们(例如,请参阅此典型帖子)。在研究项目中,大部分代码注定会被遗忘。努力实现代码重用实际上可能会减慢你的速度。
限制代码重用是可以在研究中承担的技术债务类型。我想讨论几种代码重用模式:添加不需要的依赖项、复制粘贴代码、维护大量共享研究代码、过早的设计投资。
如果您知道一个维护良好的版本库可以加快您的速度 - 那就去吧!但在接受新的依赖之前,请尝试判断它是否值得。每增加一个都会让你更接近依赖地狱。它使您投入时间来学习和排除故障。在这篇简明的文章中查看更多依赖关系的陷阱。
如果满足以下条件,那么依赖某些东西可能没问题:
但如果出现以下情况,请警惕依赖性:
你无法快速弄清楚如何使用它,它非常新(或非常旧)或者似乎没有人知道它;没有文档或测试
它来自您的 monorepo,并且不断被其他团队更改
它引入了许多其他依赖项和工具;或者只是很难安装
最后,你觉得你(或一些法学硕士)可以在几个小时内编写出这段代码。
您可以遵循一句很好的 Go 谚语来代替显式依赖:“ 一点复制胜过一点依赖”,这是我们的下一个主题。
有人说“复制粘贴应该是非法的”。但令我惊讶的是,我发现自己经常赞成它。复制粘贴可能是探索阶段的最佳选择。
如果您依赖于代码库其他部分中频繁使用的函数,您可能会忘记轻松更改它。您可能会破坏某人的某些东西,并且必须花费宝贵的时间进行代码审查和修复。但是,如果您将必要的代码复制粘贴到您的文件夹中,您就可以自由地用它做任何您想做的事情。这对于研究项目来说是一件大事,因为实验是一种常态而不是例外。特别是如果您不确定更改是否对每个人都有用。
我发现深度学习代码库最适合复制粘贴。通常,描述模型及其训练所需的代码量并不是很大。但与此同时,它可能非常微妙且难以概括。可共享的训练脚本往往会增长到难以管理的大小:例如,Hugging Face transformers
Trainer有+4k 行。有趣的是,变形金刚选择在模型级别进行复制粘贴。请查看他们的帖子及其“单一文件模型”政策背后的推理。请参阅最后有关复制粘贴之美的更多资源。
复制粘贴的另一种选择是留在分支上。但我觉得这给团队合作带来了太多的开销。另外,我还发现了更多关于复制粘贴之美的帖子 - 请参阅结论中的更多帖子。
维护频繁使用的共享代码需要大量工作。查看针对Pytorch
版本绘制的torch.nn.Module
文件行数。您可以看到,即使是最先进的研究团队也很难控制复杂性。
不要低估维护大型共享研究代码所需的时间和资源。研究图书馆使用得越多,它就变得越复杂。它比典型的图书馆发生得更快,因为每个研究方向都有稍微不同的用例。制定非常严格的回馈规则。否则,共享代码会变得脆弱,并且会因大量选项、错误优化和边缘情况而变得过于庞大。由于大多数研究代码都消失了,所有这些额外的复杂性将永远不会再被使用。删除一些共享代码将腾出一些时间来进行实际研究。
确实,即使在生产环境中,您也不想对代码进行过多的面向未来的验证。尝试实施满足要求的最简单的解决方案。但在生产代码中,始终需要考虑可维护性方面。例如,错误处理、速度、日志记录、模块化是您通常需要考虑的。
在研究代码中,这些都不重要。你只想以最快的方式快速证明一个想法是好还是坏,然后继续前进。因此,在没有任何模块或 API 的情况下实现它的简单性是完全可以的!
不要将宝贵的时间浪费在过早的软件投资上,例如:
研究项目的目标是找到新颖的解决方案。没有人知道(根据定义)它是什么样子。它类似于信息有限的复杂研究环境中的优化过程。为了找到一个好的最小值,您需要尝试许多路径,识别好路径和坏路径,并且不要陷入局部最小值。为了快速完成这一切,您有时需要进行软件投资,而不是承担技术债务。
您想要尝试几种不同的研究路径。是否有设计、库或优化可以缩短大多数路径的时间?您应该小心,不要过度设计任何东西,因为您并不总是知道要尝试的所有想法。这对于每个项目来说都是非常定制的,但这里有一些例子:
研究人员应该能够快速提出新的、多样化的想法。项目开始时似乎很容易。但随着人们固守自己最喜欢的研究道路,它逐渐变得越来越难。为了解决这个问题,文化和组织变革至关重要。在投入太多金钱和情感之前,应该有一个过程来阻止没有希望的研究。定期演示日和技术同行评审可以作为实现此目的的有效策略。在人们欣然接受新的想法和正确结束当前项目之间找到平衡也很重要。
但这是一篇软件文章,因此这里有一些可以轻松扩展新项目的做法:
嘈杂且有缺陷的代码使结果变得如此模糊和不确定,以至于整个项目将是浪费时间。虽然您不应该过度设计事物,但您可以轻松遵循这些简单的经验法则来避免混乱的代码:
避免有副作用的代码
默认为函数而不是类;对于类,更喜欢封装而不是继承
最小化函数/类/模块的长度;最小化 if 语句的数量
熟悉Python,但使用简单的技术。抵制元类、装饰器和函数式编程等智力杂草的诱惑。
在不同的运行过程中产生不同结果的软件很难使用。如果你基于一颗不幸的种子做出了一个重要但错误的决定,你将浪费大量时间来恢复。以下是处理非确定性软件时的一些技巧:
笑点来自这篇关于研究代码的文章:
“你不必为[良好的软件设计]而烦恼,因为代码不是重点。代码是一个工具,可以为您提供所需的答案” 。
拥有良好的编码基础非常重要。但归根结底,探索和实际有用的产品才是最重要的。如果您在研究中使用过多的生产软件,您就会浪费发现新东西所需的时间。相反,找出是什么减慢了你的探索过程。通过投资快速分支、获得结果的时间和干净的无噪音代码来加快研究路径。
完全反对代码重用是疯狂的。我只是想指出,代码重用应该是一项平衡良好的活动。在研究中,废弃代码的比例比在生产中更大。天平进一步向不利于重用的方向倾斜。以下是一些关于代码重用陷阱的精彩帖子:
这里还有一些争论复制粘贴做法的帖子:
感谢您的阅读!我觉得有些地方有点争议,请在评论中告诉我!
也出现在这里。