qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

提高代码质量系列之一—尽可能少写注释

关于<<提高代码质量系列>>
  这是我新开的一个系列,旨在记录我对整个编码规范,代码风格,语法习惯,架构设计的一些思考,感悟和总结.
  前言
  不知道大家会不会觉得我的标题很噱头,不是一般应该提倡写注释的么?首先我得解释下,我这句话有两个意思!
  1,绝非提倡不写注释,而是不要写不必要的注释.
  2,命名规范的作用大于注释
  好吧,这么一说,其实还是有点噱头的感觉的,因为我这篇文其实重心更放在强调命名规范和设计规范上面,良好的规范,让你的代码有自释性,省去了注释的步骤.
  还要强调下的是:这个观点绝非我自己主观臆断,凭空瞎想出来的. 而是实实在在由项目开发里面总结出来的.
  为什么我有这个想法呢?请继续看我的蛋疼经历.
  正文
  先上段奇葩代码
/// <summary>
///     根据产品ID获取产品列表
/// </summary>
/// <param name="columnID">关键字</param>
public DataTable GetColumnInfoByColumnID(int columnID)
{
return DALColumn.GetColumnInfoByColumnID(columnID);
}
/// <summary>
///     根据产品名称获取产品列表
/// </summary>
/// <param name="columnID">关键字</param>
public DataTable GetColumnInfoByColumnID(string columnName)
{
return DALColumn.GetColumnInfoByColumnID(columnName);
}
  这是我现在维护的一个老项目了,经手的人比较多,代码写的比较垃圾,我们先不吐槽这种纯粹是脱裤子放屁的所谓三层和明明返回的是dataTable又扯什么info,就说这两个函数,tm功能应该是不一样的吧,为啥名字一模一样?这是在闹哪样?
  其实,造成这种情况的原因,我们都知道,就是某类程序员的ctrl c+v大法,这两个函数底层逻辑比较相似,懒得重构,直接copy了改改多快!copy就copy吧,好歹名字改一下啊!也许这位前辈会说,我不是写了注释了吗,看到注释,不就知道这个函数是干嘛的了?但问题是,其他调用的人,首先看到的,肯定是函数名啊,GetColumnInfoByColumnID,多直观 通过id来查找呗.虽然这里有两个比较违和的地方,一是参数默认名字是columnName,二是参数类型不是int而是string,但反正这项目的代码不规范,如果调用的人也够粗心,那么,一个隐晦的bug,就这么产生了.
  如果改下名字,叫 GetTableByColumnName,这种错误发生的概率无疑会减少很多了,
  Ps:其实现在ide功能这么强大,只要你确定没有反射调用这个函数的地方,完全可以使用全局重命名的方法,一步到位.
  看到这里读者朋友们可能会说,这是命名不规范嘛,和写不写注释有什么关系呢?
  我们可以假想一种这样的情况,同样是拷贝代码,拷贝者改了函数名,这个名字语义清晰,表意清楚, 但他却忘记改注释了,结果函数是新的函数签名,函数的注释却是其他一段莫名其妙的注释.
  或者再假想一种情况.
  原来的一个函数,名字和注释是对应的,随便举个例子,叫GetTypeByColumnId吧,注释为"通过产品id取得产品类型"
  一切ok,是吧! 但是现在逻辑变了,比如说Type和产品无关了,需要通过生产批次来确定,于是一个代码维护者,将函数重写了一下功能ok,满足新需求!!同时他比上个人好一点,他记得改函数名,然后他改为了GetTypeBySerialId,这名字也很ok,一切也都看起来很好.但偏偏他漏掉了注释,但代码仍然运行的很ok,毕竟注释可不受.net的元数据的支持,ide也不可能知道你这里犯了这么一个错误是吧!
  然后,接下来的场景大家很容易就可以联想到,
  如果是细心的调用者:
  尼玛!函数注释让我传"产品id",但函数名和参数默认名字又是"SerialId"(流水号),这尼玛闹哪样?
  如果是粗心的调用者呢?两种情况呗:
  1.看了函数名字和默认参数名字 ,没注意到注释,ok,算这个粗心的小伙伴幸运,
  2.看了注释,然后这小伙伴不认识SerialId这个单词的意思. 然后,一个悲伤的故事就这么发生了!!
  其实我以上举例的一些函数,都是功能比较具体,业务比较单纯,也容易用几个单词描述.对于这些函数,我个人的意见是,完全不需要去写注释,
有以下几个原因:
  1.浪费精力去写,
  2.调用的人需要把一段话读两遍(函数名和注释)
  3.写了还需要人去维护,(改了代码,得同步去改注释)
  4.如果强类型的参数传递不匹配,ide或者resharper插件会马上指出你的错误,但如果注释和代码不匹配,则除了通过人力CodeReview,没有其他任何办法去找出这种错误.
  其中尤其是4,完全就是埋在项目中的地雷,除非你踩到了,不然很难排查.
  当然,如果是反之,逻辑复杂,甚至有调用的前置约束,那肯定该写注释还是得写了.
  同时,这里还提一个观点,注释比代码更有价值,因为代码毕竟大部分还是在讲怎么做(how do),而注释是讲做什么(do what)?抽象程度更高,对于比较复杂的代码,如果有注释,调用者一般都会优先去阅读注释,而不是去阅读代码,所以:轻易不写注释,但如果你写了,请一定要对你的注释负责,它比代码更需要你的细心呵护!!
  好吧,我会说这篇文最大的作用,其实是可以让我吐槽发泄一下么?
  我感觉自己现在就是在这种地雷坑里,天天过着提醒吊胆的日子.
  以上例子皆非我刻意编造,来源于工作中的真实经历,只是稍作修饰,隐去了和业务有关的信息.
  根据回复的补充:
  相信很多博友有这样的经历,这个函数好复杂呀!我必须写注释啊,不然一段时间之后,我自己都看不明白了,
  有些时候是确实很复杂,但也有时候是设计的问题,这时候,写注释其实是一种逃避了.逃避你需要继续深入思考这个函数的业务和功能的设计是否合理.
  其实我这标题还有第三个意思:
  在尽可能少写注释的前提下,如果一个函数的注释仍然超过了2处,那么我认为这个函数的设计是有问题的.
  这个函数是否太大,是否是反模式里面提到的万应灵或屠龙术?
  所以有时候说的 "因为业务复杂,很难用几个单词去描述,所以很难命名",就是如此.
  你的业务抽象粒度是否太大?导致一个业务模块承担了过多的业务.
  你是否把几个流程放在了一个节点上?导致引入了额外的逻辑判断甚至是多余的逻辑分支.
  如果是这样,你想用几个单词来描述这么复杂的逻辑,当然是不可能了.
  这点我会在后续的重构系列里面谈谈我自己的理解.
  补充下自己的理解.
  我始终认为好的设计,大部分情况下,函数名就足以解释它的功能,如果你遇到了两三个单词不能解释函数功能的情况----说明你该分解函数了!
  比如一个大函数,OutPutMetaData,输入是源数据路径,使用的模板 返回解析之后的元数据
  流程大概是 采集数据->分析数据->匹配模板->生成MetaData
  代码是大约1-2k行,如果写在一个函数里面,当然也似可以的,但你想用注释解释清楚,必须在每个流程的关键节点写注释,遇到数据有前后关联关系的,还得思维反复跳来跳去.
  但如果写成下面这种模式的代码,基本就像是阅读英文说明(注释),一样阅读代码了
public MetaData OutPutMetaData(string sourcePath, MetaTemplate template)
{
var metaDataFactory = new MetaDataFactory();
if (!metaDataFactory.CheckInput(sourcePath))
{
throw new ErrorSourceException();
}
if (!metaDataFactory.TransformSourceData(template))
{
throw new ErrorFormatException();
}
return metaDataFactory.CreateMetaData();
}
  这样写虽然多了很多的类和方法,还要额外定义一些中间数据的实体类型和自定义异常,但是经过合理的封装和命名之后,整个结构非常清晰,定位错误和修改流程也方便.
  代码阅读速度基本和描述语句(注释)的阅读速度相当, 这就是代码即注释.
  最后总结:
  其实我认为最关键是要形成自己的编码规范,这个"规范"不仅仅指的是狭义的命名规则和代码格式,缩进,文件组织结构等.更关键的是,要形成一套有逻辑性,能自洽,有良性导向的一套思维模式,并时刻坚持遵守它,思考它,改进它.
  这套思维模式你可以自由的去扩展,只要不偏离它的中心思想.比如我给自己扩展的一些要求:
  1.函数一律使用动宾结构,如InitFactory,而不用FactoryInit,其实这两者没什么优劣,仅仅只是让自己习惯,以后思考和阅读自己代码的时候,能更快的带入过去的自己的思维,更快的理解自己的代码,同时找api也能节约一点时间.
  2.html标签样式id小写开头,class大写开头,同理,其实也没啥原因,就是个习惯.
  3.描述一个事物的时候要区分是what it is(名词,形容词)还是what it can do(动词,动名词,动宾短语)比如doClose, 是某个事物的动作,closing,和 closed则代表它的状态
  再就是结构设计上的一些感觉了,这个比较抽象,不好用文字很准确的描述,大致意思就是我会从一些纬度对功能进行切分,access business viewModel show interaction 等,不一定都能分的非常清楚,也不强求一个区分度很高的边际,但至少要有个模糊的定位.
  如果能长期坚持下来,以后阅读自己的代码是非常容易的,即使是没有(少量)注释.
  Ps:锤炼自己的这套思维模式的方法也很简单,也就三点
  多看优秀代码,自己动手多写,多思考总结.

posted on 2014-11-21 10:55 顺其自然EVO 阅读(240) 评论(0)  编辑  收藏 所属分类: 测试学习专栏


只有注册用户登录后才能发表评论。


网站导航:
 
<2014年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜