快捷搜索:  汽车  科技

代码整洁之道在线阅读(代码整洁之道优雅注释之道)

代码整洁之道在线阅读(代码整洁之道优雅注释之道)/** * <p> * Helpers for {@code java.lang.System}. * </p> * <p> * If a system property cannot be read due to security restrictions the corresponding field in this class will be set * to {@code null} and a message will be written to {@code System.err}. * </p> * <p> * #ThreadSafe# * </p> * * @since 1.0 * @version $Id: SystemUtils.java 1583482 2014-03-

一、Best Practice

注释应该声明代码的高层次意图,而非明显的细节

反例

/** * generate signature by code the algorithm is as follows: * 1.sort the http params if you use Java you can easily use treeMap data structure * 2.join the param k-v * 3.use hmac-sha1 encrypt the specified string * * @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"); }

说明

上文方法用于根据参数生成签名,注释中详细描述了签名算法的实现步骤,这其实就是过度描述代码明显细节

正例

/** * 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),大家都会看代码

在文件/类级别使用全局注释来解释所有部分如何工作

正例

/** * <p> * Helpers for {@code java.lang.System}. * </p> * <p> * If a system property cannot be read due to security restrictions the corresponding field in this class will be set * to {@code null} and a message will be written to {@code System.err}. * </p> * <p> * #ThreadSafe# * </p> * * @since 1.0 * @version $Id: SystemUtils.java 1583482 2014-03-31 22:54:57Z niallp $ */ public class SystemUtils {}

总结

通常每个文件或类都应该有一个全局注释来概述该类的作用

公共api需要添加注释,其它代码谨慎使用注释

反例

/** * * @author yzq * @date 2017 */ public interface KeyPairService { PlainResult<KeyPairInfoModel> createKeyPair(KeyPairCreateParam createParam); }

说明

以上接口提供dubbo rpc服务属于公共api,以二方包的方式提供给调用方,虽然代码简单缺少了接口概要描述及方法注释等基本信息。

正例

/** * dubbo service: key pair rpc service api. * * @author yzq * @date 2017/02/22 */ public interface KeyPairService { /** * create key pair info. * * @param createParam key pair create param * @return BaseResult */ PlainResult<KeyPairInfoModel> createKeyPair(KeyPairCreateParam createParam); }

总结

总结

  • 巧用TODO、FIXME、HACK等注解标识代码
  • 及时处理所有标识代码,忌滥用

适当添加警示注释

正例

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的逻辑且修改会引起较高风险,这个时候需要加注释重点说明

注释掉的代码

反例

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

说明

常见套路,为了方便需要的时候重新复用废弃代码,直接注释掉。

正例

同上,删除注释部分代码

总结

不要在代码保留任何注释掉的代码,版本管理软件如Git可以做的事情不要放到代码里

循规蹈矩式注释

反例

/** * 类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达到该效果,推荐使用灵狐插件

日志式注释

反例

/** 支持xxx code by xxx 2015/10/11 */ String countryCode = param.getCountyCode(); if(StringUtils.isNotBlank(countryCode) && !"CN".equals(countryCode)){ imageOrderParam.setCountyCode(param.getCountyCode()); imageOrderParam.setCurrency(param.getCurrency()); }

说明

修改已有代码很多人会手动添加注释说明修改日期,修改人及修改说明等信息,这些信息大多是冗余的

正例

代码同上,删除该注释

总结

不要在代码中加入代码的著作信息,版本管理可以完成的事情不要做在代码里

“拐杖注释”

反例

/** * update config map if the config map is not exist create it then put the specified key and value then return it * @param key config key * @param value config value * @return config map */ public Map<String String> updateConfigWithSpecifiedKV(final String key final String value) { if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) { return Maps.newHashMap(); } Map<String String> config = queryConfigMap(); if (MapUtils.isEmpty(config)) { return new HashMap<String String>() {{ put(key value); }}; } config.put(key value); return config; }

说明

示例代码简单实现了更新指定map k-v等功能,如果目标map不存在则使用指定k-v初始化一个map并返回,方法名为 updateConfigWithSpecifiedKV ,为了说明方法的完整意图,注释描述了方法的实现逻辑

正例

/** * create or update config map with specified k-v. * * @param value config value * @return config map */ public Map<String String> createOrUpdateConfigWithSpecifiedKV(final String key final String value) { if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) { return Maps.newHashMap(); } Map<String String> config = queryConfigMap(); if (MapUtils.isEmpty(config)) { return new HashMap<String String>() {{ put(key value); }}; } config.put(key value); return config; }

总结

抛弃“拐杖注释”,不要给不好的名字加注释,一个好的名字比好的注释更重要

过度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}. * * @author yzq * @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; }

说明

类注释使用了大量的html标签用来描述,实际效果并没有带来收益反而增加阅读难度

正例

/** * used for indicate the field will be used as a http param. * * @author yzq * @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; }

总结

  • 普通业务注释谨慎使用html标签,它不会给你带来明显收益,只会徒增阅读难度
  • 如果是公共api且用于生成javadoc可以考虑加入必要的html标签,比如链接,锚点等
二、编程语言注释实践

Java

文件/类注释规范

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

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

__强烈建议使用如上配置,统一、简洁就是最好。__如果有特殊需要需要定制类注释可以参考下图:

代码整洁之道在线阅读(代码整洁之道优雅注释之道)(1)

方法注释

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

IDE提供了统一的方法注释模版,无需手动配置,好的方法注释应该包括以下内容:

  • 方法的描述,重点描述该方法用来做什么,有必要可以加一个输入输出的例子
  • 参数描述
  • 返回值描述
  • 异常描述

举个例子:

/** * 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()); }

块注释与行注释

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

Python

文件注释

  • 重点描述文件的作用及使用方式

#!/usr/bin/python # -*- coding: UTF-8 -*- """ bazaar script collection. init_resource_entry used for init bazaar resource such as vpc vsw sg proxy ecs and so on. user manual: 1. modify ecs.conf config your key secret and region. 2. run bazaar_tools.py script this process will last a few minutes,then it will generate a init.sql file. 3. use idb4 submit your ddl changes. """

类注释

""" ecs sdk client used for xxx. Attributes: client: access_key: access_secret: region: """

  • 类应该在其定义下有一个用于描述该类的文档字符串
  • 类公共属性应该加以描述

函数注释

def fetch_bigtable_rows(big_table keys other_silly_variable=None): """Fetches rows from a Bigtable. Retrieves rows pertaining to the given keys from the Table instance represented by big_table. Silly things may happen if other_silly_variable is not None. Args: big_table: An open Bigtable Table instance. keys: A sequence of strings representing the key of each table row to fetch. other_silly_variable: Another optional variable that has a much longer name than the other args and which does nothing. Returns: A dict mapping keys to the corresponding table row data fetched. Each row is represented as a tuple of strings. For example: {'Serak': ('Rigel VII' 'Preparer') 'Zim': ('Irk' 'Invader') 'Lrrr': ('Omicron Persei 8' 'Emperor')} If a key from the keys argument is missing from the dictionary then that row was not found in the table. Raises: IOError: An error occurred accessing the bigtable.Table object. """ pass

  • Args:列出每个参数的名字 并在名字后使用一个冒号和一个空格 分隔对该参数的描述.如果描述太长超过了单行80字符 使用2或者4个空格的悬挂缩进(与文件其他部分保持一致). 描述应该包括所需的类型和含义. 如果一个函数接受*foo(可变长度参数列表)或者**bar (任意关键字参数) 应该详细列出*foo和**bar.
  • Returns: 描述返回值的类型和语义. 如果函数返回None 这一部分可以省略
  • Raises:列出与接口有关的所有异常

多行注释与行尾注释

# We use a weighted dictionary search to find out where i is in # the array. We extrapolate position based on the largest num # in the array and the array size and then do binary search to # get the exact number. if i & (i-1) == 0: # true iff i is a power of 2

  • 复杂操作多行注释描述
  • 比较晦涩的代码使用行尾注释

Golang

行注释

常用注释风格

包注释

/**/ 通常用于包注释 作为一个整体提供此包的对应信息,每个包都应该包含一个doc.go用于描述其信息。

/* ecs OpenApi demo,use aliyun ecs sdk manage ecs this package will provide you function list as follows: DescribeInstances query your account ecs. CreateInstance create a ecs vm with specified params. */ package ecsproxy

JavaScript

常用/**/与//,用法基本同Java。

Shell

只支持 # ,每个文件都包含一个顶层注释,用于阐述版权及概要信息。

其它

待完善

小结

本文先总结了注释在编程中的最佳实践场景并举例进行了说明,然后就不同编程语言提供了一些注释模版及规范相关的实践tips。关于注释我个人的认知是:注释即代码,注释即文档,写好注释一个工程师必备的素质之一,在整洁代码前提下,更少的注释是跟高的追求。关于注释的实践暂时写到这里,后面会持续完善,也欢迎大家提供好的tips,文中代码大多出自于日常业务项目,也有部分摘自开源库,若有不妥之处敬请指正。

猜您喜欢: