ドキュメントは不可欠です。特定の関数/クラス/…が何をするかを知らなければ、すべてを使用することは非常に困難です。 適切にコーディングしてください。
ツールはドキュメントを提供するのに役立ちます。ツールはソース コードから情報を抽出し、それを手動で記述された情報と組み合わせて、人間が判読できる出力形式でドキュメントを生成できます。
ただし、問題があります。C++ ドキュメント用の現在のツールはそれほど優れていません。この投稿では、その理由を説明し、(進行中の) 解決策を提供します。
何が問題なの?
Doxygen は、C++ ドキュメントのデファクト スタンダードです。ドキュメンテーション付きのほぼすべてのオープン ソース ライブラリ (私のものも含む) は、何らかの形で Doxygen を使用しています。 HTML などのさまざまな形式でドキュメントを提供します。
その出力は大幅にカスタマイズでき、Python ツール Sphinx を使用するために Breathe などの他のレンダラーと組み合わせたさまざまなプロジェクトがあります。また、BoostBook や QuickBook と組み合わせることも可能です。
しかし、問題があります。Doxygen は完全ではありません。
使ったことのある人なら誰でも私に同意するでしょう.時々問題や荒削りがあります.それはでしょう
そして、これは Doxygens のせいだけではありません。はい、古いです。ライセンスの日付は 1997 年にさかのぼります - C++ 標準化の前です!そのため、いくつかの点で、最近の C++ の変更に適切に適応していません.
しかし:ツールを使って C++ を文書化するのは簡単ではありません。
C++ の文書化は難しい
C++ は非常に複雑な言語であり、多数の機能を組み合わせて使用する方法はたくさんあります。ドキュメントを生成する際には、考慮すべきことがたくさんあります:
- <リ>
SFINAE を許可するためだけに存在するパラメーターです。関数シグネチャを表示するときは非表示にする必要があります。それらは正確にきれいではなく、初心者を混乱させる可能性があります。代わりに、要件をドキュメントに記載する必要があります。
<リ>実装定義型。一部の戻り値の型または型定義された型は、標準で定義されているように「実装定義」されています。これらの詳細タイプは、基盤となる OS へのプロキシまたはハンドルです。署名にも表示されるべきではありません。
<リ>同じことを達成する方法はたくさんあります。これらの詳細は、ドキュメントで抽象化する必要があります。例:関数オブジェクト vs フリー関数または定数 vs 列挙。
<リ>関連する注意事項:C++ には、新しい型を生成する「強力な typedef」を取得する方法がありません。そのため、typedef が新しい型である場合もあれば、別の型のエイリアスである場合もあります。ドキュメントはそれを反映する必要があります。
<リ>
一部のクラス (特に一般的なコード) には、空のベースの最適化を提供するためだけに存在するベースがあります。これらは基本クラス リストから非表示にする必要があります。他の基本クラスは、インターフェイスを取得するためだけに存在します。有名な例は std::tuple
です 、継承を使用して引数の再帰を実装する可能性があります。これらの基本クラスも表示されるべきではなく、代わりにクラスにインライン化する必要があります.
ドキュメント生成は通常 private
を無視します メンバーはインターフェイスの一部ではないためです。しかし、NVI パターンでは、すべての virtual
が提案されています。 関数は private
でなければなりません 単純に無視するだけではうまくいきません。同様に、C++11 より前のコードでは、それらを削除する関数を宣言し、定義しません。また、無視するのではなく、ドキュメントで言及する必要があります。
概念 TS がマージされる前に、テンプレート パラメーターが満たす必要がある概念を文書化する方法が必要です。文書化ツールはそれを説明する必要があります.
<リ>
ほとんど同じ const
のような定型コードがたくさんあります const
以外 すべての比較演算子のゲッターまたはオーバーロード。文書化する必要があるのは 1 回だけです。
これまでのところ、言語の使用をツール、つまり Doxygen に適応させることで、これらの問題を回避してきました。たとえば、マクロを実装定義としてマークしたり、基本クラスを EBO または SFINAE のパラメーターとしてマークしたり、単にインターフェイスを適応させたりしています。
しかし、これは間違っています :使用方法をツールに合わせる必要はありません。ツールは使用方法に合わせて調整する必要があります。ツールは生活を楽にするものであって、難しくするものではありません。私は違います 一人で - 私の C++ の使用をマクロクラスターファックやハックなしで処理できる Doxygen が欲しい.
何も見つからなかったので、自分で書き始めました。
私の目標
私の目的は明確です。C++ を処理できるツールが必要です。しかし、Doxygen は私がネイティブで必要とする正確な種類のドキュメントを提供しないため、Doxygen を修正することはできませんでした。
C++ 標準がライブラリをドキュメント化する方法が本当に気に入っています。
たとえば、これは std::swap
のドキュメントです :
template<class T> void swap(T& a, T& b) noexcept(see below );
1 備考: noexcept 内の式は、is_nothrow_move_constructible<T>::value && is_nothrow_move_assignable<T>::value
と同等です。
2 必須: タイプ T は MoveConstructible (表 20) および MoveAssignable (表 22) でなければなりません。
3 効果: 2 つの場所に保存されている値を交換します。
「効果」を通して機能が何をするかを間接的に説明する方法があります。私はそれが本当に好きです。素晴らしく構造化されています。
Doxygen は、より直接的な方法を奨励しています。 Doxygen を使用すると、次の結果が得られます:
Doxygen が関数を誤って noexcept
でマークしているという事実は取り上げません。 フラグ、これはバグと見なされる可能性があります。
私は、Doxygen が要件を指定するためのネイティブな方法を提供していないという事実 (私の知る限り) を選んでいるので、\tparam
を使用しました。 テンプレート パラメータ内の要件を文書化します。Doxygen は全体として、各エンティティを文書化するこの直接的なスタイルを推奨しています。これにより、文書全体に情報が拡散します。これは小さな問題であり、\param
を使用しないことで簡単に回避できます。 この場合.しかし、私が見る限り、これは Doxygens の設計が推奨するスタイルです.
どちらのドキュメントもまったく同じ情報を提供します.しかし、私はむしろ最初の種類のドキュメントを読みます.
これは私の個人的な意見です。あなたの意見は異なる場合があります。
しかし、私の目的は、C++ 標準が行うのと同様の方法でドキュメントを生成することでもあります。実際、これが私が独自のツールを開始する主な動機でした。そのため、その名前は Standardese です。
それで…何をするの?
それで、約 2 週間前に標準語の作業を開始しました。
libclang の助けを借りて、ドキュメント化する可能性のある* C++ エンティティの解析を実装しました。
Libclang はそこで大いに役立ち、C++ パーサー全体をゼロから作成しなくても、使用可能なものを取得することができました。まだいくつかの機能が不足しているため、explicit
のような情報を取得するためにパーサーを作成する必要がありました。 または noexcept
しかし、それがなければ、私は今この時点にいないでしょう.
ドキュメントの生成に必要な情報を含む独自の C++ AST が生成されます。その上に、現在 \effects
のようなセクション マーカーのみを検索する非常に原始的なコメント パーサーがあります。 または \throws
.カスタマイズ可能な出力システムにより、Markdown でのシリアル化が可能になります。
これまでに実装された生成は、ファイルごとの生成のみです。特定のファイル内の各 C++ エンティティに再帰的にアクセスし、それをシリアル化して概要を生成し、ドキュメントをフォーマットします。後のバージョンには章も含まれますが、現在のプロトタイプは含まれません。
これはすべてライブラリに実装されています。私は、Doxygen が無数の出力形式に使用され、C++ ドキュメント解析のフロントエンドにすぎないことを見てきました。標準ライブラリの目標は、カスタマイズ可能な、より優れた、より一般的なフロントエンドになることです。
ライブラリを駆動する標準ツールもあります。ファイル名と構成を読み取り、指定された (または指定されたフォルダー内の) 各ファイルのドキュメントを生成し、ライブラリを使用してドキュメントを生成します。
それで…どのように見えますか?
これは swap()
を文書化する方法です 標準語で:
/// \effects Exchanges values stored in two locations.
/// \requires Type `T` shall be `MoveConstructible` and `MoveAssignable`.
template <class T>
void swap(T &a, T &b) noexcept(is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value);
そして、現在のプロトタイプは次のドキュメントを生成します:
ヘッダー ファイル swap.cpp
#include <type_traits>
namespace std
{
template <typename T>
void swap(T & a, T & b) noexcept(is_nothrow_move_constructible<T>::value &&is_nothrow_move_assignable<T>::value);
}
関数テンプレート swap<T>
template <typename T>
void swap(T & a, T & b) noexcept(is_nothrow_move_constructible<T>::value &&is_nothrow_move_assignable<T>::value);
効果: 2 つの場所に保存されている値を交換します。
必須: T
と入力します MoveConstructible
とする と MoveAssignable
.
より複雑な例については、こちらを参照してください:https://gist.github.com/foonathan/14e163b76804b6775d780eabcbaa6a51
これは素晴らしい音/見た目です!行って使ってもいいですか?
可能ですが、実際のドキュメント生成に使用することはお勧めしません…まだ.前述のとおり、これは初期のプロトタイプにすぎません.
ライブラリにはドキュメントがなく (皮肉なことですが)、そのインターフェイスは非常に不安定です。解析用の単体テストはありますが、どこにでもバグがある可能性があります。前述の C++ の問題を処理しない単なるダム ジェネレータであり、次のような重要な機能が欠けています。エンティティ間のリンクとして。
基本的に、上記のすべてのハード C++ をサポートしていませんが、将来のバージョンでサポートする予定です。
私はまだそれを公開することを決めています.私はそれについてあなたのフィードバックが欲しいです.私と何か考えを共有してください.私は将来私のライブラリのドキュメントに標準語を使用する予定です.私の目標はあなたもそうする.あなたが持ちたいものについて私に話してください。
興味があれば、定期的に最新情報を投稿し、デザイン決定の背後にある動機を投稿します。現在、他に何もすることがなく、それを推し進めます。夏には 1.0 が完成します。
独自のコードベースでパーサーを実行して、バグに遭遇したかどうかを確認することも役に立ちます。
ここでコードを入手して、それを壊して楽しんでください。 :)
言葉を共有して広めてください!