注释掉的代码可以成为有价值的文档吗?


83

我写了以下代码:

if (boutique == null) {
    boutique = new Boutique();

    boutique.setSite(site);
    boutique.setUrlLogo(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getLogo());
    boutique.setUrlBoutique(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getUrl());
    boutique.setNom(fluxBoutique.getNom());
    boutique.setSelected(false);
    boutique.setIdWebSC(fluxBoutique.getId());
    boutique.setDateModification(new Date());

    boutiqueDao.persist(boutique);
} else {
    boutique.setSite(site);
    boutique.setUrlLogo(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getLogo());
    boutique.setUrlBoutique(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getUrl());
    boutique.setNom(fluxBoutique.getNom());
    //boutique.setSelected(false);
    boutique.setIdWebSC(fluxBoutique.getId());
    boutique.setDateModification(new Date());

    boutiqueDao.merge(boutique);
}

这里有一个注释行。但是我认为,通过使if和之间的区别显而易见,可以使代码更清晰else。颜色突出显示的差异更加明显。

这样的代码注释掉是个好主意吗?

Answers:


109

大多数答案都集中在如何重构这一特定情况上,但是让我对为什么注释掉的代码通常不好的问题提供一个一般性的答案:

首先,注释掉的代码不会编译。这是显而易见的,但这意味着:

  1. 该代码甚至可能无法正常工作。

  2. 当注释的依赖项更改时,它显然不会中断。

注释的代码非常“死代码”。它坐在那里的时间越长,它腐烂的越多,并且为下一个开发人员提供的价值越来越少。

第二,目的不明确。您确实需要更长的注释,以提供有关为什么存在随机注释行的上下文。当我只看到注释的代码行时,我必须研究它如何到达那里,只是为了了解为什么到达那里。谁写的?什么承诺?提交消息/上下文是什么?Etcetera。

考虑替代方案:

  • 如果目标是提供使用函数/ api的示例,请提供单元测试。单元测试是真实的代码,并且在不再正确时会中断。
  • 如果目的是保留代码的先前版本,请使用源代码管理。我宁愿签出以前的版本,然后在整个代码库中切换注释以“还原”更改。
  • 如果目的是维护同一代码的替代版本,请再次使用源代码控制。毕竟,这就是分支的目的。
  • 如果目的是要弄清楚结构,请考虑如何重组代码以使其更明显。其他大多数答案都是有关如何执行此操作的良好示例。

5
我认为您缺少一个重要原因:文档:如果目的是记录替代设计方案,则应提供替代方案的解释,尤其是为什么要替代替代方案,而不是原始代码。
Sarien

14
用编程语言比用编程语言更好地解释设计选项。
Mark E. Haase 2014年

3
后续接管我的项目的开发人员如何知道源控件中存在替代/先前/失败的实现?是否期望新开发人员浏览所有版本历史并更改日志?或者,对于每个有用的替代实现,通常都使用注释来链接到先前提交的哈希值?如果是这样,我从来没有注意到。
Moobie

但是有一个警告。有时,两种等效的代码方法可能在性能和可靠性上有所不同,一种方法是高性能的,另一种是可读的。在这种情况下,可以使用 Performant变体,但是将可读性变体放在注释中,这样可以更容易理解代码的目的。有时,(带注释的)代码行可能比冗长的解释更清晰。
Flater

263

此代码的最大问题是您重复了这6行。一旦消除了重复,该注释就无用了。

如果创建boutiqueDao.mergeOrPersist方法,则可以将其重写为:

if (boutique == null) {
    boutique = new Boutique();
    boutique.setSelected(false);
}

boutique.setSite(site);
boutique.setUrlLogo(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getLogo());
boutique.setUrlBoutique(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getUrl());
boutique.setNom(fluxBoutique.getNom());
boutique.setIdWebSC(fluxBoutique.getId());
boutique.setDateModification(new Date());

boutiqueDao.mergeOrPersist(boutique);

创建或更新某个对象的代码很常见,因此您应该解决一次,例如通过创建一个mergeOrPersist方法。对于这两种情况,您当然不应该重复所有分配代码。

许多ORM都以某种方式内置了对此的支持。例如,如果的id值为零,他们可能会创建一个新行,如果的值为非零,则可能会更新现有行id。确切的形式取决于所讨论的ORM,并且由于我不熟悉您所使用的技术,因此我无法为您提供帮助。


如果您不想创建mergeOrPersist方法,则应以其他方式消除重复,例如通过引入isNewBoutique标志。那可能并不漂亮,但是它比复制整个分配逻辑要好得多。

bool isNewBoutique = boutique == null;
if (isNewBoutique) {
    boutique = new Boutique();
    boutique.setSelected(false);
}

boutique.setSite(site);
boutique.setUrlLogo(CmsProperties.URL_FLUX_BOUTIQUE + fluxBoutique.getLogo());
boutique.setUrlBoutique(CmsProperties.URL_FLUX_BOUTIQUE + fluxBoutique.getUrl());
boutique.setNom(fluxBoutique.getNom());
boutique.setIdWebSC(fluxBoutique.getId());
boutique.setDateModification(new Date());

if (isNewBoutique)
    boutiqueDao.persist(boutique);
else
    boutiqueDao.merge(boutique);

166

这是一个绝对令人恐惧的想法。尚不清楚目的是什么。开发人员是否错误地注释了这一行?要测试什么?这是怎么回事?!

除了我在这两种情况下都看到绝对相等的6行这一事实之外。相反,您应该防止此代码重复。这样一来,您会更清楚地调用setSelected。


9
同意 我假设看到注释掉的行是已删除的旧行为。如果需要注释,则注释应使用自然语言,而不是代码。
2013年

4
我完全同意!最近,我花了数小时试图理解和清理我继承的一些应用程序,这些应用程序由于这种做法几乎完全不可读。它还包括已与所有其他代码断开连接但未被删除的代码!我认为这是版本控制系统背后的主要目的。它具有注释以及随之而来的更改。最后,由于这种做法,我至少要增加2个星期的工作量。
bsara


120

不,这是一个糟糕的主意。基于这段代码,我想到了以下想法:

  • 此行已被注释掉,因为开发人员正在调试它,却忘记将其恢复到以前的状态
  • 此行已被注释掉,因为它曾经是业务逻辑的一部分,但现在不再如此
  • 该行被注释掉,因为它在生产中导致性能问题,并且开发人员希望了解对生产系统的影响

看到成千上万行注释掉的代码后,我现在正在做唯一明智的事情:我立即将其删除。

没有明智的理由将注释掉的代码检入到存储库中。

另外,您的代码使用了大量重复项。我建议您尽快对其进行优化,以提高可读性。


1
但是我想摆脱了重复的代码,我认为这几乎不能看作是一种优化。
Alexis Dufrenoy

23
它是人类可读性的优化
JK。

11
@Traroth,您可以针对速度,内存使用,功耗或任何其他指标进行优化,因此我看不到您无法针对可读性进行优化(尽管作为指标,它有点笨拙)
jk。

3
确实,我的意思是人类可读性。这里的一个小提示:编程中最重要的责任就是代码。因此,少即是多。
Dibbeke 2013年

4
软件作为一种责任也是在处理c2.com/cgi/wiki?SoftwareAsLiability从那里:“生产更多的代码并不总是一个增益代码测试和维护,昂贵,因此,如果同样的工作可以用更少的代码来做到这一点。是一个加号。不要注释掉无效的代码,只需将其删除即可。注释掉的代码很快就会变得陈旧且无用,因此您最好尽快删除它,以免造成混乱。 。”
ninjalj 2013年

51

我想补充指出CodesInChaos的答案,您可以将其进一步重构为小方法。通过组合共享通用功能避免了以下条件:

function fill(boutique) {    
  boutique.setSite(site);
  boutique.setUrlLogo(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getLogo());
  boutique.setUrlBoutique(CmsProperties.URL_FLUX_BOUTIQUE+fluxBoutique.getUrl());
  boutique.setNom(fluxBoutique.getNom());
  boutique.setIdWebSC(fluxBoutique.getId());
  boutique.setDateModification(new Date());
}    

function create() {
  boutique = new Boutique();      
  fill(boutique);
  boutique.setSelected(false);
  return boutiqueDao.persist(boutique);
}

function update(boutique) {
  fill(boutiquie);
  return boutiquieDao.merge(boutique); 
}

function createOrUpdate(boutique) {
  if (boutique == null) {
    return create();
  }
  return update(boutique);  
}

6
我认为这是最干净的建议。
亚历克西斯·杜弗洛尼

+1,我还要补充一点,越多地避免传递null对象越好(我发现此解决方案是一个很好的例子)。
Nadir Sampaoli 2013年

我会将boutiqueDao输入作为传递给createupdate
快乐的小孩子午睡

这如何工作?您如何知道何时调用创建和何时调用更新?原始代码查看精品店,并知道是否需要更新或创建。在您调用创建或更新之前,它什么都没做...
Lyrion 2013年

Lyrion:平凡的,为清楚起见,我还将添加该代码。
亚历山大·托斯汀

27

尽管这显然不是注释掉代码的好案例,但我认为有必要这样做:

// The following code is obvious but does not work because of <x>
// <offending code>
<uglier answer that actually does work>

这是对以后看到它的人的警告,那就是没有明显的改善。

编辑:我说的是小事。如果太大,您可以改为解释。


5
怎么了 // the following part done like it is because of X?解释为什么你做了什么你做的方式,而不是你为什么这样做不是在一些它特定的方式。在您的特定示例中,它完全不需要潜在的大块注释代码。(我没有拒绝投票,但可以肯定为什么会被拒绝投票。)
CVn 2013年

13
迈克尔(Michael),因为这样可以使其他编码人员(以及您自己的几天/几周/几月后)清楚地知道,是的,您确实尝试了这种更简洁/更聪明的方法,但是不,由于X,它没有用,所以他们不应该这样做麻烦再试一次。我认为这是一种完全合法的方法,并且对此可悲的答案持反对态度。
Garrett Albright

1
@GarrettAlbright:谢谢,我很高兴看到有人得到它。
罗伦·佩希特尔

3
@LorenPechtel:不仅如此,我写的大致相同。在某些情况下,快速了解哪些“显而易见的”解决方案已经尝试但没有成功以及为什么它们不起作用非常非常有用。
JensG 2014年

3
除了失败的代码和解释之外,我还将注释掉代码的替代实现,这些实现在不同的生产环境中可能更有效。例如,我已经编码了算法的简单指数时间版本和复杂的多项式时间版本。但是在当前的生产中,n它很小,并且指数算法要快得多。如果n以后有所改变,将来跟着我的项目的开发人员将如何知道深藏在源代码控制中数百个提交中的代码的不同实现?
Moobie

14

在这个特定的示例中,我发现注释掉的代码非常模棱两可,主要是因为Dibkke的答案中列出了原因。其他人提出了一些方法,您可以重构代码,甚至避免这样做的诱惑,但是,如果由于某种原因(例如,行很相似,但距离不够近)而无法做到这一点,我会喜欢这样的评论:

//无需取消选择这家精品店,因为[无论如何]

但是,我认为在某些情况下,留下(甚至添加注释掉的)代码是不可理解的。当使用MATLAB或NumPY之类的东西时,通常可以编写等效的代码,这些代码要么1)遍历一个数组,一次处理一个元素,要么2)一次操作整个数组。在某些情况下,后者要快得多,但也很难阅读。如果将某些代码替换为其向量化的等效代码,则会将原始代码嵌入附近的注释中,如下所示:

%%下面的矢量化代码执行此操作:

% for ii in 1:N
%    for jj in 1:N
%      etc.

%,但矩阵版本在典型输入上的运行速度快约15倍(MK,2013年10月10日)

显然,需要注意两个版本实际上做同样的事情,并且注释要么与实际代码保持同步,要么在代码更改时被删除。显然,关于过早优化的通常警告也适用于...


“显然,需要注意两个版本实际上做同样的事情,并且注释要么与……保持同步。”-在那儿,您解释了为什么这不是一个好主意。
sleske 2014年

1
好吧,所有评论都有问题吧?一些矢量化的代码非常不透明,以至于注释是值得的,并且具有“展开”版本可以很方便地进行调试。
Matt Krause

真正。不过,我会尽量使注释简短,而不要使用完整的源代码。无论如何,如果您有一个示例,询问如何使其更具可读性将是一个很好的问题(在此处或在codereview.se上)。
sleske 2014年

1
在您的最后一种情况下,我会将两个代码变体都保留为可编译代码。
CodesInChaos 2015年

12

我唯一看到有用的注释代码是在配置文件中,其中提供了每个选项的代码,但是注释掉了,通过删除注释标记就可以轻松启用设置:

## Enable support for mouse input:
# enable_mouse = true

在这种情况下,注释掉的代码有助于记录所有可用选项以及如何使用它们。在整个过程中也都使用默认值,因此代码也记录了默认设置。


7

一般而言,代码仅是编写代码的人的自我证明。如果需要文档,请编写文档。不能指望一个刚接触源代码的开发人员坐下来阅读数千行代码,试图从高层次了解正在发生的事情。

在这种情况下,注释掉的代码行的目的是显示两个重复代码实例之间的差异。与其尝试用注释巧妙地记录差异,不如重写代码以使其有意义。然后,如果您仍然有必要对代码进行注释,请编写适当的注释。


2
这完全正确。许多人(包括我自己在内)都认为他们的代码太棒了,以至于不需要文档。但是,除非有完整的文档和注释,否则世界上其他所有人都会发现它。
瑞安·阿莫斯

代码仅是编写代码的人的自我证明。 ”-请选择您一年前编写的一段复杂的,未注释的代码,并尝试在有限的时间内理解它。你不能吗 哎呀
JensG 2014年

我认为这有些微妙。许多编写良好的代码是可理解的,无需注释即可理解。问题是试图找出更大的情况(即使是在相当本地的水平),而您只需要复杂的细节。注释可以很好地解释非显而易见的代码块,但是当您有不错的文档字符串,解释每个函数,类和模块的实际用途时,则不需要太多帮助来理解实现。
卡尔·史密斯

4

不,注释的代码会过时,并且比不值钱的代码更糟,它通常是有害的,因为它巩固了实现的某些方面以及所有当前的假设。

注释应包括界面详细信息和预期功能;“预期功能”:可以包括,首先我们尝试一下,然后尝试一下,然后失败。

我见过的程序员试图将内容留在注释中只是爱上了他们所写的内容,不想失去它,即使它并没有给最终产品增加任何东西。


2

在极少数情况下可能会发生,但并非如您所愿。其他答案已经很好地说明了原因。

罕见的情况之一是我们在我的商店中使用的RPM模板模板,以此作为所有新软件包的起点,主要是确保没有遗漏任何重要内容。我们的大多数(但不是全部)软件包都有一个tarball,其中包含具有标准名称并用标记指定的源:

Name:           foomatic
Version:        3.14
 ...
Source0:        %{name}-%{version}.tar.gz

对于没有源代码的软件包,我们将标记注释掉,并在标记之上放置另一个注释以保持标准格式,并指出有人在开发过程中已停止思考该问题:

Name:           barmatic
Version:        2.71
 ...
# This package has no sources.
# Source0:        %{name}-%{version}.tar.gz

您无需添加将不会使用的代码,因为正如其他人所介绍的那样,它可能会误认为属于该代码的内容。它可以。但是,添加注释以解释为什么可能缺少代码的注释很有用:

if ( condition ) {
  foo();
  // Under most other circumstances, we would do a bar() here, but
  // we can't because the quux isn't activated yet.  We might call
  // bletch() later to rectify the situation.
  baz();
}

5
可以说,注释并未被注释掉。
jk。

1
@jk:你可以说是正确的。
Blrfl 2013年

1

应用程序未使用注释掉的代码,因此需要附加注释以说明为什么不使用它。但是,这范围内,有那里注释掉的代码可能是有用的情况。

我想到的是您使用一种通用且有吸引力的方法解决问题的情况,但事实证明,您实际问题的要求与该问题稍有不同。特别是如果您的需求实际上需要更多的代码,则维护人员使用旧方法“优化”代码的诱惑可能会很强烈,但这样做只会使错误重新出现。在注释中保留“错误的”实现将有助于消除这种情况,因为您可以使用它来确切说明原因在这种情况下该方法是错误的。

我无法想象这种情况经常发生。通常,在不包含示例“错误”实现的情况下进行解释就足够了。但我想象到这还不够,那么因为这个问题是它是否是有用的,是的,它可以的情况下。只是不是大部分时间。


1
抱歉,我看不到注释代码的任何价值。未使用注释掉的代码,因此在生产代码中没有位置。
Vladimir Kocjancic 2014年

1
请定义“二手”。
JensG 2014年

我认为他的意思是“被处决”
Alexis Dufrenoy

-2

看起来不错,哥们。

注释的代码是...只是代码。代码用于逻辑的实现。使代码本身更具可读性是一门艺术。正如@CodesInChaos已经暗示的那样,重复的代码行并不是很好的逻辑实现

您是否真的认为一个真正的程序员会更喜欢可读性而不是逻辑实现。(顺便说一下,我们可以在逻辑表示中添加注释和“补语”)。

就我而言,应该为编译器编写代码,这很好-如果“它”理解该代码。对于人类可读性而言,注释对于开发人员(从长远来看),对于重用该代码的人员(例如测试人员)都是有益的。

否则,您可以在这里尝试更灵活的方法,例如

boutique.setSite(site)可以替换为

setsiteof.boutique(网站)。OOP具有不同的方面和观点,通过它们可以提高可读性。

虽然这段代码乍一看似乎很吸引人,并且可以认为他已经找到了一种人类可读的方法,而编译器也可以很好地完成其工作,但是我们所有人都开始遵循这种做法,这将导致模糊文件的可读性降低随着时间的推移,而且会越来越复杂,因为它会逐渐扩展。


15
“就我而言,应该为编译器编写代码。”哦,请不要。这样一来,您最终会遇到怪兽,看起来像它们可以直接从混淆C竞赛之类的东西中获取。计算机是二进制的,而人类则使用模糊逻辑(顺便说一下,这对宠物主人来说是两倍)。今天,计算机时间几乎是免费的(基本上只是用电),而程序员的时间则相对非常昂贵。为人类编写代码,编译器将理解它。在不考虑人类的情况下为编译器编写代码,您不会在团队中结交很多朋友。
CVn

3
为编译器编写代码 ”-实际上,您不需要。您应该记住的人就是负责维护您的代码的人。
JensG 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.