精华内容
下载资源
问答
  • 程序员的代码注释需要写么?
    千次阅读
    2021-11-01 15:13:15
    “别给糟糕的代码加注释——重新写吧。”—Brian W. Kernighan与P. J. Plaugher

    什么也比不上放置良好的注释来得有用。什么也不会比乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。

    注释并不像辛德勒的名单。它们并不“纯然地好”。实际上,注释最多也就是一种必须的恶。若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。

    注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注意,我用了“失败”一词。我是说真的。注释总是一种失败。我们总无法找到不用注释就能表达自我的方法,所以总要有注释,这并不值得庆贺。

    如果你发现自己需要写注释,再想想看是否有办法翻盘,用代码来表达。每次用代码表达,你都该夸奖一下自己。每次写注释,你都该做个鬼脸,感受自己在表达能力上的失败。

    我为什么要极力贬低注释?因为注释会撒谎。也不是说总是如此或有意如此,但出现得实在太频繁。注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误。原因很简单。程序员不能坚持维护注释。

    代码在变动,在演化。从这里移到那里。彼此分离、重造又合到一处。很不幸,注释并不总是随之变动——不能总是跟着走。注释常常会与其所描述的代码分隔开来,孑然飘零,越来越不准确。例如,看看以下注释以及它本来要描述的代码行变成了什么样子:

    MockRequest request;
    private final String HTTP_DATE_REGEXP = 
     "[SMTWF][a-z]{2}\\,\\s[0-9]{2}\\s[JFMASOND][a-z]{2}\\s"+
     "[0-9]{4}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\sGMT";
    private Response response;
    private FitNesseContext context;
    private FileResponder responder;
    private Locale saveLocale;
    // Example: "Tue, 02 Apr 2003 22:18:49 GMT"

    在HTTP_DATE_REGEXP常量及其注释之间,有可能插入其他实体变量。

    程序员应当负责将注释保持在可维护、有关联、精确的高度。我同意这种说法。但我更主张把力气用在写清楚代码上,直接保证无须编写注释。

    不准确的注释要比没注释坏得多。它们满口胡言。它们预期的东西永不能实现。它们设定了无需也不应再遵循的旧规则。

    真实只在一处地方有:代码。只有代码能忠实地告诉你它做的事。那是唯一真正准确的信息来源。所以,尽管有时也需要注释,我们也该多花心思尽量减少注释量。

    1 注释不能美化糟糕的代码

    写注释的常见动机之一是糟糕的代码的存在。我们编写一个模块,发现它令人困扰、乱七八糟。我们知道,它烂透了。我们告诉自己:“喔,最好写点注释!”不!最好是把代码弄干净!

    带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样得多。与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。

    2 用代码来阐述

    有时,代码本身不足以解释其行为。不幸的是,许多程序员据此以为代码很少——如果有的话——能做好解释工作。这种观点纯属错误。你愿意看到这个:

    // Check to see if the employee is eligible for full benefits 
    if ((employee.flags & HOURLY_FLAG) && 
       (employee.age > 65))

    还是这个?

    if (employee.isEligibleForFullBenefits())

    只要想上那么几秒钟,就能用代码解释你大部分的意图。很多时候,简单到只需要创建一个描述与注释所言同一事物的函数即可。

    3 好注释

    有些注释是必须的,也是有利的。来看看一些我认为值得写的注释。不过要记住,唯一真正好的注释是你想办法不去写的注释。

    3.1 法律信息

    有时,公司代码规范要求编写与法律有关的注释。例如,版权及著作权声明就是必须和有理由在每个源文件开头注释处放置的内容。

    下例是我们在FitNesse项目每个源文件开头放置的标准注释。我可以很开心地说,IDE自动卷起这些注释,这样就不会显得凌乱了。

    // Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
    // Released under the terms of the GNU General Public License version 2 or later.

    这类注释不应是合同或法典。只要有可能,就指向一份标准许可或其他外部文档,而不要把所有条款放到注释中。

    3.2 提供信息的注释

    有时,用注释来提供基本信息也有其用处。例如,以下注释解释了某个抽象方法的返回值:

    // Returns an instance of the Responder being tested.
    protected abstract Responder responderInstance();

    这类注释有时管用,但更好的方式是尽量利用函数名称传达信息。比如,在本例中,只要把函数重新命名为responderBeingTested,注释就是多余的了。

    下例稍好一些:

    // format matched kk:mm:ss EEE, MMM dd, yyyy
    Pattern timeMatcher = Pattern.compile(
     "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

    在本例中,注释说明,该正则表达式意在匹配一个经由SimpleDateFormat.format函数利用特定格式字符串格式化的时间和日期。同样,如果把这段代码移到某个转换日期和时间格式的类中,就会更好、更清晰,而注释也就变得多此一举了。

    3.3 对意图的解释

    有时,注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。在下例中,我们看到注释反映出来的一个有趣决定。在对比两个对象时,作者决定将他的类放置在比其他东西更高的位置。

    public int compareTo(Object o)
    {
     if(o instanceof WikiPagePath)
     {
      WikiPagePath p = (WikiPagePath) o;
      String compressedName = StringUtil.join(names, "");
      String compressedArgumentName = StringUtil.join(p.names, "");
      return compressedName.compareTo(compressedArgumentName);
     }
     return 1; // we are greater because we are the right type.
    }

    下面的例子甚至更好。你也许不同意程序员给这个问题提供的解决方案,但至少你知道他想干什么。

    public void testConcurrentAddWidgets() throws Exception {
     WidgetBuilder widgetBuilder = 
      new WidgetBuilder(new Class[]{BoldWidget.class});
     String text = "'''bold text'''";
     ParentWidget parent = 
      new BoldWidget(new MockWidgetRoot(), "'''bold text'''");
     AtomicBoolean failFlag = new AtomicBoolean();
     failFlag.set(false);
    
     //This is our best attempt to get a race condition 
     //by creating large number of threads.
     for (int i = 0; i < 25000; i++) {
      WidgetBuilderThread widgetBuilderThread = 
       new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
      Thread thread = new Thread(widgetBuilderThread);
      thread.start();
     }
     assertEquals(false, failFlag.get());
    }

    3.4 阐释

    有时,注释把某些晦涩难明的参数或返回值的意义翻译为某种可读形式,也会是有用的。通常,更好的方法是尽量让参数或返回值自身就足够清楚;但如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助阐释其含义的代码就会有用。

    public void testCompareTo() throws Exception
    {
     WikiPagePath a = PathParser.parse("PageA");
     WikiPagePath ab = PathParser.parse("PageA.PageB");
     WikiPagePath b = PathParser.parse("PageB");
     WikiPagePath aa = PathParser.parse("PageA.PageA");
     WikiPagePath bb = PathParser.parse("PageB.PageB");
     WikiPagePath ba = PathParser.parse("PageB.PageA");
    
     assertTrue(a.compareTo(a) == 0);  // a == a
     assertTrue(a.compareTo(b) != 0);  // a != b
     assertTrue(ab.compareTo(ab) == 0); // ab == ab
     assertTrue(a.compareTo(b) == -1);  // a < b
     assertTrue(aa.compareTo(ab) == -1); // aa < ab
     assertTrue(ba.compareTo(bb) == -1); // ba < bb
     assertTrue(b.compareTo(a) == 1);  // b > a
     assertTrue(ab.compareTo(aa) == 1); // ab > aa
     assertTrue(bb.compareTo(ba) == 1); // bb > ba
    }

    当然,这也会冒阐释性注释本身就不正确的风险。回头看看上例,你会发现想要确认注释的正确性有多难。这一方面说明了阐释有多必要,另外也说明了它有风险。所以,在写这类注释之前,考虑一下是否还有更好的办法,然后再加倍小心地确认注释正确性。

    3.5 警示

    有时,用于警告其他程序员会出现某种后果的注释也是有用的。例如,下面的注释解释了为什么要关闭某个特定的测试用例:

    // Don't run unless you
    // have some time to kill. 
    public void _testWithReallyBigFile()
    {
     writeLinesToFile(10000000);
    
     response.setBody(testFile);
     response.readyToSend(this);
     String responseString = output.toString();
     assertSubString("Content-Length: 1000000000", responseString);
     assertTrue(bytesSent > 1000000000);
    }

    当然,如今我们多数会利用附上恰当解释性字符串的@Ignore属性来关闭测试用例。比如@Ignore("Takes too long to run[2]")。但在JUnit4之前的日子里,惯常的做法是在方法名前面加上下划线。如果注释足够有说服力,就会很有用了。

    这里有个更麻烦的例子:

    public static SimpleDateFormat makeStandardHttpDateFormat()
    {
     //SimpleDateFormat is not thread safe, 
     //so we need to create each instance independently.
     SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
     df.setTimeZone(TimeZone.getTimeZone("GMT"));
     return df;
    }

    你也许会抱怨说,还会有更好的解决方法。我大概会同意。不过上面的注释绝对有道理存在,它能阻止某位急切的程序员以效率之名使用静态初始器。

    3.6 TODO注释

    有时,有理由用//TODO形式在源代码中放置要做的工作列表。在下例中,TODO注释解释了为什么该函数的实现部分无所作为,将来应该是怎样。

    //TODO-MdM these are not needed
    // We expect this to go away when we do the checkout model
    protected VersionInfo makeVersion() throws Exception
    {
    return null;
    }

    TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。它可能是要提醒删除某个不必要的特性,或者要求他人注意某个问题。它可能是恳请别人取个好名字,或者提示对依赖于某个计划事件的修改。无论TODO的目的如何,它都不是在系统中留下糟糕的代码的借口。

    如今,大多数好IDE都提供了特别的手段来定位所有TODO注释,这些注释看来丢不了。你不会愿意代码因为TODO的存在而变成一堆垃圾,所以要定期查看,删除不再需要的。

    3.7 放大

    注释可以用来放大某种看来不合理之物的重要性。

    String listItemContent = match.group(3).trim();
    // the trim is real important. It removes the starting 
    // spaces that could cause the item to be recognized
    // as another list.
    new ListItemWidget(this, listItemContent, this.level + 1);
    return buildList(text.substring(match.end()));

    3.8 公共API中的Javadoc

    没有什么比被良好描述的公共API更有用和令人满意的了。标准Java库中的Javadoc就是一例。没有它们,写Java程序就会变得很难。

    如果你在编写公共API,就该为它编写良好的Javadoc。不过要记住本章中的其他建议。就像其他注释一样,Javadoc也可能误导、不适用或者提供错误信息。

    4 坏注释

    大多数注释都属此类。通常,坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员自说自话。

    推荐你阅读《代码整洁之道》,第4章中有详细讲解。

    本书大致可分为3个部分。前几章介绍编写整洁代码的原则、模式和实践。这部分有相当多的示例代码,读起来颇具挑战性。读完这几章,就为阅读第2部分做好了准备。如果你就此止步,只能祝你好运啦!

    第2部分最需要花工夫。这部分包括几个复杂性不断增加的案例研究。每个案例都清理一些代码——把有问题的代码转化为问题少一些的代码。这部分极为详细。你的思维要在讲解和代码段之间跳来跳去。你得分析和理解那些代码,琢磨每次修改的来龙去脉。

    你付出的劳动将在第3部分得到回报。这部分只有一章,列出从上述案例研究中得到的启示和灵感。在遍览和清理案例中的代码时,我们把每个操作理由记录为一种启示或灵感。我们尝试去理解自己对阅读和修改代码的反应,尽力了解为什么会有这样的感受、为什么会如此行事。结果得到了一套描述在编写、阅读、清理代码时思维方式的知识库。

    如果你在阅读第2部分的案例研究时没有好好用功,那么这套知识库对你来说可能所值无几。在这些案例研究中,每次修改都仔细注明了相关启示的标号。这些标号用方括号标出,如:[H22]。由此你可以看到这些启示在何种环境下被应用和编写。启示本身不值钱,启示与案例研究中清理代码的具体决策之间的关系才有价值。

    如果你跳过案例研究部分,只阅读了第1部分和第3部分,那就不过是又看了一本关于写出好软件的“感觉不错”的书。但如果你肯花时间琢磨那些案例,亦步亦趋——站在作者的角度,迫使自己以作者的思维路径考虑问题,就能更深刻地理解这些原则、模式、实践和启示。这样的话,就像一个熟练地掌握了骑车的技术后,自行车就如同其身体的延伸部分那样;对你来说,本书所介绍的整洁代码的原则、模式、实践和启示就成为了本身具有的技艺,而不再是“感觉不错”的知识。

    更多相关内容
  • 代码进行分析,获取代码量,注释量,注释率等信息;方便审核代码,支持java php python等多门语言。
  • 代码注释

    千次阅读 热门讨论 2018-04-01 01:24:25
    只要写代码,就会遇到代码注释的问题。在不同的公司,不同的项目组,不同的项目中,可能会有不同的注释标准。有些标准让我们感觉很受益,有些则让我们感觉很反感。而对于没有明确标准的项目,我们往往会遇到...

            只要写代码,就会遇到代码注释的问题。在不同的公司,不同的项目组,不同的项目中,可能会有不同的注释标准。有些标准让我们感觉很受益,有些则让我们感觉很反感。而对于没有明确标准的项目,我们往往会遇到“百花齐放,百家争鸣”般的注释。我无法给出一个明确的标准,只是在此探讨下:什么样的注释不应该写,什么地方需要写注释。(转载请指明出于breaksoftware的csdn博客)

    “不”的原则

    不是每行代码都需要写注释

            这个原则源于之前我和同事的一个争论。当时我们讨论代码注释该怎么写的问题,最终同事抛出这么一个观点:“我之前在X为干过,那儿就需要每行代码都写注释,所以我们应该执行这样的标准”。想必大家都可以猜到那是一家什么公司,但是从我个人角度分析,同事之前可能去的是一家假的“X为”公司。因为我觉得那个公司不应该如此没有技术品味吧?

            之后的探讨,我们将以画作为例。因为画是一种艺术表达,而我们的代码也应该写的和艺术品一样,表达出一种美。下图是梵高的《向日葵》原作,图中只有若干向日葵花、花瓶、地面(或者说是承载体)等元素。

            假如我们对这份“代码”每行加一个注释,则呈现如下

            各位看官感觉如何?我觉得如果梵高画的如果不像向日葵的话,这样搞点“注释”可能还有存在的意义。但是如果作品足够表意,那么再加上注释就是画蛇添足。这种为写注释而写注释是非常不可取的。为什么呢?因为

     

    • 增加编码人员无价值的工作量
    • 让编码人员降低对自己代码质量的要求,因为“反正要写注释”,叫index还是叫xpoesiejd无所谓。
    • 让对自己编码有艺术美感的编码人员产生抵触。比如我们要是让梵高给他原作加上上图一样的注释,我想梵高可能早就不想活了。

            但是我相信我同事可能说的是“真”的,但是这个“真”被我打了一个引号。因为我怀疑他之前可能在一家给X为干活的外包公司工作。也许X为的确有严格的代码注释量要求(也许“注释行数”/“代码行数”>0.5),于是这家外包公司就做了一个“任何一行代码都要写注释”的要求。我想如果他们真的这么去执行了,代码的注释量的确是上去了,但是或许注释的质量降低了,或者工作效率降低了。

    不要写废话

            上图是欧仁·德拉克罗瓦的著名油画《自由引导人民》。这幅作品是为了法国七月革命而作,最前方的那个女性是克拉拉·莱辛,象征着自由女神。她左手边男孩象征着阿莱尔,右手边戴大礼帽的男性象征着资产阶级,贝雷帽男性则象征着工人。可以见得这幅画包含了大量的背景知识。但是如果我们用废话注释它就是

            是不是要抓狂?对于这样显而易见的信息写注释就是“废话”!

    不要写错误的注释

            上图是徐悲鸿的著名油画《田横五百士》。我们抛开这幅画的时代意义,单从历史画角度来看,大师给我们传达了一个错误的信息——人物的着装不符合故事地域年代(秦末齐国地区)。如果该幅画没有留下名字,后人通过画作的衣着信息(可以看成是一种注释)对该画所表达的故事进行推理时,可能出现断代错误,从而得出的故事和作者想表达的不同。

    不要乱留名

            

            这幅是元代赵孟頫的《水村图卷》。请注意一下右上角那个大大的、红红的、方方正正的印章——乾隆御笔之宝。可能你会觉得乾隆爷给这幅画加盖了自己的印章,会导致该幅画价值大大增加,但是实际效果是恰恰相反。乾隆爷是有名的毁画大师,他经常在一些有名的作品上胡乱加盖自己的印章——留名。于是这些画作经他这么一折腾就会贬值。连乾隆爷的名字都那么不值钱,我们何必要在代码中乱留名呢?

            但是如果这份文件是你编写的,你还是要在版权信息中写入你的名字。这就和中国古人画作一般都会自己签名和盖自己印章一样。

            可能你会辩解,说:我添加的这段逻辑非常重要,我要留名以便以后有人能找到我。我觉得这个问题应该这么看:如果你的代码重要程度可以颠覆原作,我觉得你可以考虑重写一份并署上自己名字;如果没那么重要还是把留名交给原作者吧,毕竟你的成果是在他基础之上获得呢。

    不要注释掉代码

            我想这个问题是最最常见的。大家翻翻自己项目的代码,可能都会遇到这种现象。当我们不需要一段代码时,可能也会估计是“永远不用了”还是“暂时不用了”。于是很多人对“暂时不用了”的代码使用注释方式使其无效。但是一般来说,这种“暂时不用了”的代码很有可能就是“永远不用了”,而人们已经忘记要去删除它了。这个现象在实际项目中比比皆是。所以个人觉得:不要的代码要果断删除。即使以后真的要用了,我们可以使用代码管理工具(svn或者git)方便的找回。

            下图是库尔贝的油画《受伤的男人》。其实作者在呈现这幅画之前,画布上画了一个女性头部。可能作者觉得这块逻辑可能没用了,于是就用黑色把“她”抹去,从而也成就了这幅名画。假想作者如果使用“注释”的手段将这颗头颅保留在画作中,将是如何吓人的一幅场景。

    不要写小故事

            在工作中,可能某个类的设计思路来源于和产品经理或者技术经理的思想碰撞,可能这个过程很精彩,有些编码者就将整个故事用注释的方式放在代码中。也有可能这个类和历史上一个著名人物有关,比如我们要编写斐波拉契数列实现,就将斐波拉契平生故事放到代码里。我觉得这些内容放在代码中是非常不合适的,也许你觉得很精彩,但是别人很有可能不关心啊。

            比如下图是米开朗琪罗的《耶稣受难》

            现在我们觉得耶稣受难前“最后的晚餐”故事很精彩,于是我们把这段《新约圣经》“注释”到画作中

            米开朗琪罗如果这么作画,可能现在我对他的认知中少了“画家”这一项。

    不要有宗教倾向

            这个问题在国内不算太大的问题。因为程序员大部分是无神论者。当我们看到“佛祖保佑”或者“God save me”是往往是莞尔一笑。但是我们不能假设以后阅读这份代码的人没有宗教信仰,也许他信奉的和你正好相反呢。代码是没有宗教的,编码者是有的。

            我们比较常见是的“佛祖保佑 永无bug”

            个人比较反感这种做法。所以我会在自己的项目中,将这种注释都删掉。假如我们的代码要依靠神来保佑,我只能质疑你代码的质量是不是已经超神了。我们还是要信奉二进制的。

    不要博人一笑

            代码是严谨的逻辑表达,不要写一些逗人开心的内容。可能上例中“佛祖保佑,永无bug”在一些编码者看来只是为了博人一笑。但是我觉得阅读代码是一件需要连续动脑筋的事,如果我们阅读过程被这些“笑话”不停打断,那么最终的理解效率得有多低?

            比如著名的《清明上河图》,它包含了大量的社会信息。假如我每隔一段注释一个笑脸,那么这幅画可能就没那么高的艺术价值了。

    不要批判前人

            写出完美的代码的确是非常稀少的。所以我们阅读别人代码时,往往可以发现很多问题。在感觉不爽时,可能心中默默鄙视一下作者;再不爽时,可能和同事数落一下作者;再再不爽时,可能就要在代码中批判一下作者。但是这种注释对代码是有意义的么?而且并不是我们总是对的。由于对问题的理解深度、角度不同,编码者就是可能会写出让你不满意但是符合他自己原则的代码。

            比如梵高的《星空》

            像我这种鉴赏能力比较差的,的确很难说出他画的哪儿好了。假如我给这幅画做了如下注释

            我想我并不会获得别人的赞许,可能全是对我的鄙视。那怎么办?我觉得我应该去画一幅符合我心目中“星空”的油画,这样供其他人在我和梵高中做个选择。或许这是一种自不量力,但是这却是一种对原作者的尊重。

    “要”的原则

            说了这么多“不”的原则,其实我知道我还是没有说全。但是我们换个角度,如果我们知道“要”的原则,然后对其取反,就是涵盖所有“不”的原则了。

            在讨论这个话题之前,我先说下我对代码和注释的认识。

            首先我认为代码要写的和注释一样表意。也就是说我们要穷尽自己的思想,努力掌控每个名词、每个动词、每个换行、每个空格、每个组织形式以达到让代码可以自说明逻辑及业务。

            其次代码和注释应该是一个整体。往往一份文件包含代码和注释两部分,而阅读这份文件也有两个主体——编译器和人。编译器只是通过代码来获得逻辑信息,而人的要通过代码和注释一起理解逻辑和业务。

            基于第二点,我认为一份文件最好只有一个故事线——只需要用代码去表达,因为它是人和编译器同时可以去理解的。如果一个故事经过两个人去讲述,随着时间推移,最终会变成两个故事。这是非常可怕的。

            所以我的观点是:如果代码足够表意,且不违背常理,不应该去添加注释。反之则需要加注释。

            但是现实社会中,有时候很难做到不违背常理。因为我们这个世界随机性太强,有时候我们就要放弃一些我们认识的“常理”去达到期望的目的。这个时候我们代码的表达能力可能就是不足的了,就需要注释来表达。

            比如齐白石的画作中,寿桃往往是用来祝寿的

            这个常理我们中国人都可以理解。但是假如我们看到下面这幅画,你觉得他是齐白石用来干嘛的?

            它其实也是用于祝寿的。它是齐白石向蒋公祝60大寿时送的,和这幅画一起送的还有一副对联——人生长寿,天下太平。完整的版本是

            假如觉得这副对联还不够表达“潜台词”,则上联左上侧“主席寿”几个字则可以完全说明了吧。

            这幅《松柏高立图》在2011年以4.255亿被拍卖,而它则是我认为需要写“注释”的一个典型代表。

    展开全文
  • 在有处理逻辑的代码中,源程序有效注释量必须在20%以上。 二、代码注释分类 行注释:在符号后那一行不会被编译(显示) 块注释:被块注释符号中间的部分不会被编译 三、python代码注释基础 Python中使用#表示...
  • nginx带注释代码,对于学习服务器底层开发大有裨益!
  • 教你写好代码注释

    千次阅读 2019-12-08 21:35:31
    这时候,代码注释就可以发挥它的作用了——提高晦涩难懂的代码的可读性;注释可以起到隐藏代码复杂细节的作用,比如接口注释可以帮助开发者在没有阅读代码的情况下快速了解该接口的功能和用法;如果写的好,注释还...

    前言

    相信大家都会遇到这种情况:一周前自己写的代码,现在再拿出来看,发现读不懂了,“ 这代码是我写的???”。这时候,代码注释就可以发挥它的作用了——提高晦涩难懂的代码的可读性;注释可以起到隐藏代码复杂细节的作用,比如接口注释可以帮助开发者在没有阅读代码的情况下快速了解该接口的功能和用法;如果写的好,注释还可以改善系统的设计

    既然注释这么多好处,为什么我们程序员还是不愿意写注释?

    “代码都写不完了,哪有时间写注释,以后再补吧“

    时间不够论。这是最常见的原因,在交付速度飞快的今天,“代码写不完”是一个再常见不过的情况了,但写注释真的会导致需求延迟吗?绝不!相对于写一个接口的实现,写接口注释的时间可能只需要花费前者的5%。但不写注释,后面使用接口的人必须要多花费**50%**的时间去读懂代码!而且对于大部分程序员而言,“以后再补“大概要到2910年才能落实。

    “好的代码就是最好的注释,我的代码可读性很好,没必要写注释”

    好代码胜过注释论。不少程序员认为,好的代码就是最好的注释,只要代码可读性好,注释就可以省去。然而一个软件系统很多信息是无法通过代码呈现出来的,比如系统的设计思路、函数执行的预置条件等等。此外,代码可读性也不是绝对的,对于一个没有使用过Java 8 的 Lambda表达式的开发者而言,通篇的箭头“->“简直就是一场噩梦。

    ”过期的注释容易误导人“

    过期注释论。不可否认,过期的注释很容易误导读者,但是这并不能成为否认注释的借口。除非是重大的重构或重写,对注释进行大改动的情况很少出现。通常,在更改代码之后,只需花费极少的时间去更新注释,就可以避免过期注释这种情况了。

    注释的分类

    注释大致可以分成四类:接口注释、数据成员注释、实现注释和模块依赖注释。

    接口注释

    平时我们所说的接口,通常指的是一个类(包括interface、class、enum)和方法。对类而言,接口注释主要描述该类提供的功能;对方法而言,除了描述方法功能之外,方法的入参和返回值都要进行说明。当然,使用类/方法的一些预置条件和副作用等信息都需要在接口注释中提到。

    典型的接口注释(选自JDK 1.8中的String类)

    数据成员注释

    数据成员注释和接口注释在大多数情况下都是必须的,这对于让读者快速读懂代码有很大的帮助。数据成员包括类的普通成员变量和静态成员变量,数据成员注释除了描述数据成员的本身用途之外,成员的默认值、副作用等信息都需要提及。

    典型的数据成员注释(选自JDK 1.8中的String类)

    实现注释

    对于一段代码,如果无法一眼就看出其含义而需要深入分析其实现才能读懂,就需要添加实现注释对这段代码的含义进行说明。代码的实现注释通常不是必须的,如果一段代码每一行都需要注释,那就要重新审视一下这段代码的设计是否有合理了。此外,实现注释描述还需要描述一些代码无法体现,但是对于读者了解这段代码很有帮助的信息,比如代码的设计思路等。

    典型的实现注释(选自JDK 1.8中的String类)

    模块依赖注释

    模块依赖注释相对少见一些,主要描述两个相互依赖的模块的一些依赖信息,因为它并不属于单独某个模块,所以在哪里写这个注释需要认真衡量一下。实在找不到好的位置时,可以写一个类似README的文件,专门用于描述模块之间的依赖关系。

    典型的模块依赖注释(选自《A Philosophy of Software Design》中的例子)

    如何写好代码注释

    利用好注释模板

    注释模板为注释写作提供了极大的便利,我们常用的开发工具如IDEA、VS Code都对注释模板有很好的支持。在配置好注释模板之后(一般情况下默认模板即可),只需简单的操作,就能生成模板,我们只需往模板里填上内容即可。对于方法的接口注释,注释模板尤为方便,方法的入参和返回值标识注释模板都已经提供好。

    IntelliJ IDEA默认的注释模板

    不要重复代码

    注释与代码重复是开发者最容易犯的一个错误,这也是很多开发者认为注释是冗余的原因。确实,对于那些通过读代码可以很容易就推断出来的信息,注释就是多余的,更甚者,出现过期注释还容易误导读者。

    典型的重复代码的注释(选自《A Philosophy of Software Design》中的例子)

    某些程序员喜欢在注释中使用代码中的变量名或其中的单词,这种情况也是注释重复代码的一种体现。像下面的这个例子,注释完全就是冗余的,可以猜测开发者纯粹是为了注释而注释。

    使用代码变量名的注释

    注释也要分层

    在进行系统设计时,我们常常会采用分层架构,每一层负责不同功能。系统的顶层往往会更抽象一点,为功能调用者隐藏了很多细节(high-level);底层往往会更细节一点,实现系统的具体功能(low-level)。在进行注释写作时,我们也要学会对注释进行分层。high-level的注释要提供比代码更抽象的信息,比如代码的设计思路;low-level的注释要提供比代码更细节的信息,比如表示一个范围的两个参数是左闭右开还是左闭右闭;我们需要避免写出与代码同一level的注释,因为这往往就是上一节所提到的注释重复代码

    low-level注释写作指南

    对于需要通过阅读这一段代码才能获取的信息,就适合以low-level注释写在这段代码前面,一些常见的例子有:

    • 变量的单位是什么?
    • 表示范围的一组参数是左闭右开还是左闭右闭?
    • 某个变量为null时代表什么含义?
    • 某个资源应该由谁来负责释放,接口调用者还是接口提供者?

    low-level注释不能太过抽象,否则就失去了其应有的作用。

    太过抽象的low-level(选自《A Philosophy of Software Design》中的例子)

    典型的low-level注释(选自《A Philosophy of Software Design》中的例子)

    high-level注释写作指南

    high-level注释的作用是隐藏具体实现细节,快速让读者对整一段代码有一个全局的了解。high-level注释除了描述对代码进行抽象的概括(What)之外,还经常描述代码的设计思路(Why),这有助于帮助读者更容易、深入地了解整个系统。high-level注释不应该描述具体代码实现的信息,更不能犯注释重复代码的错误。在写high-level注释之前,先问一下自己:

    • 这段代码要做什么事情?
    • 这段代码最核心的功能是什么?
    • 怎样才能以最简单的描述表达这段代码的核心功能?

    太过细节的high-level注释(选自《A Philosophy of Software Design》中的例子)

    典型的high-level注释(选自《A Philosophy of Software Design》中的例子)

    规范接口注释

    接口注释是用的最多的一种注释,它可以让读者快速了解整个模块的功能并知道如何使用该接口,而不必花费大量时间去阅读源码。

    对于一个的接口注释,其通常是high-level的注释,主要描述这个类提供的一些功能;

    对于一个方法的接口注释,则同时需要包括high-level和low-level的注释,常见的有以下几点:

    • 整个方法提供的主要功能
    • 方法的所有参数和返回值含义
    • 调用该方法的副作用(比如改变了系统的某个状态)
    • 该方法可能抛出的所有异常
    • 调用该方法的预置条件(比如调用该方法前,系统必须处于某个状态)

    实现注释描述what、why,而不是how

    写实现注释时需要记住的最重要的一点就是,描述代码是做什么的(what)和为什么这么做(why),而不是描述怎么做(how)。因为实现代码本身就是how,如果还添加描述how的注释,那就犯了“注释重复代码”的错误了。

    典型的描写how的实现注释(选自《A Philosophy of Software Design》中的例子)

    典型的描写what的实现注释(选自《A Philosophy of Software Design》中的例子)

    实现注释通常不是必须的,对于一些简短的、功能单一的函数,一个好的函数命名即可替代实现注释。

    总结

    注释是软件开发过程中的性价比极高的一个工具,它只需要花费20%的时间,即可获取80%的价值。代码注释最重要的作用就是让读者可以在不读源码的情况下,快速了解一段代码的主要功能。注释的作用还远远不止这些,John Ousterhout在《A Philosophy of Software Design》一书中提到,写注释最好的时机是在写代码之前。这样,注释就相当于一种设计工具:在编码前通过注释描述这段代码的功能,然后在通过编码实现这个功能,如果这个过程有偏差,则再调整注释。写注释时要切记两点,不要重复代码更新代码后也要更新注释!前者可以减少冗余的注释,后者则可以避免因为过期的注释而误导读者。

    当然,注释也不是越多越好,如果一个系统写的注释太多,很有可能就是该系统设计得过于复杂,只能通过注释来帮助读者理解整个系统。

    总而言之,学会怎么写好代码注释,是一个程序员的必备技能,这即是为了开发团队的其他成员,也是为了未来的自己。

    展开全文
  • 对YOLOV3的核心代码进行了注释,注释量大约上万行,有兴趣的小伙伴可以拿去研究研究或者继续注释
  • Java代码注释

    千次阅读 2015-05-27 17:21:38
    曾经我对“一份好的代码注释至少要占到一半的份量”这样话深信不疑,我也不厌其烦的给每一个函数都加上javadoc,对此,我深感自豪;而对于别人写代码不加注释的“坏习惯”,我深表遗憾。然而当我读完Robert的...

    曾经我对“一份好的代码里注释至少要占到一半的份量”这样话深信不疑,我也不厌其烦的给每一个函数都加上javadoc,对此,我深感自豪;而对于别人写代码不加注释的“坏习惯”,我深表遗憾。然而当我读完Robert的“注释”一节,我已经懊恼不已,并且我已经开始对我的代码进行审核,再次优化。我已经开始遵守“别给糟糕的代码加注释–重新写吧”这条准则。

    也许你是一个好人,会对代码进行不断的优化改进,然而你经常会把注释忽略掉,就如同下面这样:

    /**
      * 集合竞价.
      *
      * @param orderFromClient
      * @return
      */
     private Message callAuction(SelfOrder orderFromClient, long b, JadeInfo jadeInfo) {

    方法参数已经改变了,然而javadoc的注释中依然只有一个参数。

    我个人已经深深地被Robert影响了,代码才能最忠诚的告诉它在做什么,而不是注释。但是我们很多人,包括在看这本书之前的我,认为写得一手好注释的程序员才是好程序员,当然这不包含那些只会写糟糕代码的人。

    用代码来阐述

    我们来比较一下这两种代码:

    public void pause() {
    
      if (isNotAction()) {
       return;
      }
    }
    
    /**
      * 节假日或当天无交易商品,服务器不执行操作
      */
     private boolean isNotAction() {
    if (isHoliday || !hasTradingJade) {
       return true;
      }
    
      return false;
     }
    public void pause() {
    // 节假日或当天无交易商品,服务器不执行操作
    if (isHoliday || !hasTradingJade) {
       return ;
      }
    }

    我认为第一种更好,因为节假日以及无交易商品,就说明服务器当日不需要运行,那么用isNotAction来表明会更好。

    好注释

    对意图进行解释

    /* 
      * 按照用户id对资金变化量进行排序,从而保证在多线程同时更新资金字段时,不发生死锁
      */
     @Override
     public int compareTo(VarialMoneyUser o) {
      return this.getUid().compareTo(o.getUid());
     }

    这样的注释,我认为还不错,说明了使用compare的意图。

    TODO注释
    有的时候,我们的需要做一些事情,而我们暂时没有做,那么就可以使用TODO,这样IDE就会管理到这些TODO,当然还有FIXME标记,表明我们有些问题暂时不知道怎么优化、改善。

    trade.setDjzj(BigDecimal.valueOf(0));// FIXME 冻结资金待实现
    // TODO 计算账户的盈亏,根据风险控制等级,标示次日需要提醒、限制交易、强制平仓的账户

    但是需要注意,定期的回头检查,eclipse提供以下图片的功能,你要删除那些无用的TODO

    这里写图片描述

    坏注释

    喃喃自语

    public void dailyUpdateSystemData() {
      // 每日更新时进行一次会员信息更新
      AllMembercoes.init();

    以上的注释毫无必要。

    多余的注释

    /**
      * @Description: 获取指定日期的行情日报
      */
     public QuotationDailyReport getQuotationReportByDateAndScode(QuotationDailyReport report);
    
     /**
      * @Title: addQuotationReport
      * @Description: 添加行情日报
      * @param tradeReport
      * @return
      */
     public int addQuotationReport(QuotationDailyReport tradeReport);
    
     /**
      * @Title: getLastQuotationReport
      * @Description: 获取指定商品上一次的行情日报信息
      * @param map
      * @return
      */
     @SuppressWarnings("rawtypes")
     public QuotationDailyReport getLastQuotationReport(Map map);

    这些注释比没有注释还可怕,其实看方法名称,都知道要做什么,加上注释后反倒浪费时间。

    ps:我之前非常喜欢加这种注释,我恨不得在每一个方法上面加上注释,但是自从我明白了,方法名本身就应该代表了方法要做什么以后,我深恶痛绝自己以前荒唐的行为

    误导性注释

    这个非常的可怕,很多时候,注释和代码表达的意思完全相反,或者牛头不对马嘴,总之很容易让人迷惑。

    // 12点后设置isreload为true,重新加载配置,
     private void updateReloadStatusTrue() {

    这个注释在本意上应该是非常友好的,提示这个方法是在12点以后执行的,但是我上下文翻看,压根找不到任何12点的信息。

    这样会好一点

    /**
      * 设置isreload为true,表明配置服务、商品服务可以重新加载了
      */
     private void updateReloadStatusTrue() {
      reload = true;
      ConfigService.isReload = reload;
      JadeInfoService.isReload = reload;
     }

    循规蹈矩的注释

    哦,这种注释多发生在方法上,我曾经就非常热衷于这样的注释,现在我才知道其可怕之处。

    /**
      * @Title: updateQuotation
      * @Description: 修改指定商品的行情信息
      * @param quotation
      */
     public void updateQuotation(Quotation quotation) {
      this.quotationMapper.updateQuotation(quotation);
     }

    title、param的注释有两种坏处

    1. 非常多余,难道方法本身不能告诉我吗?
    2. 阻碍重构,当方法名、参数名需要重构或者添加参数时注释是不会紧随变更的。

    这里写图片描述

    日志式注释

    以前我们特别喜欢在代码里加上以下这样注释

    // start update by maweiqing
          // 如果能够接收到消息,说明客户端已经进行操作了,那么就更新session的时间
          data = CryptUtil.encrypt(data, SessionManager.getSession(getSession().getSessionId())
            .getEncryptKey());
    
          // end update by maweiqing 2015-05-27

    看到Robert的建议后,我真的才恍然大悟,这样的代码还要SVN的代码管理器干嘛?SVN在提交的时候自然会让你输入你的修改理由、以及自动显示署名、日期的。

    废话注释

    // 标识列
     private Integer id;
     // 用户id
     private Integer userId;
     // 用户名
     private String userName;

    这样的注释完全没有必要,你认为有吗?

    /**
      * @return the id
      */
     public Integer getId() {
      return id;
     }
    
     /**
      * @param id
      *            the id to set
      */
     public void setId(Integer id) {
      this.id = id;
     }

    还有利用IDE生成的这种bean注释,我真后悔自己当初为什么会这样想,加上这样的注释让自己显得专业吗,显然适得其反。

    位置标记

    虽然我暂时没有在自己的代码中找到,但是之前我这样做过。

    //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    //aaaaaaaaaaaaaaaaaaaa

    注释掉的代码

    哦,显然我又中招了,之前我特别喜欢把那些所谓还有作用的代码留在代码里,及时压根就是错误的。但是自从我看到Jeff的博客后,我就开始删掉了那些注释掉的代码,今天再看到Robert的文章,感觉这些伟大的程序员他们都会有这样相似的真理。

    如果你的代码中还有这样的注释,请尽快删掉吧,危害太大。

    总结:我觉得Robert的文章越来越深入的影响着我,让我改变了很多陋习。

    展开全文
  • C++ 去除代码注释

    千次阅读 2021-04-17 17:52:31
    输入一段代码,去除其中的注释和空行 支持去除以下形式的注释: //这是一条注释 /*这是一条注释*/ /*这是 一条 注释*/ #include<iostream> #include<fstream> #include<cstring> #...
  • java的三种代码注释方式

    千次阅读 2021-11-26 22:17:37
    抖音等那种中大型程序,没有了代码注释,对于日后程序运行维护将是很大困难。我们可以在平时编码过程中养成规范代码注释习惯,也是我们成为优秀程序员的原因 2.java中的注释分类 Java中的注释分为以下三种:单行注释...
  • 以下选项中,Python语言中代码注释使用的符号是: ( )答:#中国古代舞蹈灿烂辉煌,但在理论研究方面却相对薄弱,这种情况直到明清都无显著改变。答:错误Photoshop中下列工具中不可以定义新的画笔的是答:魔棒工具 ...
  • 强大的代码行统计工具,可以统计不同类别的代码的行数、注释行数、注释率、空行数量等信息。
  • SourceCounter统计源程序代码量,绿色版,轻级,下载后即可用。可统计内容:代码行数、注释行数、空行数、总行数、单元测试用例数、测试缺陷数、总文件数、总人月数等。
  • 多少注释才算适当?
  • JavaScript代码注释范例

    千次阅读 2018-06-10 21:23:43
    #JavaScript代码注释范例&gt; 做为一个有情怀的Coder,最近收集了一下JavaScript代码注释范例,希望能够帮助大家撸得一手妖媚而又放荡的Bug。## 普通注释### 单行注释**使用 ```//``` 作为单行注释。****单行...
  • SVO代码注释

    2018-09-28 18:36:42
    因为代码注释的工作都是在makrdown下完成的。如果要将所有的内容再po到这块,工作有点大。所以给出如下链接,可以在链接中查看SVO 代码笔记 https://www.zybuluo.com/zhuqingzhang/note/1188358 有理解不对的...
  • 【vs code / idea】快速去除代码注释

    千次阅读 2021-11-10 20:05:38
    【vs code / idea】 快速去除js代码注释一、前言二、删除单行注释 ( // )三、删除多行注释(块注释 /**/)四、同时删除单行注释和多行注释五、删除多余的空白行最后 一、前言 随着代码量的增加,我们的注释量也会...
  • Python代码注释的一些基础知识

    万次阅读 多人点赞 2019-07-16 19:11:39
    使用注释是增加代码可读性的另一个方便简单且重要的方法! 我们将介绍编写Python注释的一些基础知识。您将学习如何优雅地编写干净、简洁的注释,以及了解何时您可能根本不需要编写任何注释。 #为什么注释代码...
  • 代码注释的重要性

    千次阅读 2018-06-01 20:24:05
    养成代码注释的重要意义。首先,不愿意注释主要是因为懒惰,被懒惰束缚,为了赶工,甚至是对自己编程的信心。习惯是很可怕的一件事,习惯的力量很难被改变。很多人从开始编写学习编写程序时都是从几行代码开始的,...
  • 代码规范】常见注释规范

    万次阅读 多人点赞 2018-06-21 15:10:50
    1.在有处理逻辑的代码中,源程序有效注释量必须在20%以上。说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。 2.文件注释:文件注释写入...
  • 产stm8智能车载空气净化器的完整设计(原理图+PCB+源代码注释清晰) 智能车载空气净化器介绍: 这是一款把车厢变成绿色森林的空气净化器。该智能车载净化器采用STM8S003F3(STM8S003F3数据手册)作为主控制芯片,...
  • java代码注释规范

    千次阅读 2018-03-20 22:05:50
    一、规范存在的意义 ...2、好的编码规范可以改善软件的可读性,可以让开发人员尽快而彻底地理解新的代码; 3、好的编码规范可以最大限度的提高团队开发的合作效率; 4、长期的规范性编码还可以让开发人...
  • java注释量统计代码实现

    千次阅读 2015-09-07 11:19:37
    由于项目需要统计代码注释量,故寻此代码。 由于我们需要统计的代码中,注释有多种形式,需要过滤出别人以前写的注释,只统计我们新写的注释。 旧的注释采用的注释方式有三种: // 注释1 /* * 注释2 */ /* 注释3 ...
  • 这样工作可谓庞大,今天给大家带来关于注释的快捷键使用,一起来看看吧~关于python编程注释快捷键1、注释单行(1)方法1:直接在单行代码前边加 #(2)方法2:选中需要注释代码,Ctrl+/ 即可注释2、注释多行代码选中...
  • 刚找到一个代码注释统计的软件(带源码). 希望你们喜欢.
  • Python语句中用于注释代码的符号是

    千次阅读 2021-01-12 10:36:01
    单产为(零A生复杂,不足普通中存在的机床解决机床加工点和一系了在列缺数控。削(响较)精对车度影大,符号表面表面端面内孔外圆螺纹螺距,向窜主轴动时车床有轴数控。...语句于注项目主要建工抵押的建地使产开程用...
  • latex添加代码注释Stop me if you’ve heard this one before… 如果您之前听过我的话,请阻止我... “Good code is self-documenting.” “好的代码是自我记录。” In 20+ years of writing code for a living, ...
  • Statistic是idea上面的一个用于统计代码数量,注释数量的工具。 1.安装 重启idea后,工具就能直接使用,如过没有结果的话,刷新下: 扩展设置
  • 24位高精度ad cs5532 程序代码 stm32 已调试可用(含大量注释

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 413,569
精华内容 165,427
关键字:

代码注释量多少