Chinaunix首页 | 论坛 | 博客
  • 博客访问: 56434
  • 博文数量: 16
  • 博客积分: 691
  • 博客等级: 上士
  • 技术积分: 130
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-07 14:53
文章分类
文章存档

2010年(16)

我的朋友

分类: C/C++

2010-12-04 11:05:24

    转载按:这是我在InfoQ上看到的一个关于代码书写规范的一系列的讨论,文后的评论,有些比较有意思的,我也一并摘抄下来了,大家有什么看法、想法也可以在这里继续讨论。




又见switch:

switch(firstChar) {
  case ‘N’:
    nextFirstChar = ‘O’;
    break;
  case ‘O’:
    nextFirstChar = ‘P’;
    break;
  case ‘P’:
    nextFirstChar = ‘Q’;
    break;
  case ‘Q’:
    nextFirstChar = ‘R’;
    break;
  case ‘R’:
    nextFirstChar = ‘S’;
    break;
  case ‘S’:
    nextFirstChar = ‘T’;
    break;
  case ‘T’:
    throw new IllegalArgument();
  default:
  }


出于多年编程养成的条件反射,我对于switch总会给予更多的关照。在那本大名鼎鼎《重构》里,Martin Fowler专门把 switch语句单独拿出来作为一种坏味道来讨论。研习面向对象编程之后,看见switch,我就会联想到多态,遗憾的是,这段代码和多态没什么关系。仔 细阅读这段代码,我找出了其中的规律,nextFirstChar就是firstChar的下一个字符。于是,我改写了这段代码:

switch(firstChar) {
  case ‘N’:
  case ‘O’:
  case ‘P’:
  case ‘Q’:
  case ‘R’:
    nextFirstChar = firstChar + 1;
    break;
  case ‘T’:
    throw new IllegalArgument();
  default:
  }


现在,至少看起来,这段代码已经比原来短了不少。当然这么做基于一个前提,就是这些字母编码的顺序确确实实是连续的。从理论上说,开始那段代码适用性更强。但在实际开发中,我们碰到字母不连续编码的概率趋近于0。

但 这段代码究竟是如何产生的呢?我开始研读上下文,原来这段代码是用当前ID产生下一个ID的,比如当前是N0000,下一个就是N0001。如果数字满 了,就改变字母,比如当前ID是R9999,下一个就是T0000。在这里,字母也就相当于一位数字,根据情况进行进位,所以有了这段代码。

代码上的注释告诉我,字母的序列只有从N到T,根据这个提示,我再次改写了这段代码:

if (firstChar >= ‘N’ && firstChar <= ‘S”) {
    nextFirstChar = firstChar + 1;
  } else {
    throw new IllegalArgument();
  }


这里统一处理了字母为T和default的情形,严格说来,这和原有代码并不完全等价。但这是了解了需求后做出的决定,换句话说,原有代码在这里的处理中存在漏洞。

修改这段代码,只是运用了非常简单的编程技巧。遗憾的是,即便如此简单的编程技巧,也不是所有开发人员都驾轻就熟的,很多人更习惯于“平铺 直叙”。 这种直白造就了代码中的许多鸿篇巨制。我听过不少“编程是体力活”的抱怨,不过,能把写程序干成体力活,也着实不值得同情。写程序,不动脑子,不体力才 怪。

无论何时何地,只要switch出现在眼前,请提高警惕,那里多半有坑。





以下是原出处的评论(摘抄):

2010年11月24日 下午7时32分 发表人 张 经纬

很好的系列,成长的过程需要被总结和借鉴,可以整理成培训材料
有些情况看起来弱智,可现实都会存在。昨天看到一段代码,很典型的问题,列出来当素材吧:

if ( bussinessType == 0 ) {
if ( devId == 0 ) {
if ( linkId == 0 ) {
if ( year == 0 ) {
if ( month == 0 ) {
if ( day == 0 ) {
if ( hour == 0 ) {
if ( min == 0 ) {
return 0;
} else if ( min < 0 ) {
return -1;
}
return 1;
} else if ( hour < 0 ) {
return -1;
}
return 1;
} else if ( day < 0 ) {
return -1;
}
return 1;
} else if ( month < 0 ) {
return -1;
}
return 1;
} else if ( year < 0 ) {
return -1;
}
return 1;
} else if ( linkId < 0 ) {
return -1;
}
return 1;
} else if ( devId < 0 ) {
return -1;
}
return 1;
} else if ( bussinessType < 0 ) {
return -1;
}
return 1;

--------------------------------------------

2010年11月24日 下午8时28分 发表人 毕 成栋

减少第一次代码的“量”还是20%的功,真正的体力活是需求变更。将switch改成if我没看出任何灵活性。switch真正的危害是出现在需求变更的情况下。用多态将switch封装可以将变化点隔离在一处。但是作者确没有写.......

--------------------------------------------

2010年11月24日 下午9时31分 发表人 曹 云飞

用多态将switch封装是经典的手法,对于这个例子而言并不合适。针对这个需求,这么改是巧妙的。作者这篇文章的确没有说明“经典的switch陷阱”,但是体现了因地制宜的进行重构的精神,不如换个标题,“因地制宜的改造switch陷阱”。

--------------------------------------------

2010年11月24日 下午11时27分 发表人 高 翌翔

首先肯定,作者的最终优化代码更精简,不过可读性仍有改进的余地。

if (firstChar >= 'N' && firstChar <= 'S') {
nextFirstChar = firstChar + 1;
} else {
throw new IllegalArgument();
}

俺进一步改写为:

{
...
FirstCharShouldBeInLegalRange(firstChar);

nextFirstChar = GetNextFirstChar(firstChar);
...
}

private void FirstCharShouldBeInLegalRange(char value)
{
if (firstChar < 'N' || firstChar > 'S')
throw new IllegalArgument();
}

private char GetNextFirstChar(char value)
{
return (value + 1);
}

修改后的代码功能不变,但凭空多出来两个私有函数,
或许有人会说,这是多此一举(前半句就免了,实在不雅,哈哈)。

但可读性得到了提升,原有的 if else 被封装起来,取而代之的是两句平铺直叙的函数调用。

这种写法并非俺独创,在单元测试代码中遍地都是,但读起来一目了然,一气呵成,痛快之极!
读者根本不用考虑 switch-case 或是 if-else 等分支逻辑(都被封装起来了),

慢慢地俺喜欢上这种写法,把它应用到开发代码中,发现效果依然显著,代码可读性大大提高!

结论:switch-case 与 if-else 虽然可以相互转换、简化,
   但在不可避免使用的情形下,将它们封装成语义明确的方法(函数)更有意义,
   当然这种方法在 Martin Fowler 的 Refactoring 早就提到了,但想用好还需持续实践和反思!

------------------------------------------------

2010年11月24日 下午11时37分 发表人 Peng Shawn

是的,太正确了。变成IF会造成更多奇怪的代码,有时候反而不够switch好看,switch困扰了很多人,包括很多SSE。
如果每一个分支都有一段复杂逻辑,那么switch将变得非常难看。
幸 好在我所接触过的很多代码中,case里包裹的逻辑真得很少(只有一两行赋值而已),很多人认为逻辑简单,没有必要使用state,当出现Switch时 也就意味着状态实在太多了,抽取State会让人觉得烦琐,大多数人认为这样也工作得很好。坦率地讲,我不欢迎switch但也从来不拒绝。
以经验来看,大凡switch内分支都具备一些共性,一般地讲,所有的分支总是在处理同一批状态,于是我们才把这些状态封装成一个对象,而不同的Case则是不同的state,他们看起来会非常完美,但你仍旧得处理分支,你需要使用工厂来创建状态对象。

------------------------------------------------

2010年11月25日 上午12时3分 发表人 侯 辛酉

看了几篇,感觉写的很好,至少问题写的都很好,解决方法却是大家都有不同的方法,我更倾向于把对应的东西存入hashtable中,虽然这个例子中是有规律的,但并不是所有的情况都是有规律的,我一般会找到一种更加通用的方式!

------------------------------------------------

2010年11月25日 上午8时1分 发表人 zhang jinsong

把这一个系列都看了看,有两个我觉得容易给人误导的倾向,当然这个倾向已经快变成共识了。

简短的不重复的代码就一定强过啰嗦的代码。
声明式代码一定强于命令式代码。

作者不可能举过于复杂的例子,这是文章所限,不能强人所难,但问题是,在简单的场合,这两个原则的确是我认为有问题的,再多的原则,我以为都比不上“易读”这两个字。

有很多时候啰嗦的命令式代码恰恰是易读的,它甚至因为恰当的重复而达到这个目的。

过份在每个地方强调这样的写法,我怀疑会造成代码的阅读困难,交流困难,还可能带来一些不易察觉的bug。

简洁的声明式代码的强项更多的体现在架构设计,面对复杂变化时的场合。在例子中恰当的体现这一点,无疑对写作提出了更高的要求。

------------------------------------------------

2010年11月25日 上午8时7分 发表人 zhang jinsong

这些说法,在当前的潮流下可能显得反动了,哈哈。不如再提个更加反动的观点,有时候先写出易读,但是僵硬的代码,面临变化时再去重构,可能比一开始就考虑各种需求变化来得更好。

原因是:我们思考的层次本来就是如此递进的,不先把一些简单的线索先搞清楚,很难面对后面复杂的变化。

对代码风格,我觉得有一个是最高原则,无可置疑,这就是“易读”。

------------------------------------------------

2010年11月27日 下午8时23分 发表人 孙 奇辉

有一个是最高原则,无可置疑,这就是“易读”。
--请问,你怎么定义或理解“易读”?我想“易读”是指容易读懂吧,这跟个人素质有关 的。例如,台湾同学对同一篇技术文章,从右至左的看繁体字,比大陆同学从左至右看简体字,算是“易读”吧;常春藤名校的、以UNIX和LISP背景的同 学,比国内的WINDOWS和C背景的同学看同一段程序,“易读”的感觉不一样吧?

简短的不重复的代码就一定强过啰嗦的代码。
声明式代码一定强于命令式代码。
--很显然,以上的命题是正确的,除非读者一直以来,是以WINDOWS+C/JAVA等为背景的程序员,没有在UNIX/LINUX下的函数式编程的经历或兴趣。

阅读(1667) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~