许多科学家写的代码在风格上很糟糕,但在科学上却是正确的(严格检查/验证输出等)。专业的商业软件开发人员很有资格审查代码风格,但大多数人对检查科学有效性或什么是好的科学实践一无所知。后者中的一些人对帝国科维德-西姆模式的批评充其量也是夸大其词。
我越来越恐惧地关注着围绕冠状病毒爆发最可信的科学证据和建议提供者受到各种出于政治动机的政党的攻击-从英国著名的党派报纸及其盟友政客,到一夜之间如雨后春笋般涌现的好奇的“草根”组织,再到大量数字处理的Twitter账户。虽然肯定有理由在不久的将来某个时候对英国的SAGE(科学紧急情况咨询小组)系统进行强有力的审查,但显然,一些错误信息运动正在如火如荼地进行,试图破坏和诋毁这一独立的、符合公共利益的科学建议的重要来源,以推动特定的事业。不用说,这可能是非常危险的-诋毁我们最需要倾听的人的信誉可能会产生非常可怕的结果。
近几十年来,任何在气候变化或疫苗相关领域工作过的人都会熟悉被用来削弱圣人顾问的策略。我将在这里特别强调一个问题--利用其他领域的“专家”来质疑该领域实际专家本身的可靠性。特别是,这是一种试图解释这类文章有什么问题的尝试,这些文章被用作不诚实的政治文章的弹药,比如这一篇[付费墙]。这两篇文章都清楚地考虑到了特定的政治观点,但它们都有一定的可信度,因为这些批评被认为是来自一位似乎相关的专家。在这种情况下,(匿名)专家自称是一名拥有30年经验的专业软件开发人员,包括在一家声誉良好的软件公司工作。他们是有资质的,所以他们必须是可信的,对吗?有谁比软件开发人员更适合审查帝国医疗集团的流行病学代码呢?
我要说的大部分内容是对数学/统计/计算顾问约翰·D·库克(John D.Cook)的一篇旧文章的不那么简洁的重述。库克解释了科学家如何将他们的代码用作“外骨骼”-一种不断进化的工具,帮助自己(或许还有他们周围的一小群人)回答特定的问题-而不是作为一种工程“产品”,旨在为另一组可能永远看不到或修改过代码的单独用户解决预先指定的问题。虽然科学家和软件开发人员都可能以编写代码为生-甚至可能使用相同的编程语言和相似的开发工具-但这并不意味着他们试图用自己的代码实现类似的目标。与科学编码者相比,软件开发人员更关心可维护性和最终用户体验,而科学编码者可能更看重灵活性和可控性。重要的是,这意味着一种编程模式和规范对另一种可能不起作用-例如,保持代码简单、消除弊端、限制参数和设置的数量的忠告,实际上可能会干扰科学代码的预期应用。
“锁定怀疑论者”文章中的关键缺陷是他们应用软件工程启发式方法来评估代码的质量,而这些方法根本不适用于这里。作为一个在我的科学生涯中花了很大一部分时间编写和使用不同类型的建模代码的人,让我首先尝试阐述高质量科学代码的理想属性。对于大多数建模应用程序,这将是:
科学正确性:正在研究的模型在数学和逻辑上的正确表示,以及对任何输入数据的正确处理和解释。这与“正确拟合观察”是不同的--虽然找到最适合某些数据的模型可能是代码的目标之一,但探索反事实的能力也很重要。代码实现的模型可能与数据格格不入,但在正确实现反事实模型的意义上仍然可以是“高质量”的。
灵活性:能够添加、调整、打开/关闭不同的效果、尝试不同的假设等。这些代码通常是探索性的,随着时间的推移将用于研究许多不同的问题,包括许多只有在初始开发很久之后才会出现的问题。大量的参数和丰富的if语句是常态。
性能:足以让科学家回答问题的速度和/或精度
请注意,我考虑的是通常寻求对某些类别的现象进行建模的专家小组使用的代码类型。还有其他类型的科学代码旨在更普遍地使用,其目标更接近软件工程师通常试图实现的目标。然而,帝国法典并不属于这第二类。
对于专业软件开发人员来说,这份清单中缺少哪些东西是最优先考虑的呢?以下是几个例子:
可维护性:大多数科学代码的开发都没有考虑到未来的维护人员。根据约翰·库克的说法,它们更有可能是由特定科学家或为特定科学家开发的“外骨骼”,随着时间的推移,随着新问题的出现,它们会有机地增长。可维护性是一个很好的东西,特别是如果其他人会使用代码的话,但它对代码的科学质量几乎没有影响。一些有科学价值的代码修改起来真的很烦人!
文档:提供代码和最终用户文档是一种很好的实践,但对于科学代码来说这不是必需的。例如,不同的领域对于代码是在发布时开源还是仅在请求时提供有不同的规范,而且许多科学家在包含注释或编写易于阅读的代码方面做得很糟糕。这是因为代码本身很少是最终产品-它通常只是一种运行某种特定数学模型的方法,然后在期刊文章中呈现(并对其进行辩护和剖析)。重要的是方法、假设以及结果的一致性和准确性-只要代码在这一意义上是科学正确的,它本身就可能是一堆难看的烂摊子。
用户校对/错误检查:对于软件开发人员来说,设计良好的代码不应该要求终端用户了解任何内部实现细节。代码应该检查用户输入的有效性,并在可能的最大范围内防止用户做错误、无效或会产生无意义结果的事情。在科学代码中具有一定程度的错误检查也是不错的,但在许多情况下,代码是“按原样”呈现的-用户应该通过理解代码的内部结构和背后的科学原理来确定什么是正确和有效的输入。事实上,代码甚至可能故意产生在某些方面“错误”而在其他方面“正确”的输出-例如,曲线的幅度是错误的,但其形状是正确的。本质上,假设用户理解代码及其输出的所有(已知/预期)限制。如果您自己运行代码并且是特定领域的专家,通常会出现这种情况。
正式测试:软件开发人员知道测试套件的价值:编写所有内容的单元测试;在代码中抛出大量无效输入以检查它是否失败;使用持续集成或类似的方法来例行测试回归。这是一种很好的做法,经常可以捕捉到错误。然而,建立这样的基础设施仍然不是科学代码开发的规范。那么,科学家是如何处理回归等问题的呢?答案是,大多数人使用特殊方法来检查问题。当新的结果第一次从代码中出来时,我们往往会研究到死。这有意义吗?如果我更改此设置,它是否会按预期响应?它能重现理想化的/先前的结果吗?它是否同意这种替代但等效的方法?再说一次,这是科学过程的关键部分。当然,我们还输出代码的有意义的中间结果。请记住,我们通常处理的是与现实世界中的某些东西相对应的数量-例如,您可以确保在计算过程中不会传播负的死亡人数。虽然这些检查也可以由单元测试来处理,但大多数科学家最终通常只能得到他们自己的一组奇怪的特别测试输出和打印语句。它很难看,也不是万无一失的,但考虑到我们密集的结果测试行为和社区交叉检查的性质,它往往工作得很好。
后四点会让大多数软件开发人员感到恐惧(我知道很多人--我在自由/开源软件运动中活跃了整整十年;买我的书,等等)。如果您正在为最终用户开发软件,跳过这些事情是很糟糕的做法。但对于科学软件来说,这就不那么重要了。如果您的用户不是您自己,他们会在一段时间后弄清楚(这是研究生入门时最喜欢的项目!)。或者给你发电子邮件询问。如果您输入了无效的输入,您的测试和对结果的其他类型的科学检查通常会发现错误。而且,说真的,谁会在乎你的代码是不是又丑又乱呢?只要你做了正确的事情,在发表之前对科学结果进行了适当的检查,即使你是用bl写的,也没有关系。
我要警告这一节的事实是,我是一名天体物理学家,而不是流行病学家,所以我不能批评模型假设,甚至不能真正批评它在帝国法典中得到很好实施的程度。不过,我可以解释一下,我认为封锁怀疑论者的文章忽略了这类代码的要点。
非确定性输出:这是最重要的一个,因为在特定情况下,它可能是一个有效的批评。这段代码实现的模型是一个随机模型,因此期望产生具有一定随机性的输出(它正在探索某种概率分布的特定实现;多次运行它将允许我们重建该分布,这是一种称为蒙特卡洛(Monte Carlo)的方法)。然而,计算机处理的是“伪随机性”;给定相同的起始“种子”,它们将产生看起来相同的随机序列。爱丁堡的一个竞争小组的一项审查发现,同一种子存在一个错误,导致不同的结果,这通常不是你希望发生的事情。正如您在该链接上看到的那样,帝国代码的一名开发人员承认了该错误,并对其影响进行了一些解释。
这里的关键问题是,这个漏洞是否会在已发表的论文或建议中造成实质性的错误结果。根据开发商的反应,我预计不会。他们清楚地知道以前发生过类似类型的行为,这意味着他们运行代码的方式可能会发现这种行为(即,他们正在运行一些可重复性测试-标准的科学实践)。这个漏洞并不是未知的。这里的一种特殊解决方法似乎是使用不同的种子多次重新运行模型,这就是您无论如何都会对此代码执行的操作;或者使用不同的设置,这些设置似乎不会受到此bug的影响。我的猜测是,此bug导致的“伪随机性”根本无关紧要,或者它不会以正常的代码运行方式出现。他们并不担心--不是因为这是一场他们试图掩盖的灾难,而是因为这只是一个例行公事的漏洞,并不会真正影响任何重要的事情。
再说一次,这是科学规划的主要工作。他们以前见过这个问题,因此意识到代码的这个限制。在理想情况下,他们应该已经修复了bug,是的,但是对于这类代码,我们通常不会试图达到近乎完美的状态,为点发布或类似的东西做好准备,就像商业软件一样。取而代之的是,代码正以一种不断发展的状态使用。因此,也许意识到这一点,考虑到他们使用代码的方式,修复它并不是一个非常高的优先级。实际上,他们为什么要以这样一种方式运行代码,从而导致错误出现并故意使其结果无效呢?很明显,这并不是一个主要的结果--仅凭他们的行为(以及来自爱丁堡的记者的行为)就可以使bug无效。
未记录的方程式:有关文档的方法,请参阅上面的内容。用文档记录公式肯定要方便得多,但这是否意味着代码不好呢?不是的。据我们所知,有一本破旧的乳胶笔记解释了这些方程,或者它们出现在早期的一篇论文中(两者都很常见)。这完全是正常的-丑陋的,对试图理解代码的非专家没有帮助,但不是代码质量差的指示器。
持续开发:如上所述,这类科学代码通常会根据需要进行发展,而不是针对特定的发布日期或特性集。持续开发是正常的,像错误修复这样的东西会在突然出现时应用。影响以前发表的结果的严重问题通常会导致错误(例如,参见我的这一篇);一些科学家不太擅长发布勘误表(或更正后续论文),特别是对于更小的问题,尽管对大多数人来说,掩盖真正严重的问题将是职业生涯的终结。正如我希望从上面说清楚的那样,这篇文章对严重“质量问题”的指控实际上并没有得到证实,它们只是(无害的!)。从一个完全不同的领域违反了他们习惯的规范。
“最初的程序是‘一个已经工作了十年的15,000行文件’(这被认为是极其糟糕的实践)”--对科学家来说不是!如果说有什么不同的话,那就是这个团队十年来一直在努力研究这个代码,合作者越来越多,面对越来越多的同行评审员,经受住了越来越多来自其他团队的比较,这让我对它更有信心。它肯定会提高随着时间的推移发现并解决大量错误或注意到结构缺陷的机会。年轻的、未经验证的密码才是危险的!虽然使用大型单文件结构肯定会很烦人(因此在这个意义上也很差),但它并没有
“它所做的事情最好用‘没有图形的模拟城市’来形容。”--太棒了!最初的SimCity使用了一个非常复杂的模型来完成它的工作,甚至被用来教授城市规划师(我记得在90年代读过手册)。
“如果把帝国团队中的人放在一家经营良好的软件公司…的背景下,他们很快就会做得更好。ICL和软件业的不同之处在于后者有检测和防止错误的过程“-现在真的,这是在教奶奶吸蛋。请记住,许多现代编程都是从学术科学中脱颖而出的。这并不是说普通的科学程序员不能忍受学习一些更干净的编码实践,而是指责科学家没有检测和防止错误的过程-荒谬!科学过程的基础是结果的验证和自我纠正,我们的武器库里有很多高效的工具来处理这一点,非常感谢。现在我发现单元测试、持续集成等在我的一些基础设施项目中很有用,但它们是便利而不是必需品。实际上,我认识的每个科学家都把大部分时间花在检查他们的结果上,而不是编写代码,我真诚地怀疑帝国理工学院有什么不同。如果说有什么不同的话,那就是在大多数领域都有一种“明显正确”的文化--发现别人工作中的错误是一项非常有价值的活动(特别是如果他们是直接竞争对手的话)。
“把自己的产出作为投入消耗的模型是私营部门熟知的问题--这可能导致迅速的分歧和错误的预测”--这是一种高度简单化的看待事物的方式,我怀疑作者对这种方法了解不多。我不知道帝国理工学院的人在这里具体在做什么,但有一些重要的方法使用了这种反馈循环,称为迭代方法,在求解复杂的耦合方程系统等方面很常见,在数学上非常严格。我有时会在统计建模的上下文中使用它们。弗格森和他的同事都有很高的数学背景,所以我认为可以有把握地认为他们没有遗漏这类明显的问题。
我可以继续下去,但希望这足以证明我的情况那篇文章的作者超出了他们的深度,而且显然不知道许多b。
..