コメントが少ない他人のコードを読むのは大変
昨日、ファイル・関数ヘッダない、コメントが少ないのコードを「解読」していることをTweetしたので、自分はどうしているかを書いておく。
今日明日は久しぶりにリーマンサット の作業で、RSP-01 ソフトのドキュメント作成です。
— SONODA Takehiko (@OzoraKobo) January 10, 2021
メンバーが作ったコードを見ていますが、ファイル・関数ヘッダがないし、コメントも少ないので解読しなきゃならない(作り自体はちゃんとしてるからなんとかなりそう)
かつての仕事(受託開発)では、お客様のコーディング規約で決まっていることがあり、やらざるを得なかった。そして、自社開発やその他でも、ヘッダとコメントは書くように自分に課していた、今もそうしている。(テストプログラムのように自分しか見ないコードには書かないけどね。)
ヘッダ・コメントを書く利点
- いつ頃、誰が書いたかわかる
- 関数ヘッダはAPIリファレンスとして使える
- 時間が経つと自分が書いたコードでも忘れるが、コメントがあれば思い出しやすくなる
- 他人が読む時の助けになる
- 処理のポイントをコメントにすることによって、整理される
- 変数の意味・目的がつかみやすくなる
例
自作のmbedのシリアル通信クラス(C++)のコードで一例を示す。
ファイルヘッダ
/******************************************************************************
* @file SerialCom.cpp
* @brief SerialCom シリアル通信 クラス
* @auther SONODA Takehiko
* @date 2018/09/29 新規作成
* @par
* Copyright (c) 2018 SONODA Takehiko All rights reserved.
******************************************************************************/
- Doxygen対応
- これは初版だが、変更があれば日付と変更内容も記述する
関数ヘッダ
/******************************************************************************
* @fn SendBinData
* @brief シリアル通信 データ送信(バイナリ)
* @param pBuffer : 送信データバッファへのポインタ
* @param iSize : 送信データサイズ
* @return 受信終了動作結果
* - SERIAL_COM_SUCCESS : 正常
* - SERIAL_COM_PARAM : パラメータエラー
* @sa
* @detail 送信データバッファのデータを送信データサイズ分送信する
* バイナリデータの送信可能
******************************************************************************/
- 引数があれば、引数名と意味、必要に応じて値の範囲を記述
- 戻り値は、少なくとも内容を下記、必要に応じて値の範囲や意味を記述
- 戻り値がなければ、void
- @detailに簡単な概要を記述することもある
- @dateに変更履歴を記述することもある
変数のコメント
int _baud; /* ボーレート */
bool _bEchoback; /* エコーバック有無 */
uint8_t *_pRecvBuffer; /* 受信バッファへのポインタ */
int _iRecvLength; /* 受信バッファ長 */
int _iRecvDataLength; /* 受信データ長 */
bool _bRecvCompleted; /* 受信完了フラグ */
uint8_t _ucTerminationCode; /* 終端コード */
- 変数の定義には必ずその変数を表す意味を記述する
- 関数の内部変数では、値に意味のある変数は記述するが、単純なテンポラリ変数には記述しないこともある
// ワーク用変数
_pRecvBuffer = pRecvBuffer; /* 受信バッファへのポインタ */
_iRecvLength = iRecvLength; /* 受信バッファ長 */
_iRecvDataLength = 0; /* 受信データ長 */
_bRecvCompleted = false; /* 受信完了フラグ */
- 値を設定、初期化するときも記述する
処理ポイントのコメント
/******************************************************************************
* @fn RecvFunc
* @brief シリアル通信 受信割り込みハンドラ
* @param void
* @return void
* @sa
* @detail 受信割り込みコールバック関数から呼ぶこと
* 受信データを受信データバッファに格納する
* 受信データが終端コードなら受信完了にし、受信データが取得可能になる
* 受信データバッファが一杯になったときも受信完了にする
******************************************************************************/
void SerialCom::RecvFunc()
{
uint8_t ch; // 受信文字
if (readable()) {
// 読み込み可能な文字あり
// 受信文字取得
ch = (uint8_t)_getc();
if (_bEchoback) {
// エコーバック有効
// 受信文字を出力する
_putc((int)ch);
}
if (!_bRecvCompleted) {
// 受信完了状態なら読み飛ばす
if (ch == _ucTerminationCode) {
// 受信文字が終端コードなら受信完了
_bRecvCompleted = true;
_pRecvBuffer[_iRecvDataLength] = '\0';
}
else {
// 受信文字≠終端コード
// 受信バッファに受信文字を追加
_pRecvBuffer[_iRecvDataLength++] = ch;
if (_iRecvLength - 1 <= _iRecvDataLength) {
// 受信バッファサイズ - 1 ≦ 受信データ長
// 受信完了にする
_bRecvCompleted = true;
_pRecvBuffer[_iRecvDataLength] = '\0';
}
}
}
}
}
- 条件文のブロックの先頭に、そのブロックに入る条件を記述
- 処理のまとまり毎に処理の内容を1行で記述
- 処理内容は、必要ならより詳しく書くこともある
※ご指摘によりバッファオーバーフローを引き起こすコードを修正しました。
ここに挙げたところだけでも押さえていただけると、読み手としてはかなり助かる。
仕事のコードではもっとちゃんと書いているのだが、公開できないので、その点はご了承いただきたい。