まず第一に、この質問では、ソースコードのコメントが良いか悪いかという論争から離れたいと思います。なぜ、何を、どのように伝えるコメントについて話すとき、人々が何を意味するのかをより明確に理解しようとしています。
「コメントはなぜあなたに伝えるべきか;コード自体はどのように伝えるべきか」のようなガイドラインをよく目にします。抽象レベルでの声明に同意するのは簡単です。しかし、人々は通常これをドグマのように落とし、それ以上の説明なしに部屋を出ます。私はこれが非常に多くの異なる場所やコンテキストで使用されるのを見てきました。それは人々がキャッチフレーズに同意できるように見えますが、彼らは完全に異なることについて話しているようです。
では、質問に戻りましょう。コメントで理由を説明する必要がある場合、これがなぜ私たちが話しているのでしょうか。これがそもそもそのコードが存在する理由ですか?これはそのピースコードがやるべきことですか?誰かが明確な説明をしてから、良い例をいくつか追加していただければ幸いです(悪い例は実際には必要ありませんが、対照的に自由に追加できます)。
コメントが良いか悪いかについて多くの質問がありますが、なぜあなたに伝えるコメントの良い例が何であるかという特定の質問に対処する人は誰もいません。
最も一般的で最も特徴的な例は、さまざまな回避策に関するコメントです。たとえばこれは:
https://github.com/git/git/blob/master/compat/fopen.c :
/* * The order of the following two lines is important. * * FREAD_READS_DIRECTORIES is undefined before including git-compat-util.h * to avoid the redefinition of fopen within git-compat-util.h. This is * necessary since fopen is a macro on some platforms which may be set * based on compiler options. For example, on AIX fopen is set to fopen64 * when _LARGE_FILES is defined. The previous technique of merely undefining * fopen after including git-compat-util.h is inadequate in this case. */ #undef FREAD_READS_DIRECTORIES #include "../git-compat-util.h"
あなたはきっとGitとLinuxのソースでもっと多くの例を見つけるでしょう。どちらのプロジェクトもこのルールに従っています。
commit logsを使用して、このルールをさらに厳密に守ることもお勧めします。コードコメントの場合、コードを修正して、コメントの更新を忘れることがあります。通常のプロジェクトのコードの量で、それは遅かれ早かれ起こることが保証されています。一方、コミットログは特定の変更に関連付けられており、バージョン管理システムの「注釈」/「注釈」機能を使用して呼び出すことができます。繰り返しになりますが、GitとLinuxにはいくつかの良い例があります。
見てください このコミット で。 (ここではコピーせず、長すぎます)。正確に何が間違っていたのか、なぜそれが間違っていたのかを説明するページ全体(および少し画面全体)の4つの段落があり、SIX行のすべての動作を変更します。彼らは2つの目的のためにこのようなコメントを使用します:
(注:これらの2つの例を思い付くには、gitレポジトリをランダムにブラウジングするのに最大10分かかりました。そのため、そこにもっと簡単に見つけることができます)
あなたに知らせるコメントwhyはコードの背後にある理由を説明します-例えば:
// We need to sync the values if the temp <doodad> GUID matches one of the active <doodad>'s
// GUID, as the temp <doodad> has the most recent values according to the server and said
// values might have changed since we added the <doodad>. We want a user to be able to <foo>
// the <doodad> whenever, which means those values must be accurate.
for (doodad in doodads) {
if ([doodad guid] == [tempDoodad guid]) {
[doodad updateFromDoodad:tempDoodad];
break;
}
}
あなたに知らせるコメントhowはコードが何をするかを説明します。
// Loop through our <doodads> and check for a GUID match. If it matches, copy the new values
// on the <doodad> that matches
for (doodad in doodads) {
if ([doodad guid] == [tempDoodad guid]) {
[doodad updateFromDoodad:tempDoodad];
break;
}
}
違いは、メンテナが最初のものを見て、「ああ、これは時代遅れかもしれません!」と言うことができるということです。 2番目のケースでは、メンテナがコメントしているため、コード自体が明らかにしていないことは何もわかりません(適切な変数名を想定)。
ゲートウェイアドレス(または妥当な推測)を取得する必要がある場所で私が取り組んだいくつかのiOSコードからの、whyコメントの実際の例を次に示します。 「受信ソケットを初期化する」などのコメントを残しておくこともできますが、それは何が起こっているのかをメンテナー(または将来私)に伝えるだけであり、ゲートウェイアドレスを最初の場所。
/*
We're going to do something really hacky here and use a custom partial
implementation of traceroute to get our gateway IP address.
[rant removed - irrelevant to the point]
There's no good way to get at the gateway address of an iDevice
right now. So, we have two options (per https://devforums.Apple.com/message/644915#644915 ):
1. Get at and parse the routing table (like netstat -rn, or route -n)
2. Do a traceroute and grab the IP address for the first hop
As far as I can tell, the former requires <sys/route.h> from the Mac OS X
header files, which doesn't seem like a good idea to me. Also, there's a
thread on the Apple Developer forums that seems to imply that header isn't
in iOS for a reason (https://devforums.Apple.com/message/774731#774731 ).
So when we send our request with a TTL of one it will survive a single hop
to the router and return, triumphant, with the router's IP address!
Viva la kludge!
PS: Original source was the below SO question, but I've modded it since then.
http://stackoverflow.com/questions/14304581/Hops-tracing-ttl-reciveform-on-ios/14304923#14304923
*/
// Default to using Google's DNS address. We used to try checking www.google.com
// if reachability reported we had internet, but that could still hang on routers
// that had no internet connectivity - not sure why.
const char *ip_addr = [kGoogleDNS UTF8String]; // Must be const to avoid undefined behavior
struct sockaddr_in destination,fromAddr;
int recv_sock;
int send_sock;
// ... more code follows
ジェフ・アトウッドのブログ投稿での引用から私の回答を始めたいと思います コードは方法を教え、コメントは理由を教えます :
最高の種類のコメントはあなたが必要としないものです
彼はまたこう述べています:
まず、コメントを頼りにすることなく、コードをできるだけ簡単に理解できるように努力する必要があります。コメントを追加し始めるのは、コードをわかりやすくすることができない場合に限られます。
私は完全に同意し、この時点で、コードを可能な限り簡単にする前に、コードを機能させてからリファクタリングを開始することを追加する必要があります。したがって、リファクタリング前の最初の実行中に、なぜコメントを追加することが非常に役立ちます。
たとえば、データの解析中に2次元のハッシュテーブルを含む3つのネストされたループを使用して平日のテーブルを埋める場合、数週間見ていない場合、誰かまたは自分自身によって何が行われたかを追跡できなくなり、突然リファクタリングされます。
[loop1]6oclock -> [loop2]Monday -> [loop3]stage 1 to 4
-> tuesday-> stage 1 to 4
...
-> Saturday -> stage 1 to 4
7oclock -> Monday-> stage 1 to 4
....etc.
上部は、3つのネストされたループがリファクタリング前にどのように機能するかの例です。
また、いくつかの分岐条件を説明すると、プロセスで考えていたことがコードをよりよく理解するのに役立ちます。
// added a zero before the actual day in order for the days always to be 2 digits long.
if( actualDayFuture < 10 )
{
actualDayFuture = padIfSingleDigitDate(actualDayFuture);
}
単純で明白なコードでさえ、コメントとうまく連携します。ソフトウェアを保守する上で、同僚や自分自身にとっても、少しわかりやすく、わかりやすく、理解しやすくするためです。
確かにxpは自己説明的なコードを持っていると述べていますが、1行のコメントは害になりますか?
私はこれから blog から次のルールも非常に役立つと思います:
- 書く前に資料を理解する
- 聴衆が4年生であるかのように書く
- 読者があなたを誤解するかもしれない方法について考えてください
自分のコードや他の誰かのコード、あるいはレガシーコードに戻る必要がある人は誰でも、それが頭痛の種になる可能性があることを知っています。だから、何もコメントしない、またはほとんどコメントしないで怠惰になったり、ユーバーコーダーになろうとする代わりに、引用されたルールに従うことで、自分のコードを維持する必要のある自分や貧しいバガーの将来をより楽にしてみませんか?.
また、行われた多くのプログラミングの決定はレビュー中に疑われ、コードが何年にもわたって使用されたときに発見された主要なバグが原因でプログラムの動作に不可欠なコードのセクションがある場合でも、一部のパーツがなぜ書かれたのかが必ずしも明確ではありません。したがって、tl; drですべてを完全に退屈させないために、最後の引用 acmqueue で閉じます。
以前の明確で広範なドキュメントは、生き残り、適応できるソフトウェアを作成する上で重要な要素です。高水準に文書化することで、開発時間が短縮され、作業が改善され、収益が向上します。どんなテクニックからもそれ以上のものを求めるのは難しいです。
私は、特定の機能/コードがより完全に説明されている参照、または特定のプログラミング方法が選択された理由を説明するためにコメントを減らす傾向があります。
同様のスキルを持つ他のプログラマーがコードを使用または読み取ることを考えると、何かを達成するために予想とは異なる方法を使用する場合はコメントすることが重要です。したがって、この方法を選択する理由をコメントで説明できます。
たとえば、Androidデバイスで2つの異なるセンサーを使用でき、そのうちの1つがニーズに合わない場合、他のセンサーを選択した理由をコメントで説明できます。
したがって、「なぜ」は、選択した内容について根拠を与える必要があります。
コメントは、コードが何をしないかを示す必要があり、必ずしも[〜#〜] why [〜#〜]、[〜#〜] how [〜#〜] =、または[〜#〜] what [〜#〜]。適切な名前があり、関数の輪郭がはっきりしている場合、コードが何が起こっているのかを正確に伝えることができる可能性があります。例えば:
List<LightMap> maps = makeLightmaps(receivingModels);
TrianglePartitioner partition = new Octree(castingTriangles);
List<Photon> photons = firePhotons(lights, partition);
if (photons.Count > 0)
{
PhotonPartitioner photonMap = new KDTree(photons);
gatherPhotons(maps, photonMap, partition, lights);
}
このコードにはコメントは必要ありません。関数と型の名前は理解しやすくします。
ただし、上記のような流暢なコードを実際に作成することが困難または不可能である場合もあります。たとえば、次のコードスニペットは、球上の統計的にランダムなポイントを見つけるためのものです。数学はかなり不透明なので、説明へのリンクが付いたコメントは、わかりやすくするためのものです[〜#〜] how [〜#〜]機能します。これを関数にラップして[〜#〜] what [〜#〜]コメントを必要とせずに2回以上必要な場合にそれを行うことができます。そうでない場合、リンクタイトルもその部門で役立ちます。
double randomA = localGenerator.NextDouble();
double randomB = localGenerator.NextDouble();
//http://mathworld.wolfram.com/SpherePointPicking.html
double theta = 2 * Math.PI * randomA;
double phi = Math.Acos(2 * randomB - 1);
Vector3 randomDirection = new Vector3(Settings.ambientRayLength * (float)(Math.Cos(theta) * Math.Sin(phi)),
Settings.ambientRayLength * (float)(Math.Sin(theta) * Math.Sin(phi)),
Settings.ambientRayLength * (float)Math.Cos(phi));
コメントがコードが何をしていないかを示すもう1つの例は、決定を説明するためのものです。次の例では、コードはスレッド化されたコード内の非スレッドローカル変数をロックしません。これには理由があり、コメントは[〜#〜] why [〜#〜]を説明します。コメントがなければ、それはバグと見なされるか、単に気付かれることすらありません。
Random random = new Random();
Parallel.For(0, maxPhotons, delegate(int photonIndex, ParallelLoopState state)
{
...
//I don't actually care if this random number is unique between threads, threadsafty is not that big of a deal
// in this case and locking the random object could cause a lot of lock contention
while (random.NextDouble() > reflectProbability)
{
...
}
...
}
おそらく、そもそもランダムオブジェクトが並列ループ内に作成されない理由を説明するように改善することができます。理由がない場合は、誰かがやって来て、アイデア全体が愚かであり、リファクタリングに適した場所であることを認識させる可能性もあります。
さまざまな種類の「理由」を認識すると役立つ場合があります。
過度に複雑に見えるコードが単純化された場合に機能しない理由(たとえば、コードが一部のケースで機能することを保証するために、一見過剰な型キャストが必要になる場合があります)。
危険に見える特定の単純な操作が実際には安全である理由(たとえば、「私たちのデータフェッチルーチンは、最後よりも後のダミーアイテムアイテムが他のアイテムよりも少ないと報告し、その後のアイテムはより大きいと報告します。ソートする必要があるすべてのアイテム別の前に、一貫した昇順または降順で、少なくとも1つ以上の(おそらくダミーの)アイテムが後に続きます。
多くの場合、コードの一部の2番目のタイプのコメントは、別の部分の最初のタイプのコメントと「一致」することがあります(たとえば、「この一連の操作は簡略化できるように見えますが、Fitzルーチンはバンダースナッチが鳴らされるまで、ウォングルはウーズリングされていません。」)
私のコメントのすべてが「なぜ」タイプであるとは限りませんが、多くはそうです。
これらは1つの(Delphi)ソースファイルからの例です。
// For easier access to the custom properties:
function GetPrivate: Integer; // It's an integer field in the external program so let's treat it like that here
// The below properties depend on the ones above or are calculated fields.
// They are kept up-to-date in the OnEventModified event of the TTSynchronizerStorage
// or in the ClientDataSet.OnCalcFields of the TcxDBSchedulerStorage.DataSource.DataSet
property IsModified : Boolean read GetIsModified write SetIsModified;
property IsCatTT : Boolean read GetIsCatTT write SetIsCatTT;
property IsSynced : Boolean read GetIsSynced write SetIsSynced;
lLeftPos := pos(' - [',ASubject); // Were subject and [shiftnaam:act,project,cust] concatenated with a dash?
// Things that were added behing the ] we will append to the subject:
// In the storage the custom value must also be set for:
Self.SetCustomFieldValueByname(cCustFldIsCatTT,Result);
// When we show the custom fields in a grid, the Getters are not executed,
// because the DevEx code does not know about our class helpers.
// So we have two keep both properties synchronized ourselves:
// lNewMasterEvent was set to usUpdated, overwrite because we added:
if ARepair then
lNewMasterEvent.CustUpdateStatus := usRecreated
// The source occurrence date may have bee changed. Using GetOriginalDate we can retrieve the original date,
// then use that for creating a target occurrence (and update its date):
lNewTTOccurrence.CustSyncEntryID := cSyncEntryID0; // Backward compatibility with old sync methode
// Single event became recurring or vice versa; replace entire event
// In contradiction to CopySingleEventToTimeTell, CopyMasterEventToTimeTell does not have a ANewStatus parameter
// because master events are always added.
(my)whyコメントは通常、それを実行するコードの前にあることに注意してください(したがって、コロンで終わります)。
whatのみが発生していることを説明するコメントがあります。プロセスに論理グループを持つ多くのステップがある場合(およびコードが自動的に表示されるようにコードがリファクタリングされていない場合)、次のようにコメントします。
// Step 1. Initialization
プログラムを書いている場合は、ランダムに入力するだけでなく、必要なもののモデルがあるため、それが正式なドキュメントであるかどうかにかかわらず、忘れないでください。またはちょうどあなたの頭の中で。頭の中にあるものは、コンピュータのソフトウェアやデータと同じくらいリアルです(バグが含まれている可能性が高い)。
コードを読んでいる誰かの頭にはそのモデルがないかもしれないので、コメントはモデルが何であったか、そしてコードがどのようにそれに関連しているかを彼らに伝えるのに役立ちます。それが「なぜ」の意味だと思います。確かに、コード自体をできる限り自明にするのは良いことですが、それだけでは必ずしも十分ではありません。例:
// transform the x,y point location to the nearest hexagonal cell location
ix1 = (int)floor(0.5 + x + y/2);
iy1 = (int)floor(0.5 + y);
その上、モデルは時間とともに変化し、それらの変更をコードに転送する必要があります。そのため、コメントには、コードに「なぜ」何かが含まれているだけでなく、予想されるモデルの変更に応じて、重要な変更方法を示す必要があります。例:
// to change to square cell locations, remove the "+ y/2" in the above code
コメントの目的は時々無視されていると思います。
WHYは、奇妙または非論理的な方法で何かをする理由であると理解しています。 [〜#〜] how [〜#〜]は、たとえコードが「意味をなさない」としても、たとえ奇妙なものであっても、コード自体で見ることができます。 [〜#〜] what [〜#〜]は、クラス/関数ドキュメントの冒頭で最もよく説明されています。 [〜#〜] why [〜#〜]を追加することで、HOWとWHATに含まれていないものについて説明します。あなたのコントロール。
もちろん、ユニコーンと虹の地の外では、いつもそうとは限りません...
方法:
foreach($critters as $creature) {
$creature->dance();
}
WHAT:
/* Dancing creatures v1.0
*
* The purpose of this is to make all your critters do the funky dance.
*/
foreach($critters as $creature) {
$creature->dance();
}
なぜ:
// We had to store the items in an array of objects because of _____ (reason)
foreach($critters as $creature) {
$creature->dance();
}
特にAPIを他の開発者に渡したり、doxygenのようなautodocのツールを使用したりする場合は、常にC++ヘッダーファイルにコメントを書き込むことを学びました(名前が良いヒントであっても、関数が何を行うかは常に明確ではありません)。
だから私には典型的なコメントは次のようになります
/*** Functionname
/* What happens here
/* [in] Params
/* [out] params
/***
私がWHYコメントを使用したのは、「これに触れないでください!」や「プログラムが行を壊す場合IS削除されました...」
回避策、ハッキング、および奇妙な動作は、私の目でWHY基準に該当します...
非常に良い、そして陽気な例は、リチャードという誰かが書いた、めちゃくちゃになったコードのこの「回避策」で、他の誰かがそれをラップしてコメントで理由を説明しました... https://stackoverflow.com/a/184673/979785
残念ながら、ブル****をラップすることを余儀なくされることはかなりあります。「それは常にそのようにされてきた」か、またはアクセス権がないために、オリジナルに触れることができないためです。オリジナルを修正する時間がないので、オーバーヘッドの対象にはなりません。
コードは実行計画を指定することになっています。そうすることで、プログラムのフォロワー(またはコンパイラー)は、何をすべきか、そしてどのように行うべきかを理解できます。プログラムのフォロワーが従うことができるステップに分解されるもの。基本的な手順はその方法です。
コーダーの意図は別の問題です。単純で明確で単純なコードでは、その意図は明白です。合理的に熟練した人間の読者は、コードを読むだけで、コードブロックの意図にたどり着きます。ほとんどのコードはこのように読む必要があります。
時折、意図と計画の関係が不明確になります。コードは何をどのようにして明らかにしますが、理由は明らかにしません。そのとき、意図を明らかにするコメントに価値があります。プログラマーの意図がその理由です。
この問題は、複雑でやや複雑なデータモデルに対してストアドプロシージャとビューをたどっています。
「x.accountがnullでなく、x.address in(フェデックスからのアドレスを選択)次にx.account else y.account end」のような(多数の)選択があり、時間はありませんが生産性が期待されますall to readすべてのソースコード。そして、この例は一種の理にかなっていますが、それでもなお不可解です。
フェデックスの場合はxで、そうでない場合はyである理由を説明するコメントは、システム全体に光を当て、それらを十分に読んだときに取得を開始します。そして、これは単純化しすぎて、数百または数千の同様の発言があります。2007年の親切な開発者がそれらの理由を述べた人には誰でも私の心は温かく輝いています。
ええ、そうです、複雑な複雑なデータモデルと毛深いビューと、複数の有効な名前のパスを持つストアドプロシージャです。G-dの愛情で、理由を教えてください。
私はこのコメントを書いたところです。これは、説明する具体的な例ですwhyコードの行はそれが何であるか、特に私が変更した理由です。
このメソッドは、格納されたデータを調べて、現在の日付が一方の端であり、開始日が他方の端であるかどうかを評価します。
// In principal, this should be ">=", as we may have data up to the account start
// date but not complete for that day; in practice, 98% of the time if we have
// data for the start date it *is* complete, and requerying it would be a waste
// of time.
while (endDate > accountStartDate)
...
ご想像のとおり、大なり演算子は大なり小なりでした。コメントは、なぜ古い値が理にかなっているのか、なぜ新しい値の方が優れているのかを説明しています。将来誰かがこれを見ると、「>」の使用は見落としではなく、最適化であることがわかります。その後、その時の必要性に基づいて、変更したりそのままにしたりできます。