在 Fortran 中保护父范围变量的良好做法是什么?

计算科学 正则
2021-11-29 19:04:01

所以我刚刚拿起了一个用fortran90编写的项目。我习惯用 python 和 C 编码。

真正困扰我的是fortran90 中子程序的使用。在 fortran 中,人们使用子例程在子函数中设置父作用域变量。这对我来说真的很不透明,因为如果代码变得更复杂,跟踪这些变量的变化会变得非常困难。我的意思是,比较这些片段:

subroutine big()
    implicit none
    integer :: a

    a=1
    call f()
    print *,a

    contains

      subroutine f()
      implicit none
        a = 2
      end subroutine f

end subroutine big

program hello
    call big()
end program hello

2

同样,在 python 中,我可以避免覆盖 a:

def big():
  a=1
  def f():
    a=2

  f()
  print(a)

big()

1

在 python 中,我不能简单地在子函数中设置 a (除非我使用了一个global不好的做法),在 fortran 中我可以并且人们正在这样做。

  • 在保护父范围变量的情况下编写现代 fortran 代码的正确方法/良好编码实践是什么?
1个回答

有几种工具可以使代码库更加结构化。在您的示例中, big() 中的子例程在没有参数的情况下被调用,但正如您所说,在外部范围内发生了一些变化。为了防止这样的事情,你当然可以显式地声明函数的参数:

subroutine big()
  implicit none
  integer :: a
  a=1
  call f(a)
 print *,a

 contains
   subroutine f( argument )
     implicit none
     type(Complex), intent(inout) :: argument
     argument = 2
   end subroutine f
end subroutine big

这向读者表明,某事将发生在一个!也有明确的意图声明可以指定实际是哪种 arg 参数。(它只是一个不应更改的输入吗?!)

一般来说,你越多地暴露代码应该做什么的意图,你就越容易让读者使用它。

还有模块系统,您可以在其中将不同的子例程/函数分类到一个模块中,从而将您的代码构造成不同的段。这些段是什么在很大程度上取决于您的情况,但是使用模块可能会大大清楚子例程应该做什么,甚至无需查看它。

假设您将所有线性代数的东西分类到一个名为 linAlg 的模块中。然后你可以使用:

仅使用 linAlg:vectorAddition,scalarMultiply

在其他地方,读者将有足够的提示来了解正在发生的事情。

因此,我保持 fortran 代码库的怪物的两分钱是:

  1. 封装成相对较小的模块以保持命名空间较小,并使用每个模块(linear_algebra_mod、finiteElement_mod、solvers_mod、vectorTypes_mod、matrixTypes_mod、inputOutput_mod 等)自我记录您的意图。一旦你这样做了,你就可以明确地说明你想从中使用什么,例如:“use a_mod, only : foo, bar”
  2. 明确说明子程序和函数的输入/输出,并使用意图语句
  3. 使用简洁但有意义的子程序/函数名称,这些名称是不言自明的(如果在一个子程序中发生了很多事情,则将其拆分为更小的部分)
  4. 使用富有表现力的变量名。不要走在命名变量的道路上:i、ii、ij、ji、ijk、ikk、ikkijasdf :-)
  5. 在重要的子程序中使用合理数量的输入检查。如果您的方法获得大量 NaN 作为输入,或者您尝试计算 -1.0 的平方根,那么您最好在 3 小时的调试会话之后立即通知您!
  6. 非常重要:避免过早优化仅仅因为你部门的书呆子知道所有的技巧来解决另外 10% 的性能并不意味着你的代码库的可读性是无关紧要的。仅在实际遇到问题时才关注性能。

根据您的项目有多大,您还可以将其移植到更新版本的 Fortran (2008),您可以在其中封装到类中!Fortran 中的 OOP