概要
FORD (FORtran Documenter)などを作成している,Fortran F/OSS Programmers Groupのリポジトリに,Fortran FOSSプログラマ向けのスタイルガイドラインがあったので,概要をまとめましました.
併せて著者の考えを注釈として記載しました.
言語標準への準拠
言語標準に準拠することは最重要だが,実用性の観点から下記のように妥協する.
- 言語標準には可能な限り準拠せよ
- ただし,実用性の観点から拡張が必要な場合は除く
- その場合でも,複数のコンパイラによって広くサポートされている拡張を優先する
標準に準拠できない理由の例
並列計算を行う場合
HPC分野で並列計算用のハードウェアを利用するには,ライブラリなどを用いる必要がある.特化した特定の手段よりも,OpenMPやOpenACC等が好まれる1.
サードパーティーライブラリを利用する場合
OSレベルの操作や実行時エラーの処理などは,非標準ライブラリを用いた混合言語プログラミングによって解決されることが多い.このような場合は,iso_c_bindig
を利用して,可能な限り標準に準拠して開発することを検討せよ2.
プリプロセッサディレクティブ
Fortranには標準にプリプロセッサディレクティブがないので,プリプロセッサディレクティブを使用すると,厳密には言語標準に準拠しなくなる3.
プリプロセッサディレクティブは一例であるが,コードを非標準にする可能性のある他の機能にも気をつけること.
命名規則
自身の意見を盛り込んだ命名規則を採用し,それを一貫して使用する
小文字を利用する
Fortranは大文字小文字を区別しない言語であるため,構文チェックで大文字と小文字は区別できない.しかし,キャメルケースは可読性向上の一助となる可能性がある.
Fortranのキーワードとユーザが定義した名前を区別するために,Fortranキーワードを全て大文字で書く規約が採用されていたが,現在広く利用されているエディタの多くは,構文強調機能を備えているので,Fortranのキーワードを他の名前と区別できる.
- 例外としてグローバルに置かれたパラメータがある
小文字のみを用いる場合は,スネークケースを利用する
その場合,アンダースコアの使い方を統一せよ.
-
temp_wall
やtemp_gas
という変数名を定義したなら,tempfloor
やfloor_temp
ではなくtemp_floor
とせよ.
説明的で,かつ意味を持った変数名を付ける
現在のFortran(90以降)は,63文字までの長い名前が許可されている.意味のある名前を付ける検討し,名前の短縮に力を入れすぎないようにせよ.
同じ概念の処理を異なるオブジェクトに対して実行する手続きを開発する場合,手続きに意味のある説明的な名前を与えることで可読性を改善できる.
subroutine compute_transpose_of_sparse_approximate_matrix(...)
subroutine compute_transpose_of_dense_approximate_matrix(...)
subroutine compute_transpose_of_sparse_exact_matrix(...)
変数名の短縮に労力を使わない
空気の音速の定義を例にすると,いくつかの分野で使われているa
4は,この暗黙の慣習に詳しくない人にとっては不明瞭である.
最悪なのは,簡潔にするためにsos
という名前を採用することである.この変数は,音速の値なのか遭難信号なのか区別できない.speed_of_sound
のような名前が明快で長さも適切である.
もし,複数の物質における音速が必要なら,speed_of_sound(AIR), speed_of_sound(WATER)
のように,定数配列を設けて物質名をインデックスとして利用することを検討せよ5.
簡潔な方が可読性向上に繋がる場合は,名前の長さを制限してもよい
音速の変数が,気体を表す構成要素である場合を仮定する6.
type :: gas
real :: gamma = 0.0
real :: pressure = 0.0
real :: speed_of_sound = 0.0
end type gas
associate
構文を利用すると,変数名は簡単に短縮できる.
function density(low_pressure_gas)
type(gas), intent(in) :: low_pressure_gas
real :: density
associate(gamma => low_pressure_gas%gamma, &
pressure => low_pressure_gas%pressure, &
speed_of_sound => low_pressure_gas%speed_of_sound)
density = gamma * pressure / (speed_of_sound**2)
end associate
return
end function density
一文字の変数名は,配列インデックスやループカウンタとして利用する場合のみ許可
1文字変数l
は,数字(リテラル)の1
と区別が難しいので避けるべきであるが,エディタの構文強調機能があれば区別できる.O
と0
についても同様.
マジックナンバーを利用せず,パラメータを利用する7
論理変数には,それが伝えるべき状態を反映した名前を付ける
-
lib_init
よりもlib_is_initialized
-
obj_parent
よりもobj_has_parent
エンティティ8を明確化する
モジュール名,派生型名,手続き名,変数名を明確化するための規則を採用する.
下記の例のように,モジュール名と派生型名(同一スコープ内の異なるエンティティ)に同じ名前を付けたい場合がある.しかし,これは許可されていないので,モジュール名と派生型両方に意味のある名前を選択する必要がある.
module shape_sphere
type, public :: shape_sphere ! 許可されない
end type shape_sphere
end module shape_sphere
変数名を付ける場合も,同様の曖昧さが発生する事がある.
type(shape_sphere) :: shape_sphere ! 許可されない
接頭辞,接尾辞を利用する
モジュールに接尾辞_m
,派生型に接尾辞_t
を利用する.
module shape_sphere_m
type, public :: shape_sphere_t
end type shape_sphere_t
end module shape_sphere_m
ライブラリやパッケージを構成する全てのモジュールに,共通の接頭辞(例えばpkg_
)を付ける.
module pkg_shape_sphere
type, public :: shape_sphere
end type shape_sphere
end module pkg_shape_sphere
異なるスタイルを利用する
キャメルケース9とスネークケースなど異なるスタイルを用いる.ただし,この方法では一つの単語からなるモジュール名(Car
)と派生型名(car
)は区別できない.
module shape_sphere ! モジュール名はスネークケース
type, public :: ShapeSphere ! 派生型はキャメルケース
end type ShapeSphere
end module shape_sphere
サブモジュールを利用する
派生型の型束縛手続きのAPI定義とその実装を分けるためにsubmodule
を利用すると,結果としてモジュール名と派生型名が区別できる.
module speaker_interface
type :: speaker
contains
procedure, nopass :: speak
end type speaker
interface
module subroutine speak
end subroutine speak
end interface
end module
submodule (speaker_interface) speaker_implementation
contains
subroutine speak
print "(A)", "Hello, world!"
end subroutine speak
end submodule speaker_implementation
ここで,_interface
はAPI定義の識別に使う接尾辞であり,接尾辞_implementation
を付けたサブモジュールでの実装と区別される.派生型名は接頭辞や接尾辞がなくても十分に識別できる.
手続きを明確化する
型束縛手続きに派生型名を入れる
type :: object
contains
procedure :: stuff_1 => perform_stuff_1_on_object
procedure :: stuff_2 => perform_stuff_2_on_object
...
end type object
キャメルケース9とスネークケースを利用して手続きを識別する
type :: gas
real :: speed_of_sound ! 成分はスネークケース
contains
procedure :: ProjectOnCharacteristics ! 型束縛手続きはキャメルケース
end type gas
手続き名称には動作に関する動詞を含める
subroutine
には動詞形を利用し,function
には名詞形を利用するなど,subroutine
とfunction
の識別に動詞の有無が利用できる.
! 動詞形
subroutine get_useful_thing
end subroutine get_useful_thing
! 名詞形
function useful_thing
end function useful_thing
ソースファイル名は,その中で定義されている主なエンティティの名前を反映する
常にimplicit none
文を利用する
暗黙の型宣言は,コードの開発を加速できるが,コードの理解やデバッグを困難にする.
program
やmodule
,submodule
の最初にはimplicit none
を書く.外部手続き(program
,module
,submodule
内で定義されていない手続き)は,最初にimplicit none
を書く.
多くのコンパイラは,暗黙の型宣言を無効化する専用のオプションを提供しているので,その利用も検討する.
複雑よりも単純な方がよい
単純さは,可読性と保守性が高いコードを作成する鍵である.
オブジェクト指向プログラミングは,より一般的で複雑なタスクに対して保守性と再利用性を向上できる10だろうが,単純なタスクに対しては複雑なだけである.
可能な限りgoto
文の利用は避ける
ほとんどの場合,goto
文は,構造的に安全な構文によって置き換えることができる.可読性と保守性を大幅に改善しながら性能劣化もない.しかし,非常に特殊で有用な場合は例外を認めるべきである.
goto
はそれ自体が悪ではなく,カオス的なコードを開発するようプログラマを駆り立てる可能性があることが問題である.
-
Fortran標準として,
do concurrent
やco-array
が追加されている.do concurrent
でreduction
がしたいとの要望も出されているが,OpenMPの機能をFortran標準で置き換えるのは,まだまだ先になるだろう.
特化した特定の手段とは,恐らくOpenACCに関係しており,CUDA FortranよりもOpenACCを使えとの事だと考えられる.一方で,nvfortranでは,do concurrent
を自動でGPUにオフロードする機能が追加されており,言語標準に沿った上で並列計算用ハードウェアを利用する手段が整備されつつある. ↩ -
これは,例えば
!DEC$ attributes
のようなコンパイラ依存のディレクティブではなく,Fortran 2003のbind(name=c)
を使えということであろう. ↩ -
Fortran向けのプリプロセッサとしては,近年ではfyppが注目されている.原文ではCOCOというプリプロセッサが紹介されていた. ↩
-
いくつかの分野では
c
が使われることもある. ↩ -
AIR
やWATER
という名前から,それらがインデックスであることを想像するのは困難である.列挙型を利用することを想定していると思われるが,その場合はMaterialIndex_AIR, MaterialIndex_WATER
など接頭辞を用いた方がよりよいと考えている.あるいは,独自にmaterial_index
型を定義し,その成分としてair, water
を設けた上で,material_index
型定数を用いた方がよいだろう. ↩ -
著者は,
gamma
ではなくratio_of_heat_capacities
あるいはheat_capacity_ratio
を使うべきであると考えている.associate
構文で一時的かつ局所的に名前を短縮するのであれば,プログラム全体に矛盾がない限り,gamma
,p
,a
を使ってもよいと考えている.関数gamma
を利用している場合は,命名規則の検討が必要である. ↩ -
例えば定数の
1
を直接書かない代わりに,定数integer, parameter :: one = 1
を利用しても,何も解決していない.その1
が何を指しているのかを説明する変数名にせよ. ↩ -
現実世界におけるモノや概念のうち,計算機で取り扱うためにモデル化され,かつモデルが持つ情報によって一意に特定できる対象を意味する.ここでは,モジュール名,派生型名,手続き名,変数名を区別しろと言っている. ↩
-
オブジェクト指向プログラミングを利用すれば自動的に保守性と再利用性が向上するのではなく,そのように設計しなければならないという制約である. ↩