2018/03/07

関数プロトタイプ宣言はヘッダファイルとソースファイルのどちらに書くべきか

C言語のコードでは、全ての関数プロトタイプ宣言がヘッダファイルに書いてあったりするものもありますが、
個人的にはヘッダファイルに書くべきものと、ソースファイルに書くべきもしくは、書く必要さえないものを明確に区別すべきだと思います。

何をもって区別するのかと言うと、「スコープ」です。
「何を当たり前のことを」と思った方は申し訳ありません。ただ、最近「スコープなんて知らん」と言わんばかりのコードを大量に読むことになってしまいまして。



### スコープとは
> ある変数名や関数名といった名前を参照できる範囲のこと
>
> [スコープ - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97) より

言語によって指定できるスコープには違いがありますが、C言語の関数の場合は `public` か `private` を区別すべきかと思います変数はもう少し複雑。
C言語に `public` や `private` という修飾子はないですが、`static` や `extern` だと誤解を与える可能性があるので、ここではあえて `public`、`private` を使います。

`public` は他のモジュール(ファイル)から参照可能な関数。
`private` はそのモジュール(ファイル)内でのみ参照可能な関数です。

例えば、特定のハードウェアを監視しているようなモジュールの場合、外から値をチェックするための関数は `public` ですが、
特定の状態変化を検知して記録するような関数は `private` になる可能性が高いですわかりにくい例しか思いつかなかった。


### スコープは狭く
一般的にスコープは狭くしておいた方が安全です。
ハードの状態変化を外から設定できてしまうと、間違ってその関数を別のモジュールから呼んでしまった場合、重大なバグの原因になりえます。

そんなの間違えて呼ばないよ。と思うかもしれません。
自分だけで書いている場合はそうかもしれないですが実際は自分で書いたものでも間違えることはよくありますが…、
チームで開発していると誰がどんな勘違いをするかわかりません。


### public 関数はヘッダファイルに、private 関数はソースファイルに書く
ヘッダファイルに書かれてる物は、複数のファイルから参照されることを前提にしていますローカルヘッダのような、特殊な場合もありますが、一般論として。
つまり、「ヘッダファイルに書いてある」=`public` と言えます。

先の例だと、以下のように状態を見る関数はヘッダファイルに、状態を記録する関数はソースファイルに書きます。

なお、ソースファイルに書く場合、先行して定義出来る場合はプロトタイプを書かなくても良いでしょう。個人的には書かない方が修正時等に余計な手間が減ってよいと思います。

```c
`title: "hoge.h";
#ifndef HOGE_H__
#define HOGE_H__

extern int hoge_get_state();

#endif // HOGE_H__
```

```c
`title: "hoge.c";
#include "hoge.h"

// このプロトタイプ宣言は書かなくても良い場合もある
static void set_state(int state);

// 関数の実体等々
```

あえて、`extern` と `static` を書きました。
上記の定義に従うと、`public` な関数は `extern` で `private` な関数は `static` になります。

実は、`extern` なプロトタイプ宣言をソースファイルの中に書いても良いわけですが、それだと各ソースファイルに同じプロトタイプ宣言が散らばることになります。
こういった宣言は一元管理が原則極端な話、ヘッダの内容を全部ソースファイルにコピペすればヘッダファイルはいらないわけですが…なので、ヘッダに書いておくべきです。


### C++ の場合は public class と private class を区別
C++ でもベターC として使用する場合は原則同じです。

C++ の class を積極的に活用する場合、
class にはアクセス指定子(`public` `private` `protected`)があるので、そちらで関数のスコープ制御を行います。

この時、class 定義をヘッダファイルに書くか、ソースファイルに書くかで、class のスコープを制御するのが良いと思いますC++にはローカルクラスもあるので、さらに細かく制御出来る。


### 変数はどうする?
一般的に「グローバル変数は使わないほうが良い」というのは広く認識されているのだと思います。

「グローバル変数」とは、どこからも参照可能になっている変数、つまり `public` な変数です。
さらに言えば、`extern` 宣言されている変数です。
基本的には「グローバル変数」は使用せず、アクセサを用意する方がより安全だと思います。

とはいえ、「単純に読み書き用のアクセサを用意するのはどうよ?」という考え方も一理あります。
C++ の話ですが、興味深いのでリンクをはっておきます。

> 参考
> 
> [C++ - アクセサにするべきかpublicにするべきか?(21279)|teratail](https://teratail.com/questions/21279)


### でも、ローカルルールには従いましょう
プロジェクトによっては、
他のファイルから呼んではいけないローカルヘッダを作る等特殊なプレフィクスがついてたりする、独自のルールでスコープを実現している場合もあります。
そういった場合は、ローカルルールに従いましょう。
全体でポリシーが統一されていない方が、後々問題になることが多いように思います。

大事なのは、スコープをわかりやすく提示することです。それにより、誤使用によるバグが減りますし、モジュールの使い方が明確になりやすいです。

もちろん、`public` にしていても使用に注意が必要な関数があったり、理想通りにはいかないことも多いです。
ただ、そういったことを意識していれば、わかりやすいコメントを入れたり、より具体的な対処が出来るのではないかと思います。


### 参考書籍
この手のことがどこかに書いてあったはずと思って手持ちの本を探しまわったら、これに書いてありました。

> 外部に公開する関数、変数のみをヘッダで宣言し、外部に公開する必要のない、その他 の作業用の変数、関数については static 指定子を付けるというのが、C でよく見られるモジュール化の手法で す。 > > p.72 より この本では、さらにオブジェクト指向っぽい実装を紹介していて実用的です。 私も 2つ以上のアドレスを持つ I2C モジュールを作成する時等に、この本で紹介されている方法を用いています。 これにも書いてあったかなと思ったのですが、関数のスコープに関してはあまり書かれていませんでした。
しかし、以下の項等は今回の話に通じるところがあります。その他の項目も一読の価値ありです。 - 9.2 変数のスコープを縮めるこの項目で書かれている 「static メソッド」は Java 等のメンバ変数を汚染しないメソッドのことを指しているので注意 - 10.6 既存のインターフェースを簡潔にする

0 件のコメント: