[如何写优雅的代码]写好代码注释
注释存在的意义
当我们需要写注释时,就必须认识到自己在做一种妥协,是在需要用代码表明自身意图时遭遇到了失败,所以只能妥协用注释来表达自我。
为什么要极力贬低注释呢?因为注释就像一个长不大的孩子,总是跟不上其所描述的代码,毕竟程序员很难坚持维护注释。
你也不要急着说:承诺程序员应该对自己写的注释负责,因为维护注释就像坚持暑假读书计划一样,容易被短期的快乐冲淡,你确定在调试一堆bug的涂涂改改各种方法时,还能记得修改那些“无关痛痒”的注释?
而不准确的注释比没有注释还要糟糕,它可能定义了一些没有的功能、制定了一些不应再遵循的规则,所以要尽量减少注释,只有代码能衷心地告诉你它做的事。
好注释
1. 法律信息
有时公司代码规范要求编写与法律有关的注释,例如版权以及著作申明,这类注释不应是合同或法典,只要有可能,应尽可能的用一个指向某个标准许可文档的链接来代替。
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
2. 对某些晦涩难懂的参数、返回值的解释
通常更好的办法是尽量让参数和返回值名字自身足够清楚,但若参数或返回值是某个标准库的一部分或着你不能修改的代码,那必要阐释参数和返回值意义就很有必要。
//Returns an instance of 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*")
=>可以删掉注释,把这段代码移到某个转化日期和时间格式的类里会更清晰
public void testCompareTo() throws Exception {
.....
assertTrue(a.compareTo(b) == 0); //a == b
assertTrue(a.compareTo(ab) != 0); //a != ab
assertTrue(a.compareTo(ba) == -1); //a < ba
}
3. 对意图的解释
这类注释主要是反应这段代码的意图。比如某段代码读懂不难但无法理解其为什么这么写时,把自己决策的理由写在注释可以解决阅读者很多的问题。
4. 警示
主要用于警告其他程序员。
//Don't run unless you have some time to kill
public void _testWithReallyBigFile();
//SimpleDateFormat is not thread safe func
//so we need to create each instance independently
SimpleDateFormat df = new SimepleDateFormat("EEE, dd MMM yyyy HH:mm:ss z")
5. 公共API的Javadoc
没有什么比描述详细的公共API更有用和令人满意的了,类似标准Java库中的Javadoc就是一例。
坏注释
1. 多余的注释
1.1 对于短小的函数,读其注释的时间比读函数体的时间还要长,所以起一个好名字比写注释更好。
1.2 毫无意义的注释就是废话。
//Returns the day of the month
public int getDayOfMonth();
//The version
private String version;
1.3 循规式注释
所谓的每个函数都要有Javadoc或者每个变量都要有注释的规矩全然是愚蠢可笑的,这类注释只会让代码变得散乱、满口胡言。
/**
* @param title The title of CD
* @param author The author of CD
* @param tracks The number of tracks on CD
* @param durationInMinutes The duration of the CD in minutes
*/
public void addCD(String tile, String author, int tracks, int durationInMinutes();
1.4 日志式注释
有人会在每次编辑代码时在模块开始处添加一条日志式的注释记录本次修改,在很久以前没有源代码控制系统时这种注释还有必要,但现在这种冗长记录只会让模块变得凌乱不堪。
2. 需要解释的注释
当阅读者读完注释后仍有疑问,还需要看其他模块的代码才能弄清原委,那这条注释存在的意义何在?
public void loadProperties(){
try{
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
laodedPrperties.load(propertiesStream);
} catch(IOExcepotion e){
// No properties files means all defaults are loaded
}
}
=>这块注释意思是当出现IOException时即没有属性文件,这种情况下说明载入默认设置值。
=>但又是谁来装载默认设置?是在该函数之前吗?还是在该函数之后?需要捕捉异常向上传递吗?还是作者告诉自己回头过来写装载默认值?
/*
* start with an array that is big enough to hold all the pixels
* plus filter bytes and an extra 200 bytes for header info
*/
this.pngBytes = new byte[((this.width+1) * this.height * 3 + 200];
=>过滤器字节是哪个呢?是那个+1还是那个*3?
3. 误导性注释
最直接的就是注释和代码的意思不一样,给调用者带来潜在的隐患。
还有被注释掉的代码,其他人不知道其存在的理由但又觉得这个代码放在那里肯定有存在的必要,又不能删除,只能像残桓破瓦堆在那里。
4. 能用变量或者函数替换的注释
/*
* does the moudle from the global list <mod> depend on the subsystem
* we are the part of ?
*/
if (smodule.getDependSubsystems().contains(subSysmod.getSubsystem()))
=>可以改成
ArrayList moduleDepedees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDepedees.contains(ourSubSystem))
5. 位置标记注释
6. 归属和署名
借力于源代码控制系统,我们现在已经不需要通过手写这些签名信息来标记代码,随着代码放置和修改的时间越久,代码和原作者的关系也会越远。
本文内容属于个人学习笔记,主要信息来源书籍《代码整洁之道》/《Clean Code》