您的位置:首页 > 编程语言

《编写可读代码的艺术》---该写什么样的注释

2014-04-17 17:14 141 查看
本章旨在帮助各位读者们去了解应该写什么样子的注释,你可能以为注释的目的就是解释代码做了什么,没错,但这只是其中一部分。


注释的目的

尽量传递信息给读者,使其对代码的熟悉程度和作者一致。


当你写代码的时候,脑海里有很多有价值的信息,你没有选择记录下来;

当其他人阅读你的代码的时候,这些信息就丢失了---它们看到的只有代码。

因此,本章主要围绕以下三点展开:

仅当需要的时候,才使用注释

记下有价值的信息

站在读者的角度,去想象他们需要什么

仅当需要的时候,才使用注释

1 如果代码本身可以快速推断出其意义,就没必要写注释。

注释会分散读者理解代码的注意力,会占用屏幕空间,因此,注释是要有存在价值的。

比如下面的代码里面的注释对于读者而言,是没有价值的。

/// <summary>
/// 日期工具类,提供日期的一系列函数
/// </summary>
class DateHelper
{
/// <summary>
/// 私有日期变量
/// </summary>
private DateTime _currenDateTime = DateTime.Now;

/// <summary>
/// 默认构造器
/// </summary>
public DateHelper()
{

}

/// <summary>
/// 获取日期
/// </summary>
/// <returns></returns>
public DateTime GetTime()
{
return _currenDateTime;
}
/// <summary>
/// 设置日期
/// </summary>
/// <param name="newdate"></param>
public void SetTime(DateTime newdate)
{
_currenDateTime = newdate;
}
}


2 注释不是复述一遍代码

如果注释仅仅只是复述了一遍代码字面上的意思,那么它对读者的价值就大大降低,这种情况,要么删掉注释;要么让注释传递更多的信息

我们来看个例子:

//使用指定名称在树指定的深度查找节点
Node FindNodeInTheTree(Tree tree, string nodeName, int searchDepth);


很显然,这个注释就属于“复述型”,要么删除它

如果查找方法还有其他细节的,你可以选择完善注释,比如下面的修改方案

//通过名称查找指定的节点,否则返回null
//如果searchDepth<=0,那么只在tree根目录查找
Node FindNodeInTheTree(Tree tree, string nodeName, int searchDepth);


3 注释不应该拿来粉饰

注释不应该拿来粉饰不友好的命名,应该使用重构工具来重命名一个友好的名字。

比如,我们有个方法,在公司人员离职的时候调用

// 当员工离职的时候,调用本方法
// 这个方法不会从数据库物理删除该用户所有数据
// 只是将用户的状态设置为禁用
bool DeleteUser(Guid userGuid);


这个时候,我们与其拿注释来粉饰这个糟糕的方法名称,还不如重构它

bool DisableUserWhenDimission(Guid userGuid);



好的代码 》 坏的代码+好的注释


记下有价值的信息

1 加入你的心得

1.1 探过的路,不必再探

比如:有个方法需要用到排序。

//在测试中,我们比较了快速排序、选择排序、冒泡排序
//最终我们选择快速排序,因为对比发现,快速排序速度最快,占用内存最小


1.2 踩过的坑,不要再踩

比如我们系统中,引入了第三方插件,但是有个BUG

//这个插件在随机创建用户名的时候
//极小情况下,会出现用户名重复的情况
//已经反馈给作者,等待作者修改,不是系统的BUG


2 为代码中的瑕疵注释

在敏捷开发里面,更新非常频繁,代码在不断的成长,最终演变成最终版。但是在这个迭代的过程中,代码肯定会存在瑕疵,我们就可以通过注释,将这些瑕疵记录下来。

这种注释给读者带来对代码质量和当前状态的宝贵理解,甚至可能会给他们指出如何改进代码的方向。

//TODO   我还没有处理完的事情
//FIXME   已知的有异常的代码
//HACK    有待优化的低劣方式
//XXX      危险! 这里有问题


3 给常量加注释

简单点讲,就是回答读者:为啥这个常量取这个值。

3.1 常量调整指南

//线程的数量:经过测试,发现如果该值<= 2*cpu核的时候,系统表现良好
const int THREAD_COUNT = 6;


3.2 不要乱动

//人类的移动速度:经过多次调整,发现在3.14速度下,人类的移动速度最自然
private const float PEOPLE_MOVE_SPEED = 3.14f;


3.3 补充说明

//人类体重的限制,额,正常人不会超过这个重量吧
const int MAX_HUMAN_WEIGHT = 500;


有些常量如果本身名称就足够清晰,那么就不需要注释,比如(PI=3.1415;MINUTES_PER_DAY=1440)

4 站在读者的角度

这个“读者”可以是不熟悉项目的人,也可以使若干个月之后我们。想象下,我们平时写的代码,对于刚接手的外人来讲,是什么样子?是泥淖还是风景?

4.1 意料之中的提问

很多网站上都专门开辟了常见问题(F&Q)专栏,对读者80%的常见问题,进行统一回答,省时省力。

因此,你觉得作者在读我们的代码的时候,遇到某些模块,会有“什么?为什么要写成这样?”的问题的时候,就可以给这个问题加上注释。

比如下面的c++代码,我们要清空一个数组,读者看到Clear方法可能会疑惑

Struct Recoder{
vector<float> data;
//....

void Clear(){
//清空数组为啥不用data.clear()?
vector<float>().swap(data);
}

}


所以我们要做的是回答读者,为啥不使用data.clear()这个常用清空数组的方法?

//实际上,只有这样才会强制让data真正的把内存归还给内存分配器
//详情参阅:“STL swap trick”
vector<float>().swap(data);


4.2 公布缺陷

当为一个函数或者类的写文档的时候,可以问自己“这段代码有啥出乎意料的地方,会不会被误用?”。

也就是说,你要“未雨绸缪”,预料到别人使用你代码的时候,遇到的问题。

比如一个向用户手机发送验证码的函数:

void SendMessageToPhone(string to,string subject,string body);


但是因为短信提供商的问题,这个方法有可能会延迟几个小时(不是实时的),

所以,我们有必要给这个函数打上注释

//给用户手机发送短信,因为运营商的关系,可能会造成延时,(我们试过1个多小时后才收到短信。。擦)
void SendMessageToPhone(string to,string subject,string body);


4.3 全局观注释

对于团队的新成员来讲,最难以理解的事情之一就是理解“全局观”---类之间如何交互啊、数据如何在整个系统中流动啊、项目入口点在哪里啊。。。balabalabala

设计系统的人常常忘记给这些点加上注释,因为他们觉得这些很容易理解,以至于不需要注释。

想象下这样的场景:“团队来了个新人,他坐在你旁边,而你的任务是让他快速熟悉代码,进入到开发的角色中。”

因此,你可能会带着他遨游现有的代码库,指着某些文件、代码侃侃而谈:


“这段代码把我们的业务逻辑和数据库黏在一起,任何应用层的代码都不应该和他交互。”

“这个类看上去很复杂,实际上,它只是一个巧妙的缓存,它对系统中其他部分一无所知。”

“页面的事件执行顺序是a->b->c->d,我们之所以要这样设计,是因为balabalaba。”

“这个类包含一些辅助的函数,为我们的网站后台管理提供了便利的接口,用来处理用户的权限问题,比如登陆、管理”


相比阅读源码这样的交谈是不是让新人获益良多?那为什么我们不把他们整理下,写到注释中呢? “好记性不如烂笔头”

4.4 总结性注释

方法内部如果逻辑比较多,或者比较复杂。除了第一步进行重构,第二步我们可以给对应的代码块写上注释

对这个函数所做的事情做个总结,因此读者在深入了解代码前,对这个代码干啥用的已经了然于胸。

void ShowCustomBoughtItems(string userName)
{
//1 根据用户名从数据库中查找出该用户ID

//2 根据用户ID从商品-用户关联表里找出商品ID列表

//3 根据商品ID关联商品表,获取对应的商品详情

//4 列出这些详情
}


你可以做任何帮助读者更理解代码的事情,而不必纠结于注释的职责。“做什么”、“怎么做”、“为什么这样做”都可以拿来活用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: