如何写好java注释

2022/09/01 编程技巧 共 5189 字,约 15 分钟

什么是java注释

要回答这个问题,先提出一个说法:我们写的代码是给人看的,不是给机器看的. 如果你不认可这个说法,下面关于代码注释的一些总结想必对你也没啥帮助. 如果你认可这个说法, 那么下面的文章希望会给你一些帮助.

回到本段的主题,什么是java注释呢?

  1. 首先,注释是面向代码维护者的: 注释是对代码逻辑的一段说明, 方便后续的代码维护者能够快速的了解这段代码的含义,并依次为基础能够在现有代码的基础上做修改与维护.

  2. 其次,注释是面向系统使用者的: 要知道类似于java,或者spring这样偏向于底层,框架类的代码.全世界有数以万计的使用者的,这些人没精力也没有必要在调用api的时候去了解这段代码的底层实现逻辑,所以这段代码的api说明文档就显得很重要了.而代码上的注释就是相关api文档的主要来源.

  3. 通过javadoc命令,可以将注释抽离生成html文档,比如官方的java16API文档

如何写好java注释

想要写好代码的注释,我们要从注释的面向用户来考虑.不同的用户关注点还是有点差异的.

面向代码维护者

作为一名代码的维护者,当我们拿到一份没有任何注释的代码的时候,相信大家内心的感受是相同的.毕竟人类的悲欢并不相通, 除非看到没有注释的代码.

如果有了解设计模式的朋友,相信一定听说过一个设计原则:开闭原则, 对扩展开发,对修改关闭. 那么大家有没有想过为啥会有这个原则呢?

翻译一下: 修改现有的代码是有风险的,但是如果是扩展写新代码的话,作为开发者的你,是了解当前功能的前后背景的,而且你无论怎么写都不会对历史功能产生影响.所以对我们的代码设计能力提出来要求.

再翻译一下: 原来的代码既然跑的好好的,为啥要修改它呢, 改出问题来算谁的?

再翻译一下: 为啥我们不敢改以前的代码呢?谁知道当时为啥要写这段逻辑,谁知道这段逻辑有谁在用?改好了没有夸, 改崩了有锅背.

这就是我们维护老代码的困境!

作为一名程序员,我们要有一个概念,我们的代码是我们某个时刻的思维逻辑的固化. 如果是一个比较简单的逻辑,注释可以简单写写. 如果是一段比较复杂的逻辑,那么我们的注释要能够描述清楚我们当时的这段逻辑的背景(为什么要写这段逻辑), 意图(这段逻辑要实现什么效果), 用法(这段逻辑改如何使用,以及谁在用), 最好能有修改建议.

在业界开源的代码里有很多比较好的例子,来个spring的:

/**
 * AOP Alliance MethodInterceptor for declarative transaction
 * management using the common Spring transaction infrastructure
 * ({@link org.springframework.transaction.PlatformTransactionManager}/
 * {@link org.springframework.transaction.ReactiveTransactionManager}).
 *
 * <p>Derives from the {@link TransactionAspectSupport} class which
 * contains the integration with Spring's underlying transaction API.
 * TransactionInterceptor simply calls the relevant superclass methods
 * such as {@link #invokeWithinTransaction} in the correct order.
 *
 * <p>TransactionInterceptors are thread-safe.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see TransactionProxyFactoryBean
 * @see org.springframework.aop.framework.ProxyFactoryBean
 * @see org.springframework.aop.framework.ProxyFactory
 */

面向api使用者

对于api的使用者,注释文档的要求相对简单些.

他们只关心这个方法的功能是什么, 以及入参有哪些,出参会有哪些, 会不会抛异常等, 会不会返回null等. 他们不关心这个方法的内部实现逻辑是啥, 比如一个排序方法, 使用者不关心这个方法内部使用冒泡排序,还是快排, 只要能完成诉求就行.

所以,面向api的使用者的注释,原则就是让他知道这个方法怎么使用就行了.

/**
* Returns an Image object that can then be painted on the screen. 
* The url argument must specify an absolute <a href="#{@link}">{@link URL}</a>. The name
* argument is a specifier that is relative to the url argument. 
* <p>
* This method always returns immediately, whether or not the 
* image exists. When this applet attempts to draw the image on
* the screen, the data will be loaded. The graphics primitives 
* that draw the image will incrementally paint on the screen. 
*
* @param  url  an absolute URL giving the base location of the image
* @param  name the location of the image, relative to the url argument
* @return      the image at the specified URL
* @see         Image
*/
public Image getImage(URL url, String name) {
try {
return getImage(new URL(url, name));
} catch (MalformedURLException e) {
return null;
}
}

写好java注释常用的技巧

常用的注释tag

  1. @param 方法参数
  2. @return 方法返回值
  3. @throws 方法抛出的异常, java1.2后添加
  4. @exception 同@throws
  5. @see 查看参考代码
  6. @author 标识作者
  7. @version 代码版本
  8. @since 从某个版本开始引入
  9. @serial( @serialField @serialData)
  10. @deprecated 废弃某个版本的代码

常用的内联tag

  1. {@code} : 代码高亮,等同于 {@literal}
    <code>{@literal}</code>
    
  2. {@docRoot} : 标记文档的根路径,用来实现相对路径
    /**
    * See the <ahref="{@docRoot}/copyright.html">Copyright</a>.
    */
    
  3. {@inheritDoc} : 继承某个文档,嵌套文档使用
  4. {@link url} : 在注释文本区域内, 内联一个链接 ``` Use the {@link #getComponentAt(int, int) getComponentAt} method.
5. {@linkplain url label} 相对于{@link}, 支持自定义label文案来代指这段url

Refer to {@linkplain add() the overridden method}. 会显示为 Refer to ‘the overridden method’.

6. {@literal} 转义用, 方便显示一些特殊字符
7. {@value} 常量时, 会直接显示标注代码的值

### 注释语句中的小技巧

#### @param @return @throws
一个正常方法必然会用到的注释tag,
@param 代表方法的入参,
@return 代表方法的返回值.
@throws 代表可能引发方法中断的异常. 等同与 @exception
这里需要特别说明一下, 有些人可能觉得只有那些受检异常(也就是必须在方法签名里声明的异常)才需要在注释里声明@throws. 其实不是的. 所有引发程序中断的异常, 包括运行时异常都可以在注释里说明, 也可以在方法签名里添加. 尤其是自定义的异常.
举个例子:
```java
	/**
	 * Return the underlying ThreadPoolExecutor for native access.
	 * @return the underlying ThreadPoolExecutor (never {@code null})
	 * @throws IllegalStateException if the ThreadPoolTaskExecutor hasn't been initialized yet
	 */
	public ThreadPoolExecutor getThreadPoolExecutor() throws IllegalStateException {
		Assert.state(this.threadPoolExecutor != null, "ThreadPoolTaskExecutor not initialized");
		return this.threadPoolExecutor;
	}

这里的IllegalStateException 是一个RuntimeException异常, 我们在方法里可能抛出这个异常, 最好是在方法签名里声明一下, 然后在代码注释里说明一下.

@see

当前代码可以参考的其他代码, 后面跟代码的全路径,可以是类,方法,属性等.具体参考如下:

@see #field
@see #Constructor(Type, Type...)
@see #Constructor(Type id, Type id...)
@see #method(Type, Type,...)
@see #method(Type id, Type, id...)
@see Class
@see Class#field
@see Class#Constructor(Type, Type...)
@see Class#Constructor(Type id, Type id)
@see Class#method(Type, Type,...)
@see Class#method(Type id, Type id,...)
@see package.Class
@see package.Class#field
@see package.Class#Constructor(Type, Type...)
@see package.Class#Constructor(Type id, Type id)
@see package.Class#method(Type, Type,...)
@see package.Class#method(Type id, Type, id)
@see package

@author

标识当前代码的作者是谁, 可以一个,也可以有多个.

@version @since

都是版本相关的tag @version 标识当前版本好, 编译的时候会用到. 符合SCCS规范. @since 标识这段代码的引入版本

@serial @serialField @serialData

序列化相关的属性, 标识哪些字段可以序列化,哪些不行.

@deprecated

废弃某段代码,表示不再维护,并在一段时间后会被删除. 标记后, 相关的引用位置会被标记为删除线. 个人认为这个tag还是很有用的:

  1. 创建代码容易删除难, 这个tag能够很好的帮忙我们去下线不再维护的代码,减轻维护压力.(愿世界上屎山越来越少)
  2. 方便我们代码升级. 如果我们要升级一段代码,先将老代码废弃,然后通过@see,引导用户使用新的方法.
/**
* @deprecated  As of JDK 1.1, replaced by 
*               
 setBounds
* @see #setBounds(int,int,int,int)
*/

html标签来排版

<p> : 新起一段
<br> : 换行

代码块样式

想在JAVA的注释中添加一段代码,并且可以优雅的编译出来还是挺麻烦的.下面提供一种方法

   /**
     * 下面的代码注释可以按代码格式编译
     * <pre class=code>
     *     citys : [
     *         beijing,
     *         shanghai
     *      ]
     * </pre>
     *
     */
    private List<String>> citys;

参考

官方文档 javadoc - The Java API Documentation Generator How and When To Deprecate APIs

文档信息

Search

    Table of Contents