是否有可能在没有任何注释的情况下编写良好且易于理解的代码?

Posted

技术标签:

【中文标题】是否有可能在没有任何注释的情况下编写良好且易于理解的代码?【英文标题】:Is it possible to write good and understandable code without any comments? 【发布时间】:2010-10-21 12:28:03 【问题描述】:

谁能建议编写无需一行 cmets 即可理解的好代码的最佳方法是什么?

【问题讨论】:

相关问题:***.com/questions/209015/self-documenting-code 您是指函数体中的小 cmets,而不是 javadoc 中的函数和类文档吗? 【参考方案1】:

我认为代码在很大程度上可以自我记录,而且我认为这很关键,但即使阅读编写良好的代码也可能就像用显微镜观察人体细胞一样。有时需要 cmets 才能真正解释系统各部分如何组合在一起的大图景,尤其是当它解决一个非常复杂和困难的问题时。

考虑特殊的数据结构。如果计算机科学家曾经发表的关于数据结构的所有文章都是编写良好的代码,那么很少有人会真正理解一种数据结构相对于另一种数据结构的相对优势——因为任何给定操作的 Big-O 运行时有时只是通过阅读代码并不明显.这就是文章中提出的数学和摊销分析的用武之地。

【讨论】:

【参考方案2】:

我认为 cmets 应该表达原因,也许是什么,但代码应该尽可能多地定义方式(行为)。

应该有人能够阅读代码并从代码中了解它的作用(方式)。可能不明显的是您为什么需要这种行为以及这种行为对总体要求的贡献。

不过,如果需要评论,您应该会停下来。也许你是怎么做的太复杂了,需要写评论就说明了。

记录代码还有第三种选择 - 日志记录。一个充斥着日志语句的方法可以做很多事情来解释原因,可以触及什么,并且可能给你一个比命名良好的方法和关于行为的变量更有用的工件。

【讨论】:

【参考方案3】:

你通常可以把你的评论变成一个函数名,比如:

if (starColourIsGreaterThanThreshold()
    doSomething(); 


....

private boolean starColourIsGreaterThanThreshold()  
    return starColour.red > THRESHOLD && 
           starColour.blue > THRESHOLD && 
           starColour.green > THRESHOLD
 

【讨论】:

【参考方案4】:

是的,您可以编写不需要 cmets 来描述其功能的代码,但这可能还不够。

仅仅因为一个函数在解释它的作用时非常清楚,它本身并不能告诉你它为什么要这样做。

与所有事情一样,适度是个好主意。编写解释性代码,并编写 cmet 来解释它为什么存在或做出了什么假设。

【讨论】:

【参考方案5】:

我相信这是可能的,如果您考虑到 并非每个人都喜欢相同的风格这一事实。所以为了尽量减少cmet,了解你的“读者”是最重要的。

在“信息系统”类软件中,尽量使用陈述句,尽量将代码行近似为英文行,避免“数学编程”(以i、j和k为索引,第一个-liners-to-do-a-lot)不惜一切代价。

【讨论】:

【参考方案6】:

编写良好的代码可能不需要 cmets 解释您在做什么,但您仍然需要 cmets 解释原因。

【讨论】:

【参考方案7】:

在某些情况下 - 是的,但在许多情况下不是。 Yes 部分已经被其他人回答了 - 保持简单,写得好,给它起可读的名字,等等。No 部分用于解决你在代码中解决的问题根本不是代码问题,而是特定于领域的问题或业务逻辑问题。即使没有 cmets,我也可以阅读糟糕的代码。这很烦人,但可行。但是,如果不理解为什么会这样以及它试图解决什么,就几乎不可能阅读一些代码。所以像:

if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200)
   doSomething();

看起来不错,但在程序实际执行的上下文中可能毫无意义。我宁愿这样:

// we do this according to the requirement #xxxx blah-blah..
if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200)
   doSomething();

【讨论】:

我同意:cmets 应该描述做了什么以及为什么。如果你需要描述它是如何完成的,那么你需要重构你的代码。 你通常可以把你的评论变成一个函数名,比如: if (starColourIsGreaterThanThreshold() doSomething(); private boolean starColourIsGreaterThanThreshold() return starColour.red > THRESHOLD && starColour.blue > THRESHOLD && starColour.green > 阈值 ***.com/questions/784250/…【参考方案8】:

在大多数情况下,是的,您可以编写足够清晰的代码,让 cmets 成为不必要的噪音。

cmets 最大的问题是无法检查其准确性。我倾向于同意 Bob Martin 叔叔在他的书的第 4 章,Clean Code

正确使用 cmets 是为了弥补我们在 代码。请注意,我使用了失败这个词。我是认真的。评论总是失败的。我们必须 拥有它们是因为我们无法始终弄清楚没有它们如何表达自己, 但它们的使用并不值得庆祝。

因此,当您发现自己处于需要写评论的位置时,请考虑一下 看看有没有办法扭转局面并表达自己 代码。每次在代码中表达自己时,都应该拍拍自己的后背。每一个 写评论的时候,你应该做个鬼脸,觉得自己能力的失败 表达。

大多数 cmets 要么是不必要的冗余,要么是彻头彻尾的谬误,要么是用来解释写得不好的代码的拐杖。我说大多数是因为在某些情况下缺乏表现力的原因在于语言而不是程序员

例如,版权和许可信息通常位于源文件的开头。据我所知,在任何流行语言中都没有已知的构造。由于简单的一两行注释就足够了,因此不太可能添加这样的结构。

随着时间的推移,对大多数 cmets 的最初需求已被更好的技术或实践所取代。使用变更日志或注释代码已被源代码控制系统所取代。可以通过简单地编写较短的函数来减轻长函数中的解释性 cmets。等等

【讨论】:

干净的代码不能像 cmets 那样表达原因。像思考版权一样思考算法选择和业务规则。总体描述通常很有用。话虽这么说,如果评论可以直接不同意代码,请删除它。 “每次你写评论,你都应该做鬼脸,感觉自己表达能力的失败。” 哎哟!在那段摘录中有如此多的自以为是的应该和内疚的绊脚石!想太多,担心每一个小细节真的会让我陷入困境。我必须找到一个平衡点。总的来说,我同意他的观点,但我希望他能用它们如何满足程序员的需求而不是这样的黑白术语来解释事情。【参考方案9】:

我喜欢“人性化”代码,所以不是:

if (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200)
   doSomething();

我会这样做的:

bool starIsBright;
starIsBright = (starColour.red > 200 && starColour.blue > 200 && starColour.green > 200);

if(starIsBright)
   doSomething();

【讨论】:

有时,特别是在与脾气暴躁的 API 作斗争之后,我也喜欢通过命名函数来表达我的感受,例如 pleaseDoSomething() :)【参考方案10】:

它可能不是 cmets,但是,为了帮助某人更好地理解它正在发生的事情,您可能需要一些图表来解释程序应该如何工作,因为如果一个人了解大局,那么它更容易理解代码。

但是,如果您正在做一些复杂的事情,那么您可能需要一些 cmets,例如,在一个数学密集型程序中。

我发现 cmets 有用和重要的另一个地方是确保有人不会用看起来应该可以工作但不会工作的东西替换代码。在这种情况下,我将错误的代码留在其中,并将其注释掉,并解释为什么不应该使用它。

因此,可以在没有 cmets 的情况下编写代码,但前提是您在编写的应用程序类型方面受到限制,除非您可以在某处解释做出决定的原因,而不是将其称为注释。

例如,随机生成器可以用多种方式编写。如果您选择特定的实现,可能有必要解释为什么选择该特定生成器,因为对于当前需求而言,这段时间可能足够长,但后来需求可能会发生变化,您的生成器可能不够用。

【讨论】:

【参考方案11】:

我不确定编写不需要 cmets 的富有表现力的代码是否一定是一个伟大的目标。在我看来,这像是另一种形式的过度优化。如果我在您的团队中,我会很高兴看到清晰、简洁的代码以及足够的 cmets。

【讨论】:

【参考方案12】:

使用描述性的变量名和描述性的方法名。使用空格。

让你的代码读起来像正常的对话。

对比Junit中Matchers的使用:

assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));

用传统风格的assertEquals:

assertEquals(3, x);

当我查看assertEquals 语句时,并不清楚哪个参数是“预期的”,哪个是“实际的”。

当我查看assertThat(x, is(3)) 时,我可以将其用英文读为“断言 x 为 3”,这对我来说非常清楚。

编写自记录代码的另一个关键是在方法调用中用清晰的名称包装任何不明确的逻辑。

if( (x < 3 || x > 17) && (y < 8 || y > 15) )

变成

if( xAndYAreValid( x, y ) )  // or similar...

【讨论】:

在 xAndYAreValidForOperationJ22 之前我一直和你在一起。为您的方法提供如此长而冗长的名称只是为了引用一些模棱两可地命名为“J22”的操作是没有意义的。像 areValidCoordinates(x,y) 或 areWithinBounds(x,y) 这样的东西更有意义。 另外,最后两段代码似乎是自记录代码的反例。当一个简单的注释可以同时显示正在执行的比较并说明作者的意图时,您不必要地混淆了一个非常简单的 if() 表达式。相反,您让读者查找另一个函数以了解发生了什么。 公平点。我试图暗示存在一些名为“J22”的业务逻辑,这对于虚构领域的其他读者来说是很清楚的。【参考方案13】:

在大多数情况下,我真的不认为 cmets 是一个好主意。编译器不会检查注释,因此随着代码的变化,它们经常会产生误导或错误。相反,我更喜欢不需要 cmets 的自我记录、简洁的方法。这是可以做到的,而且我多年来一直这样做。

在没有 cmets 的情况下编写代码需要练习和纪律,但我发现随着代码的发展,纪律会得到回报。

【讨论】:

【参考方案14】:

从头到尾阅读Code Complete, 2nd Edition。也许两次。

给出一些细节:

使代码可读 消除代码重复 在编写代码之前进行设计/架构

【讨论】:

好建议。但也许提供本书将讨论的与@pang 的问题相关的一些关键概念?【参考方案15】:

Clean CodeClean Code Robert C. Martin 包含编写干净、易于理解的代码所需的一切。

【讨论】:

【参考方案16】:

描述性名称显然是您的首选。

其次,确保每个方法只做一件事且只做一件事。如果您有一个需要做很多事情的公共方法,请将其拆分为几个私有方法并从公共方法中调用它们,以使逻辑显而易见。

前段时间我不得不创建一个计算两个时间序列相关性的方法。

要计算相关性,您还需要平均值和标准差。所以我有两种私有方法(实际上在这种情况下它们是公共的,因为它们可以用于其他目的(但假设它们不能,那么它们将是私有的))用于计算 A)平均值,B)标准偏差。

这种将函数拆分成有意义的最小部分可能是使代码可读的最重要的事情。

你如何决定在哪里分解方法。我的方式是,如果名称很明显,例如getAddressFromPage 它是正确的大小。如果您有多个竞争者,您可能会尝试做太多事情,如果您想不出一个有意义的名称,您的方法可能“做得”不够——尽管后者的可能性要小得多。

【讨论】:

【参考方案17】:

如果您想在完全不使用 cmets 的情况下编写代码并且仍然可以遵循您的代码,那么您将不得不编写大量更短的方法。方法必须具有描述性名称。变量还必须具有描述性名称。这样做的一种常见方法是给变量指定名词的名称,并为方法指定口头短语的名称。例如:

account.updateBalance();
child.givePacifier();
int count = question.getAnswerCount();

大量使用enums。使用enum,您可以替换大多数booleans 和整数常量。例如:

public void dumpStackPretty(boolean allThreads) 
    ....


public void someMethod() 
    dumpStackPretty(true);

public enum WhichThreads  All, NonDaemon, None; 
public void dumpStackPretty(WhichThreads whichThreads) 
    ....


public void someMethod() 
    dumpStackPretty(WhichThreads.All);

【讨论】:

【参考方案18】:

我认为Fluent Interfaces 的概念确实是一个很好的例子。

var bob = DB.GetCustomers().FromCountry("USA").WithName("Bob")

【讨论】:

这给你的项目增加了很多耦合。如果来自 GetCustomers 的客户更改 FromCountry(),则位于第三位的此代码也将失败。尽量减少变更的影响至关重要 @Eric,我可以看到你的论点,但许多框架都在使用这种技术,而且问题与可读性而不是耦合有关,所以我仍然认为它是一个很好的例子。【参考方案19】:

如果你真的想那么你需要非常详细的变量名和方法名。

但在我看来,没有好的方法可以做到这一点。注释在编码中起着重要的作用,即使您是唯一的编码人员,有时仍需要提醒您正在查看代码的哪一部分。

【讨论】:

我同意 - cmets 是编码的重要组成部分。【参考方案20】:

我在大学时曾经有一位教授告诉我,任何好的代码都不应该需要任何 cmets。

她的方法是将非常精确的逻辑拆分为具有非常描述性的方法/属性/变量名称的小函数。事实上,她展示的大部分内容在没有 cmets 的情况下极具可读性。我尝试对我写的所有东西做同样的事情......

【讨论】:

我希望我的更多教授是这样的。我的大部分人都评论疯了。我记得有一个要求在每个源文件的开头有一个大格式的注释部分,每个函数之前至少有一个四行格式的注释部分,并且函数中的每一行也必须有一个内联注释。每次作业后我的手指都疼。 @Kenneth 这就是我们得到像 i++ 这样的东西的地方; // 将 i 加 1 并将结果存储在 i 中。

以上是关于是否有可能在没有任何注释的情况下编写良好且易于理解的代码?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在没有 php 的情况下编写 Wordpress 主题? [关闭]

在不违反 SRP、OCP、DRY 的情况下编写测试

Kiosk 游戏在没有 AIR 的情况下编写本地文件 AS3?可能的?如果没有,本地服务器?

在没有 POW 或乘法的情况下编写指数函数

良好的注释

是否可以在不使用 main() 函数的情况下编写程序?