/** * 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"); }
/** * <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 {}
通常每个文件或类都应该有一个全局注释来概述该类的作用
/** * * @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); }
/** * <p>Checks if CharSequence contains a search character, handling {@code null}. * This method uses {@link String#indexOf(int)} if possible.</p> * * <p>A {@code null} or empty ("") CharSequence will return {@code false}.</p> * * <pre> * StringUtils.contains(null, *) = false * StringUtils.contains("", *) = false * StringUtils.contains("abc", 'a') = true * StringUtils.contains("abc", 'z') = false * </pre> * * @param seq the CharSequence to check, may be null * @param searchChar the character to find * @return true if the CharSequence contains the search character, * false if not or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int) */ public static boolean contains(final CharSequence seq, final int searchChar) { if (isEmpty(seq)) { return false; } return CharSequenceUtils.indexOf(seq, searchChar, 0) >= 0; }
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; }
该方法有一行代码从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); 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; }
注释要放在距离其描述代码最近的位置
/** * 根据hash过后的id生成指定长度的随机字符串, 且长度不能超过16个字符 * * @param len length of string * @param id id * @return String */ public static String randomStringWithId(int len, long id) { if (len < 1 || len > 32) { throw new UnsupportedOperationException("can't support to generate 1-32 length random string"); } //use default random seed StringBuffer sb = new StringBuffer(); long genid = id; for (int i = 0; i < len; i++) { long pos = genid%32 ; genid = genid>>6; sb.append(RANDOM_CHAR[(int) pos]); } return sb.toString(); }
注释中说明生成随机字符串的长度不能超过16字符,实际代码已经修改为32个字符,此处注释会产生误导读者的副作用
/** * 根据hash过后的id生成指定长度的随机字符串 * * @param len length of string * @param id id * @return String */ public static String randomStringWithId(int len, long id) { if (len < 1 || len > 32) { throw new UnsupportedOperationException("can't support to generate 1-32 length random string"); } //use default random seed StringBuffer sb = new StringBuffer(); long genid = id; for (int i = 0; i < len; i++) { long pos = genid%32 ; genid = genid>>6; sb.append(RANDOM_CHAR[(int) pos]); } return sb.toString(); }
/** * define common constants for ebs common component. * * Author: yzq Date: 16/7/12 Time: 17:44 */ public final class CommonConstants { /** * keep singleton */ private CommonConstants() {} public static final String BILLING_BID = "26842"; public static final int BILLING_DOMAIN_INTEGRITY_VALID = 1; public static final int BILLING_READYFLAG_START = 0; }
/** * define common constants for ebs common component. * * Author: yzq Date: 16/7/12 Time: 17:44 */ public final class CommonConstants { /** * keep singleton */ private CommonConstants() {} /** * oms client bid. */ public static final String BILLING_BID = "26842"; /** * oms billing domain integrity true. */ public static final int BILLING_DOMAIN_INTEGRITY_VALID = 1; /** * oms billing readyflag start. */ public static final int BILLING_READYFLAG_START = 0; }
public static String randomStringWithId(int len, long id) { // TODO: 2018/6/11 需要将len的合法范围抽象 if (len < 1 || len > 32) { throw new UnsupportedOperationException("can't support to generate 1-32 length random string"); } //use default random seed StringBuffer sb = new StringBuffer(); long genid = id; for (int i = 0; i < len; i++) { long pos = genid%32 ; genid = genid>>6; sb.append(RANDOM_CHAR[(int) pos]); } return sb.toString(); }
可以扩展IDE修改标记的配置,比如加入解决人,关联缺陷等信息,以IDEA为例修改入口如下:
image.png
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>
的数组链表,这个用法比较奇怪,需要注释说明原因
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; }
常见套路,为了方便需要的时候重新复用废弃代码,直接注释掉。
同上,删除注释部分代码
/** * 类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; } }
分析上述代码可以发现两处注释非常别扭和多余:
/** * 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; } }
/** 支持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; }
抛弃“拐杖注释”,不要给不好的名字加注释,一个好的名字比好的注释更重要
/** * 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; }
目前IDE安装 灵狐 后会自动配置IDE的file templates为如下格式:
/** * @author ${USER} * @date ${YEAR}/${MONTH}/${DAY} */
强烈建议使用如上配置,统一、简洁就是最好。如果有特殊需要需要定制类注释可以参考下图:
image.png
/** * 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()); }
#!/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
# 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
常用注释风格
/**/ 通常用于包注释, 作为一个整体提供此包的对应信息,每个包都应该包含一个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
常用/**/与//,用法基本同Java。
只支持 # ,每个文件都包含一个顶层注释,用于阐述版权及概要信息。
待完善
本文先总结了注释在编程中的最佳实践场景并举例进行了说明,然后就不同编程语言提供了一些注释模版及规范相关的实践tips。关于注释我个人的认知是:注释即代码,注释即文档,写好注释一个工程师必备的素质之一,在整洁代码前提下,更少的注释是更高的追求。关于注释的实践暂时写到这里,后面会持续完善,也欢迎大家提供好的tips,文中代码大多出自于日常业务项目,也有部分摘自开源库,若有不妥之处敬请指正。