BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

专栏:代码之丑(五)——不受欢迎的大心脏

| 作者 郑晔 关注 2 他的粉丝 发布于 2010年11月30日. 估计阅读时间: 5 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

不知道为什么,初见它时,我想起了郭芙蓉的排山倒海:

ColdRule *newRule = new ColdRule(); 
newRule->SetOID(oldRule->GetOID()); 
newRule->SetRegion(oldRule->GetRegion()); 
newRule->SetRebateRuleID(oldRule->GetRebateRuleID()); 
newRule->SetBeginCycle(oldRule->GetBeginCycle() + 1); 
newRule->SetEndCycle(oldRule->GetEndCycle()); 
newRule->SetMainAcctAmount(oldRule->GetMainAcctAmount()); 
newRule->SetGiftAcctAmount(oldRule->GetGiftAcctAmoun t()); 
newRule->SetValidDays(0); 
newRule->SetGiftAcct(oldRule->GetGiftAcct()); 
rules->Add(newRule); 

就在我以为这一片代码就是完成给一个变量设值的时候,突然,在那个不起眼的角落里,这个变量得到了应用:它被加到了rules里面。什么叫峰回路转,这就是。

既然它给了我们这么有趣的体验,必然先杀后快。下面重构了这个函数:

ColdRule* CreateNewRule(ColdRule& oldRule) { 
   ColdRule *newRule = new ColdRule(); 
   newRule->SetOID(oldRule.GetOID()); 
   newRule->SetRegion(oldRule.GetRegion()); 
   newRule->SetRebateRuleID(oldRule.GetRebateRuleID()); 
   newRule->SetBeginCycle(oldRule.GetBeginCycle() + 1); 
   newRule->SetEndCycle(oldRule.GetEndCycle()); 
   newRule->SetMainAcctAmount(oldRule.GetMainAcctAmount()); 
   newRule->SetGiftAcctAmount(oldRule.GetGiftAcctAmount()); 
   newRule->SetValidDays(0); 
   newRule->SetGiftAcct(oldRule.GetGiftAcct()); 
   return newRule; 
} 

rules->Add(CreateNewRule(*oldRule)); 

把这一堆设值操作提取了出来,整个函数看上去一下子就清爽了。不是因为代码变少了,而是因为代码按照它职责进行了划分:创建的归创建,加入的归加入。之前的代码之所以让我不安,多重职责是罪魁祸首。一旦把这个函数提取出来,做完这步操作,我们就不难发现这个函数应该成为CodeRule类的一部分。篇幅所限,就不再继续了。

谈论干净代码时,我们总会说,函数应该只做一件事。函数做的事越多,就会越冗长,也就越难发现不同函数内存在的相似之处。为了一个问题,要在不同的地方改来改去也就难以避免了。但面对长长的函数,还是有人无动于衷,继续往里塞着“新”代码。

即便大家都认同了函数应该只做一件事,但多大的一件事算是一件事呢!不同的人心里有不同的标准。有人甚至认为一个功能就是一件事。于是,代码会越来越刺激。想写干净代码,就别怕事小。哪怕一个函数只有一行,只要它能完整的表达一件事。在干净代码的世界里,大心脏是不受喜欢的。

接下来,我需要用历经沧桑的口吻告诉你,这么跌宕起伏的代码也只不过是一个更大函数的一个部分。此刻,浮现在我脑海里的是层峦叠嶂的山峰。

作者简介:

郑晔,ThoughtWorks公司咨询师,拥有多年企业级软件开发经验,热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式,加入ThoughtWorks公司后,投入到敏捷开发方法的实践之中,为其他公司提供敏捷开发方法方面的咨询服务。他的blog是梦想风暴

查看原文:代码之丑(五)

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

学习到代码重构的知识了 by jim toner

首先您对代码的评论比较到位,也突出了一个重点,就是函数的单一功能性。
但是我想说的是,您的这段代码好像是模仿"重构-改善即有代码的设计"上来的,所以摘抄时希望您注明原出处。

Re: 学习到代码重构的知识了 by 王 杰

"重构-改善既有代码的设计" 经典中的经典啊。

Re: 注明出处就不必了吧 by 高 翌翔

先说说 Refactoring,这是 Martin Fowler 的经典之作,同时 Martin 还是 ThoughtWorks 的首席科学家。

作者与 Martin 乃是同门,给大家拆解几招也不足为奇,呵呵

Re: 学习到代码重构的知识了 by Zheng Ye

写这篇东西的时候,《重构》就没在身边,不存在摘抄。事实上,这个系列的所有代码都来自真实项目。

从另一个角度看,这足以说明烂代码也有相似之处了。

《俺也说说一个方法做一件事儿!》 by 高 翌翔

作者在文章中提到,即便大家都认同了函数应该只做一件事,但多大的一件事算是一件事呢!不同的人心里有不同的标准。有人甚至认为一个功能就是一件事。

作者的话过于模糊,接下来俺会一句一句话地解释,并推导出一个可指导实践的结论:

1 “函数应该只做一件事”——表明函数是用来做事的。
具体说,函数名表明“做什么事情”,而函数体记录“如何做事情”。

2 “多大的一件事算是一件事呢”——似乎问题在于如何定义“一”,其实不然,问题关键在于“大”字上!
“大”的计量单位是什么?!

至此,焦点集中在“事情”和“计量单位”上?

接下来,先看几个定义:
事情:人类生活中的一切活动和现象。
活动:有一定目的的行动。
现象:事物在发展、变化中所表现出来的外部形态。
(以上定义引自“百度词典”)

在“事情”的定义中,“人类生活”定义了问题域,活动和现象定义了问题的主体。
回到程序的世界里,问题域不仅限于“人类生活”,而是我们所能感知、并可以用代码表示的所有领域,
而活动被一系列方法所取代,现象被用若干属性来记录。

在谈“大”的计量单位之前,先来说下“大事情”,以“大事情”为关键字在 Google 上搜索就会发现,搜索结果形形色色,
从关系国计民生的政策法规,到茶米油盐酱醋茶的生活琐事,均被冠以“大事情”的标题。
可见所谓“大事情”只是人们从不同角度或出于不同目的,认为值得关注的事情。

由此可知,所谓“大”即值得关注,“大”从来就没有任何计量单位。

前两句话可以重新整理为:函数的函数名表明“做什么事情”,而函数体记录“如何做事情”。值得关注的活动和现象就可定义为一件事情。

3 “不同的人心里有不同的标准”——程序想怎么写就怎么写么?当然不是!那么写程序有统一的标准么?当然没有!
虽然没有标准,但还是有规律可循的,不过规律不在代码中,而在代码外!

前面已经讨论过了,函数是用来做事的,而所做的事情又是我们在生活中关注且经常重复的活动和现象。
因此,与其说编程的规律,不如说是事情本身的规律。

那么“事情本身的规律”又是什么?最基本的规律就是“活动的时序性”,即一件事情可以按时间的先后顺序分解为若干步骤来完成。
例如,军训时大家都有学习踢正步的经历,一个正步动作被分解为摆臂、踢腿等若干容易实现的分解动作。
从“学习踢正步”还会发现一个分解的规律,即每个分解动作只关注一个局部动作。

综上所述,事情的基本特性为“时间和空间的可分解性”。因此,方法的分解也须遵循此规律。

仔细分析就会发现,早期的面向过程程序设计只关注“时间可分解性”,随着问题域复杂度不断增加(参与者越来越多),
后来的面向对象程序设计在此基础上增加了对“空间可分解性”的关注,使得我们的编程模型更贴近事情的基本特性。

重构有可能会被一些语言所局限…… by Peng Shawn

事实上CreateNewRule方法是好的,但在某些更面向对象的语言里,这个重构并不理想。
我看到很多使用Java的程序也使用类似的重构,
createNewRule方法的名字取得太草率了,人们不知道为什么你会创建一个新的Rule对象,当看你的代码时,我会问为什么要创建一个新的对象?这个方法名可以更加有意义一些,我看方法内部的实现,有可能,我不清楚具体的业务,所以我猜测叫nextRule()会更好,假设你使用Java,你可能会把这个方法move到ColdRule这个类里去,最终的效果是
rule.nextRule();

just my guess~

Re: 学习到代码重构的知识了 by Zhao Qing

改善代码本来就是殊途同归的事情,无所谓抄不抄的吧。

Re: 学习到代码重构的知识了 by liang tang

只是这篇文章写的非常没有必要

Re: 学习到代码重构的知识了 by Peng Xiao

只是这篇文章写的非常没有必要

比这个回复有必要得多

Re: 《俺也说说一个方法做一件事儿!》 by hu yanbo

高手啊,有思想,你有没有一些例子可以分享一下,特别想学习你的设计思想。
请指教hyb_7374@126.com

似乎不可一概而论 by Lin Bruce

可能因为你有了先见“以为这一片代码就是完成给一个变量设值的时候”,所以才会在看到“它被加到了rules里面”时觉得“峰回路转”,所以才会觉得应该有CreateNewRule取而代之,但是我看着却觉得原先代码块并无不妥,比如可以理解为CloneToRules(至少此处Clone比Create更为贴切),即复制一个规则到规则列表。
当然这样似乎又违背了“函数应该只做一件事”的共识,但是在很多场合下,rules是rule的唯一归宿,即rule不单独存在,总归会容纳在rules里,此时就没有必要一定分出Create和Add两个操作,毕竟如果Create后必定要跟一个Add的话,他俩就没有必要单独存在了,以免发生Create后又不作任何后续处理的问题。
当然,实际情况下,更可能的形式是RuleList.Add(Rule& rule),这样就更清楚了。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

11 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT