首先,在这个问题上,我想避免争论源代码注释是好是坏。我只是想更清楚地理解人们谈论要告诉您原因,原因或方式的注释时的含义。你怎么”。很容易就抽象的观点达成一致。但是,人们通常会像教条一样掉下来,离开房间而无需进一步解释。我已经看到它在许多不同的地方和上下文中使用过,看起来人们可以就流行语达成共识,但是他们似乎完全是在谈论不同的事情。 :如果评论可以告诉您原因,那么我们在说什么?这就是为什么这段代码首先存在的原因吗?这是片段代码应该做什么?如果有人可以给出清楚的解释,然后再添加一些很好的示例,我将不胜感激(确实不需要坏的示例,但是可以随意添加以进行对比)。

是否存在很多问题评论是好是坏,但是没有人能解决一个具体的问题,即告诉您为什么的评论的好例子是什么。

评论

有时,最好的评论说明了为什么不是。我曾经遇到过一些看起来很容易简化的复杂代码。注释解释了为什么在这种特定情况下无法进行明显的简化(因为原始开发人员已经尝试过)。

关于评论是好还是不好有很多问题,但是没有人能解决一个具体问题,即什么是可以告诉您为什么的好评论示例。如果每个人都提供有效的示例,那么它们都是正确的答案。该网站的格式是为了简化问答过程,在此过程中并非所有答案都相等。

好点,@ david-kaczynski。你有什么建议?

在我脑海中,我想不出一种表达问题的方法,以使单个示例或通用策略可以成为“最佳”答案。 p.se中有一个聊天部分:chat.stackexchange.com/rooms/21/the-whiteboard,但实际上可能会有一个更好的论坛来回答您的问题。平心而论,您的问题似乎在这里得到了社区的积极回应,因此可能不必担心。为了找到有用的评论示例,我可以提供的最佳建议是浏览流行的公共git存储库。

#1 楼

最常见和最独特的示例是有关各种解决方法的注释。例如以下示例:


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源代码中找到更多示例;两个项目都尝试遵循此规则。

我还建议对提交日志更加严格地遵循此规则。对于代码注释,您可能会修复代码,但忘记更新注释了。借助普通项目中的大量代码,可以保证它迟早会发生。另一方面,提交日志与特定更改有关,可以使用版本控制系统的“注释” /“责备”功能进行调用。同样,Git和Linux也有一些很好的例子。

例如在此提交。 (此处未复制,太长了)。它有四个段落,几乎占据了整个页面(有点过筛),描述了到底是什么错误以及为什么是错误的,然后继续进行并修改了所有高达六行的内容。他们将这样的注释用于两个目的:


对所有提交的更改进行审核,而提交日志则是向审核者解释更改的内容。 ,则会使用“ pickaxe”或“ blame”来检索相关日志,以避免恢复为更早的错误行为。有了这两个示例,那么肯定可以在其中找到更多的内容)

#2 楼

一条注释,告诉您为什么解释代码背后的原因-例如:

// 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;
    }
}


一条注释,告诉您如何解释代码的作用。

// 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;
    }
}


不同之处在于,维护人员可以查看第一个,并说:“哦,所以这可能已经过时了!”在第二种情况下,所说的维护者有一个注释,它不会告诉您代码本身没有显示的任何内容(假设变量名很好)。我在需要获取网关地址(或对此的合理猜测)的地方工作过的一些iOS代码。我可能只留下了诸如“初始化接收套接字”之类的注释,但这只会告诉维护者(或将来给我)正在发生的事情,而不是为什么我必须做这个奇怪的事情才能在网关中获取网关地址第一名。

/*
 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


评论


第一个示例过于冗长,包括许多“如何”。它应该说只是“从temp 更新,以便用户可以在任何时候安全地它”。其余的从这个或代码中隐含着微不足道的含义。最后一个示例的前四段中的“童话介绍”也完全没有意义。我要离开“万岁啦!”;很有趣,到最后。但是开始之前,在获得真正的解释之前,必须先挖掘很多单词。

– Jan Hudec
2013年8月9日15:03

@JanHudec根据您的反馈进行调整。看看对不对?

– Thegrinner
13年8月9日在15:38

关于第二个示例的好处之一是,它不仅解释了为什么代码以特定的方式工作,而且还解释了为什么没有采用其他合理的替代方法。这使代码更具可维护性,因为下一个阅读该代码并想到“为什么我不能仅解析路由表?”的人。可以阅读评论。此外,确实提出了合理理由来更改代码的人将更有信心这样做是安全的。否则,维护人员会担心在导致未知事件的(未知)场景中任何更改都会失败。

–布赖恩
13年8月9日在20:15



#3 楼

我想以Jeff Atwood在他的博客文章“代码告诉你如何,评论告诉你为什么”中的一句话开始回答:不需要


他还指出:


首先,您应该努力使您的代码尽可能简单,而不必依赖注释作为拐杖。仅在无法简化代码的地方,才应开始添加注释。


我完全同意,在这一点上,我必须补充一点,在开始使代码尽可能简单之前,我先使代码工作,然后开始重构。因此,在重构之前的第一次运行中,请添加注释为什么很有帮助。

例如,如果在解析数据时使用带有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声明有可以自我解释的代码,但是一行注释会不会痛?

我还发现此博客中的以下规则非常有帮助:尽管您的听众是四年级生
想一想读者可能会如何误解您



任何必须重新使用自己的代码,别人或什至是遗留代码的人都知道,这可能会令人头疼。因此,与其不懒惰或尝试成为一个超级程序员,不评论任何东西或很少发表评论,何不通过遵循引用的规则,使自己或一些可怜的bugger(这些人必须维护您的代码)来简化未来的生活。

在回顾过程中还对许多编程决定提出了质疑,尽管有些代码段由于一个主要的错误对于程序的运行至关重要,但并非总是很清楚为什么写了某些部分发现,因为该代码已使用多年。因此,为了不让所有人完全厌烦tl; dr,并用acmqueue的最后一个引号引起来:生存和适应。记录高标准将减少开发时间,改善工作并改善底线。很难从任何技术中获得更多要求。


评论


在第二个示例中,可以通过重构来完全消除注释:actualDayFuture = padIfSingleDigitDate(actualDayFuture);这是微不足道的,但更可靠的示例将从此方法中受益。

–克里斯·库德莫(Chris Cudmore)
13年8月9日在12:46

我也将条件转移到方法中。再次重申,这并非微不足道,但它使我完全不必考虑填充逻辑。不过,我不会替换您原来的示例,因为这是对问题的更好回答。它只是附带说明,还探讨了其他替代方法。

–克里斯·库德莫(Chris Cudmore)
13年8月9日在12:58

广告“确定xp声明有可以自我解释的代码,但是一行注释会受到伤害吗?”:注释很好,但是也存在过分注释的危险。注释的每一行都是人们在更改代码时可能会忘记更新的内容。

– Jan Hudec
13年8月9日在14:15

更好的说法是“最好的评论是无需评论”。不需要(但仍要写)的注释不是好的注释。

–卡兹
13年8月10日在0:36

有趣的是,引用的代码int directionCode =(x> oldX)吗? DIRECTIONCODE_RIGHT:(x> oldX)? DIRECTIONCODE_LEFT:DIRECTIONCODE_NONE;错误。当然应该是...(x
–chux-恢复莫妮卡
2015年9月9日在22:23



#4 楼

我倾向于将注释减少为对某些功能/代码进行更彻底解释的参考,或者解释为什么选择某种编程方式。 ,如果您使用与预期不同的方式来实现目标,则进行评论非常重要。因此,您可以在评论中解释为什么选择这种方式。

例如,如果您可以在Android设备上使用两个不同的传感器,但其中一个不适合您的需求,则可以解释在评论中为什么要选择另一个。

因此,“为什么”应为您做出的选择提供理由。

评论


参考是一个很好的例子。 //此方法使用furshclingeheimer算法对foobit进行ronsterize。见http:// ...

–克里斯·库德莫(Chris Cudmore)
13年8月9日在12:49

#5 楼

注释应该告诉您代码没有什么内容,不一定要用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);
}


此代码确实不需要注释。函数名称和类型名称易于理解。

有时候,很难或不可能真正制作出像上面这样的流利代码。例如,下一个代码段用于查找球体上的统计随机点。数学是相当不透明的,因此带有解释链接的注释可以帮助您了解其工作原理。可以将其包装在一个函数中,以告知其功能,如果需要的话可以不止一次,而不用注释,否则链接标题对该部门也有帮助。

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));
注释何时告诉您代码不用于解释决定的示例。在下一个示例中,代码不会在线程代码段内锁定非线程局部变量。这是有原因的,评论解释了原因。如果没有评论,它可能被认为是一个错误,或者甚至没有被注意到。首先在并行循环内部创建。如果没有理由,它也可能使某人出现,并意识到整个想法是愚蠢的,是重构的好地方。

评论


当注释以WriteText而不是//开头时,将代码描述为不需要注释是否合理?

–user25946
13年8月9日在17:42

正如我在回答中所说,即使没有打印语句,也不需要注释,但是为了使观点更清楚,我对其进行了编辑以删除打印语句。

–耐嚼口香糖
13年8月9日在20:03

#6 楼

识别不同类型的“为什么”可能是有帮助的-最值得注意的是:


原因很简单,看起来过于复杂的代码将无法工作(例如,可能看起来多余的类型转换是确保代码在某些极端情况下有效的必要方法。否则,该项目之后的项目将更大;任何应以一致的升序或降序在另一个项目之前进行排序的项目,其后将至少再有一个项目(可能是虚拟项目)”)。

在许多情况下,代码一部分中的第二种类型的注释可能与另一种类型中的第一种类型的注释“匹配”(例如,“虽然看起来可以简化此操作序列,但是Fitz例程直到Bandersnatch被鞭打后,Wongle才开始颤抖。“)

#7 楼

别忘了,如果您正在编写程序,则不仅是在随机输入内容,还因为您拥有所需模型,无论是在正式文档中还是在脑海中。 br />脑海里的东西与计算机中的软件/数据一样真实(并且可能包含错误)。

读代码的人可能并不喜欢这种模型,因此注释可以帮助他们了解模型是什么以及代码与代码之间的关系。
我认为这就是“为什么”的意思。
当然,使代码本身具有自解释性是很好的
示例:

// 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


我认为有时忽略评论姿势。

评论


问题是要举例。您能否添加示例以使此答案更有用?

–布莱恩·奥克利(Bryan Oakley)
2013年8月9日13:46

第一段代码对我来说就像是解释“什么”的经典示例。并不是说这是一个不好的评论,但我认为它不能回答OP的问题。

–user25946
13年8月9日在17:46

@Jon:如果没有评论,读者可以看到发生了什么,但不知道为什么。

–迈克·邓拉维(Mike Dunlavey)
13年8月15日在12:10

@MikeDunlavey:我不同意。我仍然不知道-您为什么想要最近的六角形单元位置?获取此位置的目的是什么?如果删除这两行会不会有任何影响?

–user25946
13年8月15日在14:36

#8 楼

并非我的所有注释都是“为什么”类型,但是很多注释都是。
这些是来自一个(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.


请注意(我)为什么注释通常会在要执行的代码之前(因此以冒号结尾)。

我确实有一些注释仅解释正在发生的事情,例如当一个流程中有许多包含逻辑分组的步骤(并且代码未重构为自动显示)时,我将这样评论:

#9 楼

我理解WHY是您以可能奇怪或不合逻辑的方式执行某项操作的原因,因为在特定情况下需要这样做。无论代码多么奇怪,即使代码没有“感觉”,也可以在代码本身中看到HOW。在类/函数文档的开头可能会最好地说明WHAT。这样一来,您就可以添加WHY,在其中解释任何未包括在HOW和WHAT中的内容,以及由于无法控制的原因而需要采取的特殊方法。

案例,在独角兽和彩虹之地之外...

如何:

foreach($critters as $creature) {
   $creature->dance();
}


内容:

为什么:

/* Dancing creatures v1.0
 * 
 * The purpose of this is to make all your critters do the funky dance.
 */

foreach($critters as $creature) {
  $creature->dance();
}


评论


这如何回答所提问题?

– gna
13年8月9日在10:18

引用OP的话:“那么,回到问题上:如果评论应该告诉您为什么,我们在说什么呢?”,我回答了这个问题:谈论的原因是存在原因的原因。给定一段代码。

– Juha Untinen
13年8月9日在10:21

这个问题要问几次实例。您能否在此答案中添加示例以使其更有用?

–布莱恩·奥克利(Bryan Oakley)
13年8月9日在13:48

我认为这些评论中的任何一条实际上都没有帮助。如果函数的签名是critters.dance(),则注释仅重复显而易见的内容,并且“我们无法使其与我们尝试的任何其他方式一起工作”完全没有帮助。同样,说“我们将为每个对象调用该方法”是在重复代码中非常清楚地说明的内容。

–布伦丹·朗(Brendan Long)
2013年8月9日23:16



#10 楼

我学会了总是在C ++头文件中写注释(因为尽管名称总是提供一个很好的提示,但并不总是清楚函数的作用),尤其是当您将API传递给其他开发人员或使用诸如doxygen之类的自动文档工具时。

所以对我来说,典型的注释类似于

/*** Functionname
/*   What happens here
/*  [in] Params
/*  [out] params
/*** 

甚至对程序员来说,例如“请勿触摸!因为...”或“如果删除线,程序将崩溃……”

解决方法,黑客行为和怪异行为符合我的WHY标准眼睛...

一个很好的甚至有趣的例子是这种“变通方法”,用于某些由理查德(Richard)某人编写的混乱代码,而其他人则将其包裹并在注释中解释原因... https ://stackoverflow.com/a/184673/979785

不幸的是,有很多次,您因为无法触摸原始图片而被迫包装牛市,或者是因为“一直都是这样”,或者您没有访问权限,或者......,您没有时间来修复原始文档,而实际上并没有资格承担这些开销。

评论


除了问题是关于评论,而不是文档。它们实际上是不同的东西(文档标记令人遗憾,但仍然不适用于该问题)。

–托马斯
2013年8月9日在11:53



很好的事实是,在我的母语注释和文档注释中可以互换使用,因此使用标签,我认为它也适用于此问题。那真的是拒绝投票的原因吗?

– AnyOneElse
13年8月9日在13:23

该问题询问了几次为什么要发表评论的示例,但是您所包括的唯一示例是评论。人们忽略了示例的答案可能会被您的示例误导。您能举一个为什么要发表评论的例子吗?

–布莱恩·奥克利(Bryan Oakley)
2013年8月9日13:50

尽管我说代码中很少有WHY,但是我举了两个例子:EDITED ...这是一个链接,绝对可以实现WHY

– AnyOneElse
2013年8月9日14:07

@AnyOneElse我没有投票。在我到达之前就在那里。

–托马斯
2013年8月9日14:44



#11 楼

代码应指定执行计划。这样,程序跟随者(或编译器)可以弄清楚该做什么以及如何去做。什么被分解为程序跟随者可以遵循的步骤。基本步骤就是操作方法。

编码器的意图是另一回事。在简单,清晰,直接的代码中,意图很明显。任何相当精通的人类阅读器都将通过阅读代码来达到代码块的目的。大多数代码应如下所示。

有时,意图和计划之间的关系不明确。该代码揭示了内容和方式,而不是原因。那时,揭示意图的评论是值得的。程序员的意图就是原因。

评论


这个问题要问几个例子。您可以在答案中添加示例以使其更有用吗?

–布莱恩·奥克利(Bryan Oakley)
2013年8月9日13:52



#12 楼

现在,这个问题已经遍历了存储过程和针对复杂且有些复杂的数据模型的视图。输入(从fedex选择地址),然后输入“ x.account否则y.account结束”,尽管根本没有时间读取所有源代码,但仍有望实现生产率。这个例子有点合理,但仍然难以理解。

注释解释了为什么在fedex中使用x,如果不是,则使用y –阐明了整个系统,当我们阅读了足够多的内容时,便开始理解它。而且这过于简化了,有成千上万的类似陈述。我对2007年那种善良的开发人员是谁的原因深感高兴。

所以,是的,复杂的卷积数据模型和毛茸茸的面纱以及具有多个有效命名路径的存储过程,请热爱G-d的人告诉我们原因。

#13 楼

我只是写了这个评论;这是一个具体的示例,用于解释为什么一行代码是它的本质,尤其是为什么我更改了它。

该方法检查存储的数据,并评估到目前为止这一天是否完整,直到另一端的开始日期为止。该评论解释了为什么旧值有意义,而新值更好。如果将来有人对此进行研究,他们会看到“>”的使用不是疏忽,而是一种优化。然后,他们可以根据当时的需要更改或保留它。