《代码整洁之道》阅读笔记 4注释

542 阅读10分钟

"Comments Do Not Make Up for Bad Code."(注释不是对劣质代码的补救)。事实上好的代码即便没有注释也拥有良好的可读性,但恰当的注释会让代码变得更可读、可维护性更高。 在这里插入图片描述

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

1.==带有少量注释的整洁而有表达力的代码==,要比带有大量注释的零碎而复杂的代码像样得多。

2.==与其花时间编写解释你搞出的糟糕的代码的注释==,不如花时间清洁那堆糟糕的代码

2.用代码来阐述

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

// check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) {
    // do something
}

改进

if (employee.isEligibleForFullBenefits()) {
    // do something
}

3.好注释

当然,有些注释是必须的,也是有利的。

1.法律信息:公司代码规范要求编写与法律有关的注释,例如 ==版权和著作申明==。


2.提供信息的注释

// 格式化匹配的 kk.mm.ss EEE, MMM dd, yyyy Pattern timeMatcher = Pattern.compile("\d*:\d*:\d* \w*, \w* \d*, \d*" );


3.对意图的解释: 有时注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图

/**
     * generate signature by params and secret, used for computing signature for http request.
     *
     * @param params request params
     * @param secret auth secret
     * @return secret sign
     * @throws Exception  exception
     */
    public static String generateSignature(Map<String, Object> params, String secret) throws Exception {
        final StringBuilder paramStr = new StringBuilder();
        final Map<String, Object> sortedMap = new TreeMap<>(params);
        for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
            paramStr.append(entry.getKey());
            paramStr.append(entry.getValue());
        }

        Mac hmac = Mac.getInstance("HmacSHA1");
        SecretKeySpec sec = new SecretKeySpec(secret.getBytes(), "HmacSHA1");
        hmac.init(sec);
        byte[] digest = hmac.doFinal(paramStr.toString().getBytes());

        return new String(new Hex().encode(digest), "UTF-8");
    }
  • 注释一定是表达代码之外的东西,==代码可以包含的内容,注释中一定不要出现==
  • 如果有必要注释,请注释意图(==why==),而不要去注释实现(==how==),大家都会看代码

4.阐释:有时注释把某种晦涩难明的参数或返回值的意义翻译为某种可读形式。

====特别是参数或者返回值是某个标准库的一部分====,或者你不能修改代码,那帮助阐释其含义的代码就会有用,例如:

assertTrue(bb.compareTo(ba)==1);//bb>aa
assertTrue(a.compareTo(b)==-1);//a<b

直接看方法可能不明确,但有注释就明白多了。


5.警示:警告会出现某种后果。

private BaseResult putReadyFlag(BillingDataContext context, Integer readyFlag) {
    // warn! oms data format require List<Map<String,String>> and the size of it must be one.
    List<Map<String, String>> dataList = Lists.newArrayListWithExpectedSize(1);
}

该方法==创建了一个大小固定为1且类型为Map<String,String>的数组链表== ,这个用法比较奇怪,需要注释说明原因。

代码里偶尔出现一些非常hack的逻辑且修改会引起较高风险,这个时候需要加注释重点说明

下面的注释解释了为什么要关闭某个特定的测试用例:

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

当然,现在我们多数会利用符合恰当解释性字符串的@Ignore属性来关闭测试用例。比如当

@Ignore(“Takes too long to run ”)

6.TODO。注意要==清理==

有时,有理由用//TODO形式在源码中放置要做的工作列表。

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


7.放大:有的代码可能看着有点多余,但编码者当时是有他自己的考虑,这个时候需要注释下这个代码的重要性。避免后面被优化掉。


8.公共API中的Javadoc

编写公共API就该为它编写良好的标准的java doc说明。不过,就像其他注释一样,javadoc也可能误导、不适用或者提供错误信息

4.坏注释

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

4.1 喃喃自语

如果你觉得应该或者因为过程需要就添加注释,那就是无谓之举。注释应该是给读者看得不是给自己看得。

4.2 多余的注释

反例

/**
 * 类EcsOperateLogDO.java的实现描述:TODO 类实现描述
 * 
 * @author xxx 2012-12-6 上午10:53:21
 */public class DemoDO implements Serializable {
    private static final long serialVersionUID = -3517141301031994021L;
    /**
     * 主键id
     */
    private Long  id;
    /**
     * 用户uid
     */
    private Long  aliUid;
    /**
     * @return the id
     */
    public Long getId() {
        return id;
    }
    /**
     * @param id the id to set
     */
    public void setId(Long id) {
        this.id = id;
    }
    /**
     * @return the aliUid
     */
    public Long getAliUid() {
        return aliUid;
    }
    /**
     * @param aliUid the aliUid to set
     */
    public void setAliUid(Long aliUid) {
        this.aliUid = aliUid;
    }
}

分析上述代码可以发现两处注释非常别扭和多余:

  • 类注释使用了默认模版, 填充了无效信息
  • IDE为Getter及Setter方法生成了大量的无效注释

正例

/**
 * Demo model.
 * @author xxx 2012-12-6 上午10:53:21
 */public class DemoDO implements Serializable {
    private static final long serialVersionUID = -3517141301031994021L;
    /**
     * 主键id
     */
    private Long   id;
    /**
     * 用户uid
     */
    private Long   aliUid;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Long getAliUid() {
        return aliUid;
    }
    public void setAliUid(Long aliUid) {
        this.aliUid = aliUid;
    }
}
  • 不要保留任何循规蹈矩式注释,比如IDE自动生成的冗余注释

  • 不要产生任何该类注释,可以统一配置IDE达到该效果,推荐使用灵狐插件

4.3 误导性注释

代码返回 false ,注释则始终返回 true 。

/***
 * Always returns true.
 */
public boolean isAvailable() {
    return false;
}

4.4 循规式注释

例如,要求每个函数都要写javadoc,就会得到很多原本无需写注释的注释。

4.5 日志式注释

==在每个模块开始添加每一次修改的注释性日志已经没有必要==,现在很多代码维护工具提供更好的修改记录;

/**
 * @author
 * @date 2020-09-10
 * @param cardType
 * 会员卡消耗
 *
 * 2020-09-11 添加支持次卡消费 add by zhangwei
 * 2020-09-12 添加支持年卡消费 add by zhangwei2
 */
public void membershipConsume(CardType cardType){

}

4.6 废话注释

比如private int dayofMonth ; 注释为 the day of the month ;

4.7 可怕的废话

javadoc也可能是废话,

4.8 能用函数或变量时就别用注释

==注释本身是为了解释未能自行解释的代码,如果注释本身还需要解释==,那就毫无作用。

4.9 位置标记:

少用这种无理,鸡零狗碎的注释。 // Actions ///////////////////////////

4.10 括号后面的注释

尽管这对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱,如果你发现自己想标记右括号,其实更应该做的是缩短函数。 在这里插入图片描述

4.11 归属与署名

源代码控制系统非常善于记住是谁在何时添加了什么恶。没必要用那些小小的签名搞脏代码。你也许会认为, 这种注释大概有助于他人了解应该和谁讨论这段代码。不过,事实却是 ==注释放在那儿一年又一年,越来越不准确,越来越和原作者没关系==

4.12 注释掉的代码

有些已经不用的程序,依然被注释在哪里,有的人可能回想代码留在哪里也许会有用。但是我们现在有源代码控制系统,无需再用注释来标记,删掉即可

 private Object buildParamMap(Object request) throws Exception {
        if (List.class.isAssignableFrom(request.getClass())) {
            List<Object> input = (List<Object>)request;
            List<Object> result = new ArrayList<Object>();
            for (Object obj : input) {
                result.add(buildParamMap(obj));
            }
            return result;
        }

        Map<String, Object> result = new LinkedHashMap<String, Object>();
        Field[] fields = FieldUtils.getAllFields(request.getClass());
        for (Field field : fields) {
            if (IGNORE_FIELD_LIST.contains(field.getName())) {
                continue;
            }

            String fieldAnnotationName = field.getAnnotation(ProxyParam.class) != null ? field.getAnnotation(
                ProxyParam.class).paramName() : HttpParamUtil.convertParamName(field.getName());

            //Object paramValue = FieldUtils.readField(field, request, true);
            //if (paramValue == null) {
            //    continue;
            //}
            //
            //if (BASIC_TYPE_LIST.contains(field.getGenericType().getTypeName())) {
            //    result.put(fieldAnnotationName, String.valueOf(paramValue));
            //} else {
            //    result.put(fieldAnnotationName, this.buildParamMap(paramValue));
            //}

        }
        return result;
    }

4.13 HTML注释

源代码注释中的HTML标记是一种厌物,如果注释将由某种工具抽取出来,呈现到网页,那么该是工具而非程序员来负责给注释加上合适的HTML标签。

/**
 * used for indicate the field will be used as a http param, the http request methods include as follows:
 * <li>Get</li>
 * <li>Post</li>
 * <li>Connect</li>
 *
 * the proxy param will be parsed, see {@link ProxyParamBuilder}.
 *
 * @date 2017/12/08
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ProxyParam {

    /**
     * the value indicate the proxy app name, such as houyi.
     *
     * @return proxy app name
     */
    String proxyApp() default "houyi";

    /**
     * proxy request mapping http param.
     *
     * @return http param
     */
    String paramName();

    /**
     * the value indicate if the param is required.
     *
     * @return if this param is required
     */
    boolean isRequired() default true;
}

正例

/**
 * used for indicate the field will be used as a http param.
 *
 *
 * @date 2017/12/08
 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ProxyParam {

    /**
     * the value indicate the proxy app name, such as houyi.
     *
     * @return proxy app name
     */
    String proxyApp() default "houyi";

    /**
     * proxy request mapping http param.
     *
     * @return http param
     */
    String paramName();

    /**
     * the value indicate if the param is required.
     *
     * @return if this param is required
     */
    boolean isRequired() default true;
}

4.14 非本地信息

==注释一定要描述离它最近的代码== 反例

//说明
//该方法有一行代码从map里删除了一个数据,注释放在了put调用之前,而没有直接放在remove之前

private Map<String, String> buildInstanceDocumentMap(String version, String instanceId) {
        Map<String, String> instanceDocumentMap = Maps.newLinkedHashMap();

        Map<String, String> instanceDocumentMapMetadataPart = metaDataService.getInstanceDocument(instanceId, version,
            instanceDocumentMetaKeys);
        instanceDocumentMap.putAll(instanceDocumentMapMetadataPart);
        //the map must remove the old key for instance type
        instanceDocumentMap.put("instance-type", instanceDocumentMap.get("instance/instance-type"));
        instanceDocumentMap.remove("instance/instance-type");

        return instanceDocumentMap;
    }

正例

private Map<String, String> buildInstanceDocumentMap(String version, String instanceId) {
        Map<String, String> instanceDocumentMap = Maps.newLinkedHashMap();

        Map<String, String> instanceDocumentMapMetadataPart = metaDataService.getInstanceDocument(instanceId, version,
            instanceDocumentMetaKeys);
        instanceDocumentMap.putAll(instanceDocumentMapMetadataPart);
        instanceDocumentMap.put("instance-type", instanceDocumentMap.get("instance/instance-type"));
        //the map must remove the old key for instance type
        instanceDocumentMap.remove("instance/instance-type");

        return instanceDocumentMap;
    }

4.15 信息过多

在这里插入图片描述

4.16 不明显的联系

注释及其描述的代码之间的联系应该==显而易见==。如果你不嫌麻烦要写注释,至少让读者能看着注释和代码,并且理解注释所谈何物

4.17 函数头

短函数不需要太多描述。==为只做一件事的短函数选个好名字,通常要比写函数头注释要好==

4.18 非公共代码中的Javadoc

虽然javadoc对于公共API非常有用,但对于不打算作公共用途的代码就令人厌恶了。为系统中的类和函数生成javadoc页面并非总有用,而javadoc注释额外的形式要求机会等同于==八股文章==。

4.19 范例

4.20 补充

目前IDE安装 灵狐 后会自动配置IDE的file templates为如下格式:

文件/类注释规范

/**
 * @author ${USER}
 * @date ${YEAR}/${MONTH}/${DAY}
 */

__强烈建议使用如上配置,统一、简洁就是最好。

方法注释

/**  
  * xxx
  * 
  * @param 
  * @param 
  * @return 
  * @throws 
  */

举个例子:

/**
     * Converts a <code>byte[]</code> to a String using the specified character encoding.
     *
     * @param bytes
     *            the byte array to read from
     * @param charsetName
     *            the encoding to use, if null then use the platform default
     * @return a new String
     * @throws UnsupportedEncodingException
     *             If the named charset is not supported
     * @throws NullPointerException
     *             if the input is null
     * @deprecated use {@link StringUtils#toEncodedString(byte[], Charset)} instead of String constants in your code
     * @since 3.1
     */
    @Deprecated
    public static String toString(final byte[] bytes, final String charsetName) throws UnsupportedEncodingException {
        return charsetName != null ? new String(bytes, charsetName) : new String(bytes, Charset.defaultCharset());
    }

块注释与行注释 单行代码注释使用行注释 // 多行代码注释使用块注释 /* */

4.21 参考文献

《代码整洁之道》

developer.aliyun.com/article/603…

zhuanlan.zhihu.com/p/267001652

www.jianshu.com/p/c44eca6ad…

blog.csdn.net/c_lanxiaofa…

关注公众号“程序员面试之道”

回复“面试”获取面试一整套大礼包!!!

本公众号分享自己从程序员小白到经历春招秋招斩获10几个offer的面试笔试经验,其中包括【Java】、【操作系统】、【计算机网络】、【设计模式】、【数据结构与算法】、【大厂面经】、【数据库】期待你加入!!!

1.计算机网络----三次握手四次挥手

2.梦想成真-----项目自我介绍

3.你们要的设计模式来了

4.震惊!来看《这份程序员面试手册》!!!

5.一字一句教你面试“个人简介”

6.接近30场面试分享