第 10 章 科学计算的最佳实践

本章将介绍适合科学计算应用程序、API和工具的开发者使用的最佳实践。最佳实践是人们通过长期的研究与实践总结的经验。根据这些实践方法进行开发,可以达到事半功倍的效果。

本章将涉及的最佳实践主题如下:

  • 方案设计阶段的最佳实践

  • 功能实现阶段的最佳实践

  • 数据管理与应用部署的最佳实践

  • 实现程序高性能的最佳实践

  • 数据隐私与网络安全的最佳实践

  • 程序维护与客户支持的最佳实践

  • Python程序开发专用的最佳实践

通常,科学家用科学计算工具做研究,他们绝大多数没有受过正式的计算机科学培训。这就可能导致他们开发出低效率的产品,开发周期也可能更长。而且可能实现的算法效率不高,开发时间很长,代码也达不到预期的效果。最佳实践可以帮助他们解决这些问题。科学家遵循最佳实践,就可以用正确的科学方法进行软件开发,并让他们的代码没有无用的异常和错误。

许多科学程序库/应用程序/工具箱都是完全由非计算机专业的科学家开发的。这些最佳实践帮助他们取得了更好的效果,提升了开发效率,改善了开发体验。

最佳实践可以被视为用于执行软件开发任务的可重复的标准方法。

10.1 方案设计阶段的最佳实践

软件开发过程中的设计阶段的最佳实践如下。

  • 任务分配到不同的团队:把开发生命周期的不同阶段的任务分配给不同的团队,效果更好。这样既可以减轻一个人的负担,也可以在更短的时间内实现更好的效果。最好为开发的每个阶段选择一个团队(一到两人一队),分别负责设计、实施和测试阶段。这些团队的内部成员紧密合作,他们在不同的阶段完成各自负责的任务。这样做比不同团队之间的合作更高效。这种合作方式如下图所示,不同的步骤都用普通的图形表示。设计师团队可以在编程上支持开发团队,这种合作方式被称为结对编程。类似地,功能实现团队与测试团队密切合作,可以修复bug,改善系统的整体性能。

{%}

  • 把大任务分割成小任务,分而治之:不要指望一次性写一个大程序实现一个大功能,应该把大任务分解成若干子任务。这是一种循序渐进的方法,通过不断地完成一个个子任务来实现最终任务的完成。这样将会提升整体开发体验,并改善实现代码的质量。按照这样的方法,代码质量更高,时间消耗更少,可维护性更强。

  • 每个子任务的生命周期:每个子任务都按照开发生命周期(设计、实现和测试)来执行,可以减少程序的代码错误,因为每个子任务都经过了测试。这还可以改善代码的整体质量,因为每个团队只需要负责一小部分代码。具体方法如下页图所示。

采用这种方法,开发团体可以避免陷入影响整体重大的错误之中。而且这种方法可以为最终的测试阶段节省很多时间。

  • 每个阶段使用专业的软件:推荐团队在开发生命周期的各个活动中使用专业的软件。大多数活动都有许多标准软件。例如,有一些专门用于设计与建模工具的软件(如微软的Visio)。为了支持开发活动,有集成开发环境(如用Eclipse开发Java)、版本控制软件(如Git和CVS)、程序调试器(如GDB)、编译工具(如常用于Java开发的ANT)。还有许多应用测试和性能分析的专业软件。

{%}

每个子任务的生命周期

10.2 功能实现阶段的最佳实践

下面介绍的最佳实践适合应用于功能实现阶段。

  • 代码注释与文档最大化:大多数科学应用都涉及复杂的算法和计算,因此它们的实现过程也很复杂。如果对那些复杂的功能实现的代码进行详细的说明,就可以更方便未来的功能增强。将代码注释与文档最大化,可以让用户/开发者很好地理解程序背后的设计思路。尤其重要的是,对于复杂逻辑的合理注释,可以方便开发团队继续增强应用程序/工具/API的功能。代码注释可以解释代码的逻辑。

  • 提升代码可重用性:不要重复发明轮子,在开发生命周期开始之前,先看看有没有合适的程序库。站在现有程序库的基础上开发会节省很多时间。而且,使用现有的优质程序库还可以减少运行错误、bug,因为这些库都经过反复测试,久经考验。使用现有的程序库可以让科学家将精力集中在科学研究之中。这样可以节省大量时间。唯一要花时间的地方是学习这些程序库,然后用它们完成任务。

  • 首先开发一个功能完整的原型:一种开发应用程序、工具、API的好方法,是首先开发一个可以正常运行的原型,然后再不断地优化。即使是开发一些简单的商业软件,这种方法也值得尝试。正常运行的原型可以通过优化改善性能。然而,开发过程中制定优化计划可能会分散团队注意力。因此,在开发阶段,团队的重心应该放在实现需要的功能上,之后可以优化正常运行的应用程序、工具、API以改善性能(过早优化是魔鬼)。

  • 预防未来可能发生的错误:采取积极主动的方法应对未来的错误。这种方法会涉及断言、异常处理、自动化测试与调试。断言可以用来判断代码的前置条件和后继条件都是正常的。自动化测试可以帮助开发人员保证程序的功能没有改变,即使程序已经被调整过了。在测试阶段,每个错误都应该被转换成一个测试用例,这样未来再遇到时就可以自动化测试。使用调试器比直接在代码里用print打印结果来验证的代码正确性更高效。调试器可以帮助开发者深入理解程序每一行语句所做的动作。异常处理可以帮助开发者提前处理异常。下面的代码演示了Python断言的用法:

  1. # 判断前置条件的测试
  2. def centigradeToFahrenheit (centigrade):
  3. assert type(centigrade) is IntType, "Not an integer"
  4. assert (centigrade >= 0), "Less then absolute Zero"
  5. return (9 * centigrade/5 + 32)
  6. print centigradeToFahrenheit(40)
  7. print centigradeToFahrenheit(15)
  8. print centigradeToFahrenheit(-10)
  9. # 判断前置条件与后继条件的测试
  10. def calculate_percentage (marks1, marks2, marks3):
  11. assert (marks1 >= 0), "Less then absolute Zero"
  12. assert (marks2 >= 0), "Less then absolute Zero"
  13. assert (marks3 >= 0), "Less then absolute Zero"
  14. result = (marks1 + marks2 + marks3) / 100.0
  15. assert (0.0 <= result <= 100), "Percentage should be between 0 and 100"
  16. return result
  • 数据与代码的开源与标准出版物:开发的代码与实验用的数据最好都以开源/标准的形式发布,这样数据与代码就可以被该研究领域的科学家们使用。这样做可以增加应用程序、工具和API的关注度,最终也会形成更大的用户基础。

代码与数据的发布会吸引大量的用户,这些用户会参与程序测试与未来的功能改善。数据也会被改善,并根据新用户的需求不断更新。通常,开源软件经过全球各地的开发者和科学家的协作,都会不断地更新。为了支持大量分散的开发者贡献项目,分布式版本控制工具1应运而生。分布式版本控制工具是基于网络开发的系统,具有强大的扩展性,可以支持大量的开发者。传统的版本控制工具不能支持众多的开发者共同维护一个项目。

{%}

分布式版本控制工具的原理

1如GitHub和Bitbucket。——译者注

10.3 数据管理与应用部署的最佳实践

这部分内容是数据管理与应用部署阶段的最佳实践。

  • 数据备份:这条实践适用于关键任务的应用程序,数据一旦丢失将是严重损失,可能造成很高的实验成本,甚至实验失败会造成生命的代价。对于这类关键任务的应用程序,应该适当考虑数据备份,这样可以保证当系统部分功能出现故障时,不会影响到系统整体功能。备份数据必须放到不同的位置,这样即使某地区发生自然灾害,也不会影响最终功能。

数据备份的概念如下图所示。每份数据都会在全球的不同位置复制三份。即使一两块数据出现了故障,也不会影响系统整体功能。

  • 用真实和模拟数据做测试:应用程序可以用真实数据测试,也可以用模拟数据测试。如果拿不到真实数据,就用模拟数据。要获取模拟数据,可使用第3章中介绍的基于统计分布的随机数生成技术。通常,绝大多数常用的科学应用程序都有公开的数据集,在第3章里已经介绍过。如果有合适的数据集,就可以拿来实验,对应用程序进行测试。

{%}

10.4 实现高性能的最佳实践

这部分内容主要面向需要高性能的应用程序。实现高性能的最佳实践如下所示。

  • 为将来的扩展性需求做准备:主动地为将来的扩展性需求做好准备,是更加合理的决策。系统运行的数据集可能是小数据集、大数据集、千万亿级甚至百亿亿级;在设计阶段必须考虑这个问题。根据这个需求,硬件配置、软件开发框架和数据库都需要进行合理配置。设计过程中需要考虑到系统将来需要处理大数据的可能性。

  • 选择软件与硬件:花足够的时间为应用程序、工具和API选择最合适的技术。这个过程需要一开始花时间挑选能够实现目标功能的合适的开发环境。这些技术包括选择一种适宜的编程语言和开发框架、合适的数据库/数据存储、需要的硬件、合适的部署环境,等等。

  • 选择合适的API:如果有API可以实现想要的功能,选择最合适的API对成功、高效地实现功能至关重要。在确定要使用的API之前,需要适当地分析API是否可以实现目标功能与性能要求。最终产品的性能直接受到建立系统时使用的API性能的影响。

  • 使用适当的性能测试工具:对于性能关键型应用,需要使用合适的性能测试工具。有许多性能测试工具可以测试不同类型的应用程序、工具和API的性能。例如,DEISA性能测试套件是一个专门设计的高性能科学计算应用程序。通常,性能测试工具都是由应用程序所在领域的一些用户自定义和实际的程序案例构成。这些程序会运行许多次,以测试目标应用程序的性能。

10.5 数据隐私与安全的最佳实践

数据隐私与安全是应用程序能够被用户接受并广泛使用的最重要前提。本节介绍关于应用程序和数据的合理隐私与安全的最佳实践。

  • 数据隐私:有一些应用程序涉及数据采集,对于这类应用程序,开发者需要注意保护用户数据隐私。数据隐私非常重要,这些数据可能是财务和医药数据,一旦泄露就可能让数据采集者倾家荡产。因此,在开发生命周期的各个阶段都需要时刻关注。

  • 网络应用/服务的安全注意事项:如果应用程序被设计成网络应用/服务,网络信息安全是必须考虑的一环,因为网络服务系统是网络攻击的主要对象。有一些成熟的安全策略既适用于网络服务系统的安全防护,也可以用于对系统进行攻击。从应用程序开发生命周期的第一步开始,就应该考虑安全防护措施。适当的证书许可与鉴权机制可以同时实现应用程序的隐私与安全防护。

10.6 测试与维护的最佳实践

适当的测试与维护对软件开发是至关重要的。本节重点介绍测试与开发阶段的最佳实践。

  • 单元测试优先:最好优先进行单元测试。单元测试成功后,再进行集成测试。待集成测试通过后,再进行验证测试。单元测试可以保证系统的不同模块能正常工作,并且有助于尽早发现错误。这样不仅可以修复模块中的bug,还可以帮助开发者找到最初想法实现中缺失的部分。由于单元测试一次只对一个模块进行测试,关注点很小,因此可以找到功能实现阶段落下的部分。

  • 不同的测试团队:测试是产品最终获得成功的关键环节。测试阶段中最好是不同的团队负责不同的功能。这些团队共同合作获取更好的结果。这样做可以识别功能实现阶段的bug和问题,让最终产品获得更好的功能。

  • 成立客户支持工作组:为了向大型系统的用户提供支持和维护,最好为系统的不同功能模块创建多个工作组。最好各个功能模块的一个开发者可以成为工作组的一员。这样,开发者(也是组员)就很容易发现问题并及时修复。每个工作组专门负责一部分。这样,工作组的每个成员都会对那部分功能有透彻的理解。这些组员也可以轻松地管理支持与维护的工作。

  • 为大型项目成立多个工作组:对于大型项目,需要创建多个工作组共同分担工作任务。每个工作组负责一块任务,为客户提供支持、维护和改善服务。专攻一个领域的工作组可以不断改善系统的整体质量,为客户提供更好的支持。由于一个团队在较短的时间内遇到了同一领域的许多问题,因此他们处理问题的经验更丰富,并最终为系统提供客户真正需要的更新。

  • 建立用户帮助与支持邮件列表:为每个工作组创建一个用户邮件/反馈列表。用户可以向邮件列表提出问题,工作组成员也可以通过邮件列表答复用户问题。邮件列表可以作为用户与开发者的沟通桥梁。

10.7 Python常用的最佳实践

这一节将介绍一些Python开发者常用的最佳实践。

  • Python的PEP 0008代码规范:这部分最佳实践的第一条是深入理解并遵照PEP 0008编码规范。详情请参考https://www.python.org/dev/peps/pep-0008/

  • 命名习惯:推荐所有的Python开发者都保持前后一致且有意义的命名习惯。这条建议不仅对应用程序的原始开发者有帮助,同样也适用于未来扩展程序功能的开发者。统一并有意义的名称可以改善代码的可读性。命名习惯应该遵循统一的命名规则,并为具体的语言调整相应的命名规则。例如,用下划线与驼峰式大小写方式连接多个单词,构建一个变量名和函数名。下表列出了推荐使用的名称和不推荐使用的名称。

不值得推荐的代码风格值得推荐的代码风格 变量

var1var2mycalculation

temp_valf1num35变量

areaincomeTaxproductCost

Counterlambdasigma

sum_of_product 函数

func1()function2()

calculation_func()perform_func()函数

calculateArea()product_of_sum()

sinx()

  • 统一的代码风格:一般情况下,推荐你在整个系统中使用一个标准、统一的代码风格。代码断言、缩进、注释和其他内容的风格都应该一致。为代码注释采用或开发一个标准风格,并在整个系统的编码过程中严格执行。类似地,整个系统代码的格式也应该一致。还需要考虑代码中空格与缩进的使用。

下面的例子演示了两种不同的代码格式。

空格与缩进格式不统一空格与缩进格式统一

  1. x=(b\*d-4\*a\*c)/2\*a
    y = 2 \* x \* x + 4 \* x + 5
    def sample_function()
    print "in function"
    print " last line"
    def second_sample()
    print "in function"
    print "last line"
  1. x = ( b \* d 4 \* a \* c ) / 2 \* a
    y = 2 \* x \* x + 4 \* x + 5
    def sample_function()
    print "in function"
    print " last line"
    def second_sample()
    print "in function"
    print "last line"

10.8 小结

本章介绍了科学计算团队需要使用的最佳实践。首先介绍了方案设计的最佳实践,然后介绍了写代码的最佳实践,之后介绍了数据管理与应用部署的最佳实践。

紧接着,介绍了高性能计算的最佳实践以及数据隐私与网络安全的最佳实践,之后介绍了程序维护与客户支持的最佳实践,最后介绍了Python开发者常用的最佳实践。