- 浏览: 1431548 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (363)
- oracle (43)
- 缓存策略 (4)
- 数据库设计 (7)
- IntelliJ IDEA (8)
- freemarker (3)
- JSP (12)
- java基础 (75)
- tomcat (14)
- 服务器 (10)
- maven (3)
- linux (21)
- e-learning (2)
- 手机开发 (1)
- 设计模式 (6)
- 技术之外 (2)
- 工具的使用 (8)
- HTML 5 (5)
- 网站前台 (38)
- 网站后台 (11)
- 项目管理 (12)
- 面试题 (5)
- 幽默 (0)
- 系统架构 (8)
- 创业 (10)
- div-css (5)
- 诗词歌赋 (1)
- 网络课堂组知识库 (4)
- 杂家 (3)
- android (12)
- 框架 (14)
- 读书笔记 (1)
- 孙子兵法 (1)
- mysql (3)
- 小人书 (0)
- Jquery (6)
- eclipse (2)
- 正则表达式 (1)
最新评论
-
a98709474:
写的很详细,看完了,明白了这种概念了
数据库水平切分(拆库拆表)的实现原理解析(转) -
a98709474:
MYSQL分库分表总结 -
CatchU:
7年前的招聘要求,厉害厉害
面试要求 -
luozhixiong:
<table class="bbcode&qu ...
MYSQL分库分表总结 -
沈寅麟:
好用
freemarker格式化
原文:http://www.infoq.com/cn/news/2010/11/ugly-code-0
开篇
我是一个程序员,也是一个咨询师。成为咨询师之后,我有机会在不同的项目中穿梭。同客户合作的过程中,我经常干的一件事是:code diff,也就是用源码管理工具的diff功能把当天全部修改拿出来,从编码的角度来分析代码写得怎么样。
因为这个工作,我看到了许多不同人编写的代码,我的编码底线不断受到挑战。许多东西,我以为是常识,但实际上不为许多人所知。比如,下面这段代码,你会做何感想?
有的人会想,怎么写得这么笨啊!但是,请放心,绝对会有人这么想:挺好的,实现功能了。这并非我臆造出的代码,而是源自一个真实的codebase。
这些代码的存在,给了我很多机会与人分享一些编码的心得。其间,有人建议,为什么不能把你说的这些内容写下来,与更多人分享。于是,有了这个即将看到的系列:《代码之丑》,以此向《代码之美》致敬。
最后要说的是,上面那段代码可以写成这样:
刘文涛注: 我觉得如果是:
这样的代码 ,应该改成:
没必要的 else{..} 就别写了,可读性不好。
代码之丑(一)——让判断条件做真正的选择
诸位看官,上代码:
乍一看,这段代码还算比较简短。那下面这段呢?
看出来问题了吗?经过仔细的对比,我们发现,如此华丽的代码,if/else的执行语句真正的差异只在于一个参数。第一段代码,二者的差异只是发送的消息,第二段代码,差异在于最后那个参数。
看破这个差异之后,新的写法就呼之欲出了,以第一段代码为例:
为了节省篇幅,我选择了条件表达式。我知道,很多人不是那么喜欢它。如果if/else依旧是你的大爱,勇敢追求去吧!
由这段代码调整过程,我们得出一个简单的规则:
让判断条件做真正的选择。
对于前面调整的代码,判断条件真正判断的内容是消息的内容,而不是消息发送的过程。经过我们的调整,获取消息内容和发送消息的过程严格分离开来。
消除了代码中的冗余,代码也更容易理解,同时,给未来留出了可扩展性。如果将来retCode还有更多的情形,我们只要调整消息获取的部分进行调整就好了。当然,封装成函数是一个更好的选择,这样代码就变成了:
至于第二段代码的调整,留给你练手了。
代码之丑(二)——长长的条件
这是一个长长的判断条件:
之所以注意到它,因为最后两个条件是在最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是可以接受的。但这是遗留代码,每次可能只改了一两行,通常我们会不只一次踏入这片土地。经年累月,代码成了这个样子。
就我接触过的代码而言,这并不是最长的判断条件。这种代码极大的开拓了我的视野,现在的我,即便面前是一屏无法容纳的条件,也可以坦然面对了,虽然显示器越来越大。
其实,如果这个判断条件是这个函数里仅有的东西,我也是可以接受的。遗憾的是,大多数情况下,这只不过是一个更大函数中的一小段而已。为了让这段代码可以接受一些,我们不妨稍做封装:
现在,虽然条件依然还是很多,但比起原来庞大的函数,至少它已经被控制在一个相对较小的函数里了。更重要的是,通过函数名,我们终于有机会告诉世人这段代码判断的是什么了。
虽然提取函数把这段代码混乱的条件分离开来,它还是可以继续改进的。比如,我们把判断的条件进一步提取:
这样的话,如果以后要加一个新的type,只要在数组中增加一个新的元素即可。
代码之丑(五)——不受欢迎的大心脏
不知道为什么,初见它时,我想起了郭芙蓉的排山倒海:
就在我以为这一片代码就是完成给一个变量设值的时候,突然,在那个不起眼的角落里,这个变量得到了应用:它被加到了rules里面。什么叫峰回路转,这就是。
既然它给了我们这么有趣的体验,必然先杀后快。下面重构了这个函数:
把这一堆设值操作提取了出来,整个函数看上去一下子就清爽了。不是因为代码变少了,而是因为代码按照它职责进行了划分:创建的归创建,加入的归加入。之前的代码之所以让我不安,多重职责是罪魁祸首。一旦把这个函数提取出来,做完这步操作,我们就不难发现这个函数应该成为CodeRule类的一部分。篇幅所限,就不再继续了。
谈论干净代码时,我们总会说,函数应该只做一件事。函数做的事越多,就会越冗长,也就越难发现不同函数内存在的相似之处。为了一个问题,要在不同的地方改来改去也就难以避免了。但面对长长的函数,还是有人无动于衷,继续往里塞着“新”代码。
即便大家都认同了函数应该只做一件事,但多大的一件事算是一件事呢!不同的人心里有不同的标准。有人甚至认为一个功能就是一件事。于是,代码会越来越刺激。想写干净代码,就别怕事小。哪怕一个函数只有一行,只要它能完整的表达一件事。在干净代码的世界里,大心脏是不受喜欢的。
接下来,我需要用历经沧桑的口吻告诉你,这么跌宕起伏的代码也只不过是一个更大函数的一个部分。此刻,浮现在我脑海里的是层峦叠嶂的山峰。
代码之丑(六)——分家的声明和使用
这是一段长长的C++代码,我的问题是:relaPri、relaSec和 scoutBySec这三个变量在哪里用到了?
经过认真仔细的查看,或是使用传说的中“查找”功能,我们发现上面提到的那三个变量只在最后用了一下。
不知道你是否注意到,我在最初特意强调了一下这是C++代码。这意味着,变量可以随用随声明,而不必像传统的C程序那样,只能在函数的开头把函数内部用到的变量一口气声明。 那么 ,我们就让声明和使用团聚吧!
当声明和使用走到一起,我们的观察就有了新的视角,其实,这几个变量完全是可以不声明的,于是,代码再进一步:
看到这里,我们就可以看出原来的做法到底有多么浪费:浪费时间给变量起名字——我们都知道,起个好名字不容易,也 浪费了时间在执行上,修改前的代码创建了两个XString对象,而修改后,只创建了一个对象。
或许,你会觉得,有个变量会让我们了解这里实际上填加的内容到底是什么。不过,也许一个好的函数命名才是更好的选择,比如addRelaPri。这个疑问会揭示出这段代码存在另外一个问题,直接使用基本的数据结构而没有进行封装。不过,这不是这里讨论的目标,就到此打住吧!
根据这段代码的调整,我们得出一条规则:
有的C程序员会暗自念叨,这个要求对C程序来说,简直太不合情理了。好吧!我承认,从语言的角度来说,是这样的。但是,我们需要仔细想想,为什么对于C语言来说,变量的声明和使用会距离遥远。通常,遥远的背后意味着硕大的函数,这才是让声明和使用天各一方的重要原因。
在干净代码的世界里,大函数永远是不受欢迎的。为了让声明和使用尽早团聚,请把函数写小。
代码之丑(九)——退让的缩进
这是一个让我纠结了很久的话题:缩进。
不是因为它不够“丑”,而是表现它不那么容易。找出一段能表现它特点的代码轻而易举,但放到一篇文章里,大片的代码还是容易让人怀疑我在偷懒。
咬咬牙,我还是拿出了一段。就是这样一段已经缩进很多层的代码,实际上,也只不过是一个更大缩进中的一小段。而且,省略号告诉我们,后面还有。
回到这段代码上,能出现多层缩进,for循环功不可没。出现这种循环,很多情况下,都是对一个集合进行处理,而循环里的内容,就是对集合里的每一个元素进行处理。这里也不例外。所以,我们先做一次提取:
至此,我们去掉了一层缩进,而且因为这个提取,语义也变得很清晰:这个新函数只是处理集合里的一个元素。
接下来,这个函数里面长长的代码是对IsCallFunc进行设值,后面省略的部分会根据这里求出的结果进行处理。所以,这里把processAttr进一步分拆:
缩进还有,如果有兴趣,还可以继续分解。这里就到此为止吧!
多层缩进是那种放在代码海一眼就可以认出来的代码,用一条简单的规则就可以限制它:
代码之丑(十二)--无状态方法
诸位Java程序员,想必大家对SimpleDateFormat并不陌生。不过,你是否知道,SimpleDateFormat不是线程安全的(thread safe)。这意味着,下面的代码是错误的:
从功能的角度上看,单独执行这段代码是没有问题的,但放到多线程环境下,因为SimpleDateFormat不是线程安全的,这段代码就会出错。所以,要想让这段代码正确,我们只要稍做微调:
不知你是否注意到,这里的调整只是由原来的共享format这个变量,变成了每次调用这个方法时创建出一个新的SimpleDateFormat变量。
作为一个专业程序员,我们当然知道,相比于共享一个变量的开销要比每次创建小。之所以我们必须这么做,是因为SimpleDateFormat不是线程安全的。但从SimpleDateFormat提供给我们的接口上来看,实在让人看不出它与线程安全有和相干。那接下来,我们就要打开JDK的源码,看一下其中的代码之丑。
如果你手头没有JDK的源码,这里是个不错的参考。
在format方法里,有这样一段代码:
其中,calendar是DateFormat的protected字段。这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。
想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
线程1调用format方法,改变了calendar这个字段。
中断来了。
线程2开始执行,它也改变了calendar。
又中断了。
线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。
BANG!!! 稍微花点时间分析一下format的实现,我们便不难发现,用到calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
这个问题背后隐藏着一个更为重要的问题:无状态。
无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。
写程序,我们要尽量编写无状态方法。
引用
作者简介:
郑晔,ThoughtWorks公司咨询师,拥有多年企业级软件开发经验,热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式,加入ThoughtWorks公司后,投入到敏捷开发方法的实践之中,为其他公司提供敏捷开发方法方面的咨询服务。他的blog是梦想风暴。
郑晔,ThoughtWorks公司咨询师,拥有多年企业级软件开发经验,热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式,加入ThoughtWorks公司后,投入到敏捷开发方法的实践之中,为其他公司提供敏捷开发方法方面的咨询服务。他的blog是梦想风暴。
开篇
我是一个程序员,也是一个咨询师。成为咨询师之后,我有机会在不同的项目中穿梭。同客户合作的过程中,我经常干的一件事是:code diff,也就是用源码管理工具的diff功能把当天全部修改拿出来,从编码的角度来分析代码写得怎么样。
因为这个工作,我看到了许多不同人编写的代码,我的编码底线不断受到挑战。许多东西,我以为是常识,但实际上不为许多人所知。比如,下面这段代码,你会做何感想?
if(db.Next()) { return true; } else { return false; }
有的人会想,怎么写得这么笨啊!但是,请放心,绝对会有人这么想:挺好的,实现功能了。这并非我臆造出的代码,而是源自一个真实的codebase。
这些代码的存在,给了我很多机会与人分享一些编码的心得。其间,有人建议,为什么不能把你说的这些内容写下来,与更多人分享。于是,有了这个即将看到的系列:《代码之丑》,以此向《代码之美》致敬。
最后要说的是,上面那段代码可以写成这样:
return db.Next();
刘文涛注: 我觉得如果是:
if(db.Next()) { ..... return true; } else { .... return false; }
这样的代码 ,应该改成:
if(db.Next()) { ..... return true; } .... return false;
没必要的 else{..} 就别写了,可读性不好。
代码之丑(一)——让判断条件做真正的选择
诸位看官,上代码:
if (0 == retCode) { SendMsg("000", "Process Success", outResult); } else { SendMsg("000", "Process Failure", outResult); }
乍一看,这段代码还算比较简短。那下面这段呢?
if(!strcmp(pRec->GetType(), RECTYPE::INSTALL)) { CommDM.ChangGroupInfo(const_cast(CommDM.GetAttr("IPAddress", &(pGroup->m_Attr))), true); } else { CommDM.ChangGroupInfo(const_cast(CommDM.GetAttr("IPAddress", &(pGroup->m_Attr))), false); }
看出来问题了吗?经过仔细的对比,我们发现,如此华丽的代码,if/else的执行语句真正的差异只在于一个参数。第一段代码,二者的差异只是发送的消息,第二段代码,差异在于最后那个参数。
看破这个差异之后,新的写法就呼之欲出了,以第一段代码为例:
String msg = (0 == retCode ? "Process Success" : "Process Failure"); SendMsg("000", msg, outResult);
为了节省篇幅,我选择了条件表达式。我知道,很多人不是那么喜欢它。如果if/else依旧是你的大爱,勇敢追求去吧!
由这段代码调整过程,我们得出一个简单的规则:
让判断条件做真正的选择。
对于前面调整的代码,判断条件真正判断的内容是消息的内容,而不是消息发送的过程。经过我们的调整,获取消息内容和发送消息的过程严格分离开来。
消除了代码中的冗余,代码也更容易理解,同时,给未来留出了可扩展性。如果将来retCode还有更多的情形,我们只要调整消息获取的部分进行调整就好了。当然,封装成函数是一个更好的选择,这样代码就变成了:
SendMsg("000", msgFromRetCode(retCode),outResult);
至于第二段代码的调整,留给你练手了。
代码之丑(二)——长长的条件
这是一个长长的判断条件:
if (strcmp(type, “DropGroup") == 0 || strcmp(type, "CancelUserGroup") == 0 || strcmp(type, "QFUserGroup") == 0 || strcmp(type, "CancelQFUserGroup") == 0 || strcmp(type, "QZUserGroup") == 0 || strcmp(type, "CancelQZUserGroup") == 0 || strcmp(type, "SQUserGroup") == 0 || strcmp(type, "CancelSQUserGroup") == 0 || strcmp(type, “UseGroup") == 0 || strcmp(type, "CancelGroup") == 0)
之所以注意到它,因为最后两个条件是在最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是可以接受的。但这是遗留代码,每次可能只改了一两行,通常我们会不只一次踏入这片土地。经年累月,代码成了这个样子。
就我接触过的代码而言,这并不是最长的判断条件。这种代码极大的开拓了我的视野,现在的我,即便面前是一屏无法容纳的条件,也可以坦然面对了,虽然显示器越来越大。
其实,如果这个判断条件是这个函数里仅有的东西,我也是可以接受的。遗憾的是,大多数情况下,这只不过是一个更大函数中的一小段而已。为了让这段代码可以接受一些,我们不妨稍做封装:
private boolean shouldExecute(String type) { return (strcmp(type, “DropGroup") == 0 || strcmp(type, "CancelUserGroup") == 0 || strcmp(type, "QFUserGroup") == 0 || strcmp(type, "CancelQFUserGroup") == 0 || strcmp(type, "QZUserGroup") == 0 || strcmp(type, "CancelQZUserGroup") == 0 || strcmp(type, "SQUserGroup") == 0 || strcmp(type, "CancelSQUserGroup") == 0 || strcmp(type, “UseGroup") == 0 || strcmp(type, "CancelGroup") == 0); }
if (shouldExecute(type)) { ... }
现在,虽然条件依然还是很多,但比起原来庞大的函数,至少它已经被控制在一个相对较小的函数里了。更重要的是,通过函数名,我们终于有机会告诉世人这段代码判断的是什么了。
虽然提取函数把这段代码混乱的条件分离开来,它还是可以继续改进的。比如,我们把判断的条件进一步提取:
private boolean shouldExecute(String type) { String [] types= { "DropGroup", "CancelUserGroup", "QFUserGroup", "CancelQFUserGroup", "QZUserGroup", "CancelQZUserGroup", "SQUserGroup", "CancelSQUserGroup", "UseGroup", "CancelGroup" }; int size = types.size; for (int i = 0; i < size; i++) { if (strcmp(type, types) == 0) { return true; } } return false; }
这样的话,如果以后要加一个新的type,只要在数组中增加一个新的元素即可。
代码之丑(五)——不受欢迎的大心脏
不知道为什么,初见它时,我想起了郭芙蓉的排山倒海:
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类的一部分。篇幅所限,就不再继续了。
谈论干净代码时,我们总会说,函数应该只做一件事。函数做的事越多,就会越冗长,也就越难发现不同函数内存在的相似之处。为了一个问题,要在不同的地方改来改去也就难以避免了。但面对长长的函数,还是有人无动于衷,继续往里塞着“新”代码。
即便大家都认同了函数应该只做一件事,但多大的一件事算是一件事呢!不同的人心里有不同的标准。有人甚至认为一个功能就是一件事。于是,代码会越来越刺激。想写干净代码,就别怕事小。哪怕一个函数只有一行,只要它能完整的表达一件事。在干净代码的世界里,大心脏是不受喜欢的。
接下来,我需要用历经沧桑的口吻告诉你,这么跌宕起伏的代码也只不过是一个更大函数的一个部分。此刻,浮现在我脑海里的是层峦叠嶂的山峰。
代码之丑(六)——分家的声明和使用
这是一段长长的C++代码,我的问题是:relaPri、relaSec和 scoutBySec这三个变量在哪里用到了?
void DealForServiceA(const char *oprCode, const char *subID, const char *oID, XList *callCicsList) { XString relaPri(“NULL”); XString relaSec(“NULL”); XString scoutBySec(“0”); XList *tempList = new XList ; callCicsList->Add(tempList); tempList->Add(new XString(oprCode)); tempList->Add(new XString(oID)); XString *psTelNum = new XString; tempList->Add(psTelNum); GetServnumberBySubsID(subID, *psTelNum); tempList->Add(new XString(relaPri.table { font-size: 10pt;}c_str())); tempList->Add(new XString(relaSec.c_str())); tempList->Add(new XString(scoutBySec.c_str())); }
经过认真仔细的查看,或是使用传说的中“查找”功能,我们发现上面提到的那三个变量只在最后用了一下。
不知道你是否注意到,我在最初特意强调了一下这是C++代码。这意味着,变量可以随用随声明,而不必像传统的C程序那样,只能在函数的开头把函数内部用到的变量一口气声明。 那么 ,我们就让声明和使用团聚吧!
XString relaPri(“NULL”); tempList->Add(new XString(relaPri.c_str())); XString relaSec(“NULL”); tempList->Add(new XString(relaSec.c_str())); XString scoutBySec(“0”); tempList->Add(new XString(scoutBySec.c_str()));
当声明和使用走到一起,我们的观察就有了新的视角,其实,这几个变量完全是可以不声明的,于是,代码再进一步:
tempList->Add(new XString(“NULL”)); tempList->Add(new XString(“NULL”)); tempList->Add(new XString(“0”));
看到这里,我们就可以看出原来的做法到底有多么浪费:浪费时间给变量起名字——我们都知道,起个好名字不容易,也 浪费了时间在执行上,修改前的代码创建了两个XString对象,而修改后,只创建了一个对象。
或许,你会觉得,有个变量会让我们了解这里实际上填加的内容到底是什么。不过,也许一个好的函数命名才是更好的选择,比如addRelaPri。这个疑问会揭示出这段代码存在另外一个问题,直接使用基本的数据结构而没有进行封装。不过,这不是这里讨论的目标,就到此打住吧!
根据这段代码的调整,我们得出一条规则:
引用
代码的声明和使用应尽量接近。
有的C程序员会暗自念叨,这个要求对C程序来说,简直太不合情理了。好吧!我承认,从语言的角度来说,是这样的。但是,我们需要仔细想想,为什么对于C语言来说,变量的声明和使用会距离遥远。通常,遥远的背后意味着硕大的函数,这才是让声明和使用天各一方的重要原因。
在干净代码的世界里,大函数永远是不受欢迎的。为了让声明和使用尽早团聚,请把函数写小。
代码之丑(九)——退让的缩进
这是一个让我纠结了很久的话题:缩进。
for (int j = 0; j < attributes.size(); j++) { Attr *attr = attributes.get(j); if (attr == NULL ) { continue; } int IsCallFunc = -1; if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { IsCallFunc = 1; } else if(0 == strcmp(attr->attrValue(), "0")) { IsCallFunc = 0; } } } else if (attr->status() == STATUS_DELETED) { IsCallFunc = 0; } ... }
不是因为它不够“丑”,而是表现它不那么容易。找出一段能表现它特点的代码轻而易举,但放到一篇文章里,大片的代码还是容易让人怀疑我在偷懒。
咬咬牙,我还是拿出了一段。就是这样一段已经缩进很多层的代码,实际上,也只不过是一个更大缩进中的一小段。而且,省略号告诉我们,后面还有。
回到这段代码上,能出现多层缩进,for循环功不可没。出现这种循环,很多情况下,都是对一个集合进行处理,而循环里的内容,就是对集合里的每一个元素进行处理。这里也不例外。所以,我们先做一次提取:
for (int j = 0; j < attributes.size(); j++) { processAttr(attributes.get(j)); } void processAttr(Attr *attr) { if (attr == NULL ) { return; } int IsCallFunc = -1; if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { IsCallFunc = 1; } else if(0 == strcmp(attr->attrValue(), "0")) { IsCallFunc = 0; } } } else if (attr->status() == STATUS_DELETED) { IsCallFunc = 0; } ... }
至此,我们去掉了一层缩进,而且因为这个提取,语义也变得很清晰:这个新函数只是处理集合里的一个元素。
接下来,这个函数里面长长的代码是对IsCallFunc进行设值,后面省略的部分会根据这里求出的结果进行处理。所以,这里把processAttr进一步分拆:
void processAttr(Attr *attr) { if (attr == NULL ) { return; } int IsCallFunc = isCallFunc(attr); ...... }
int isCallFunc(Attr *attr) { if(attr->status() == STATUS_NEW || attr->status() == STATUS_MODIFIED) { if(strcmp(attr->attrID(), "CallFunc") == 0) { if(0 == strcmp(attr->attrValue(), "1")) { return 1; } else if(0 == strcmp(attr->attrValue(), "0")) { return 0; } } } else if (attr->status() == STATUS_DELETED) { return 0; } return -1; }
缩进还有,如果有兴趣,还可以继续分解。这里就到此为止吧!
多层缩进是那种放在代码海一眼就可以认出来的代码,用一条简单的规则就可以限制它:
引用
不允许出现多层缩进。
代码之丑(十二)--无状态方法
诸位Java程序员,想必大家对SimpleDateFormat并不陌生。不过,你是否知道,SimpleDateFormat不是线程安全的(thread safe)。这意味着,下面的代码是错误的:
class Sample { private static final DateFormat format = new SimpleDateFormat("yyyy.MM.dd"); public String getCurrentDateText() { return format.format(new Date()); } }
从功能的角度上看,单独执行这段代码是没有问题的,但放到多线程环境下,因为SimpleDateFormat不是线程安全的,这段代码就会出错。所以,要想让这段代码正确,我们只要稍做微调:
public class Sample { public String getCurrentDateText() { return new SimpleDateFormat("yyyy.MM.dd").format(new Date()); } }
不知你是否注意到,这里的调整只是由原来的共享format这个变量,变成了每次调用这个方法时创建出一个新的SimpleDateFormat变量。
作为一个专业程序员,我们当然知道,相比于共享一个变量的开销要比每次创建小。之所以我们必须这么做,是因为SimpleDateFormat不是线程安全的。但从SimpleDateFormat提供给我们的接口上来看,实在让人看不出它与线程安全有和相干。那接下来,我们就要打开JDK的源码,看一下其中的代码之丑。
如果你手头没有JDK的源码,这里是个不错的参考。
在format方法里,有这样一段代码:
calendar.setTime(date);
其中,calendar是DateFormat的protected字段。这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。
想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
线程1调用format方法,改变了calendar这个字段。
中断来了。
线程2开始执行,它也改变了calendar。
又中断了。
线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。
BANG!!! 稍微花点时间分析一下format的实现,我们便不难发现,用到calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
这个问题背后隐藏着一个更为重要的问题:无状态。
无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。
写程序,我们要尽量编写无状态方法。
发表评论
-
打印IP 来源
2014-10-16 22:07 946<% String userAgent = reques ... -
java 写文本换行
2014-08-08 18:34 1977import org.apache.commons.io.Fi ... -
DBCP连接池配置示例
2014-06-17 11:44 1246<bean id="dataSourceOra ... -
CGLIB与asm
2014-05-05 14:42 1349“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言 ... -
201404转成 2014.04
2014-03-31 17:45 999public static void main(Str ... -
取出 字符串中的 网址
2013-10-15 17:47 1268public static void main(Str ... -
java反国际化
2013-09-23 18:10 1016public static String fromEn ... -
AtomicInteger与Volatile修饰
2013-02-05 11:50 6047今天写代码,尝试使用了AtomicInteger这个类,感觉 ... -
ExecutorService线程池
2013-02-05 11:40 1624http://blog.sina.com.cn/s/blog ... -
web.xml中load-on-startup的作用
2012-12-24 10:06 1665如下一段配置,熟悉DWR的再熟悉不过了: <servle ... -
最新版本
2012-09-13 09:28 1013Java SE 7u7 apache-tomcat-7.0. ... -
格式化数字
2012-09-10 20:12 906public static void main(Str ... -
用apache common io包获得文件扩展名
2012-09-09 23:11 1371apache common io包包含了很多非常实用的工具类, ... -
值传递,引用传递
2012-07-28 23:29 1145java 对于 8种基本类型 和 他们的包装类型 , 外 ... -
Memcache
2012-06-27 09:36 1046Memcache是一个高性能的分布式的内存对象缓存系统,通过在 ... -
ActiveMQ
2012-06-14 15:09 15159ActiveMQ 是Apache出品,最流行的,能力强劲的开源 ... -
proxool配置
2012-06-12 11:43 1185项目结构 所需jar包 proxool.prope ... -
各种文件的注释
2012-04-23 10:44 1008<%-- comment --%> JSP注释,也称为“隐藏注 ... -
List排序
2012-03-27 20:18 1202集合类List存放的数据,默认是按照放入时的顺序存放的,比如依 ... -
动态语言和静态语言
2012-03-25 22:36 1412动态语言也称为脚本语 ...
相关推荐
一段很经典的代码,希望给大家能带来帮助!
在计算机科学中,"丑数"(也称为"好数")是指仅包含质因数2、3和5的正整数。它们是能够被这三个质数的任意次幂整除的数字。例如,1、2、3、4、5、6、8、9、10、12等都是丑数。本项目是用C语言实现的一个程序,用于...
而Python虽然运行速度相对较慢,但其简洁的语法和丰富的库函数使得代码编写更为快速和直观。 为了进一步优化,我们还可以考虑使用记忆化搜索,这是一种结合了递归和动态规划的技术。它通过记录已计算过的子问题结果...
代码中定义了一个名为`Solution`的类,并实现了一个`GetUglyNumber_Solution`的方法。这个方法接收一个参数`index`,表示要找的第N个丑数。 首先,方法检查`index`是否小于1,如果是,则返回0,因为1是第一个丑数。...
3. **《敏捷软件开发》**一书中指出:“最好的软件开发人员都知道一个秘密:美的东西比丑的东西创建起来更廉价,也更快捷。”这强调了优雅代码的经济效益和长期价值。 #### 六、总结 编写优雅的代码不仅关乎个人的...
各种数据结构、算法及实用的C#源...C#,超级阿格里数字(超级丑数,Super Ugly Number)的算法与源代码 超级阿格里数字(超级丑数,Super Ugly Number)由丑数(Ugly Number)拓展而来,不过其因子质数,是事先给定的。
使用Java编写代码,判断一个给定的数是否为丑数。
此外,天干地支是农历日期的一种表达方式,由十天干(甲、乙、丙、丁、戊、己、庚、辛、壬、癸)和十二地支(子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥)组合而成,用于记录年、月、日、时。 在C#中实现农历...
丑数,又称“不规则数”,是指一个正整数,其所有的质因数仅包含2、3和5。这个问题要求我们找到第1500个丑数。在数学上,解决此类问题通常需要构建一...在编程实践中,优化算法和数据结构的选择是提高代码性能的关键。
天干有甲、乙、丙、丁、戊、己、庚、辛、壬、癸共十位,地支有子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥十二位。它们结合可以用来表示年、月、日和时,形成六十甲子,是中国传统文化中的重要组成部分。 在...
典型的分水岭算法代码,附带参考代码的网址,可以用于参考及查阅资料。
在C语言编程中,"丑数"通常是指那些只包含质因子2、3和5的正整数。例如,1、2、3、4、5、6、8、9、10、12等都是丑数,因为...通过这个题目,你可以深入练习如何用C语言实现高效算法,并提高逻辑思维和代码组织能力。
天干有甲、乙、丙、丁、戊、己、庚、辛、壬、癸共十种,地支有子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥十二种,两者相互搭配形成四柱,分别是年柱、月柱、日柱和时柱,每柱两个字,共计八字。 四柱排盘的...
飞机有点丑,看懂的可以自调 代码很烂,字符数组 要改得改多处,不建议改 有难度,敌人会发射子弹,上下左右控制,x射击 速度较慢,看懂的可以自调 子弹碰撞抵消 飞机撞墙自杀 飞机碰撞自杀 怪物自动刷新 血量很高,...
描述中的 "有点丑" 可能是指代码的可读性或者效率不高,这可以通过重构代码、优化算法或者增加注释来改善。如果允许自定义难度级别,可能涉及调整迷宫的大小、复杂度或者障碍物分布等。 在实际项目中,走迷宫代码还...
- 运行代码后,会输出前1500个丑数的序列,例如:1、2、3、4、5、6、8、9、10等,这些数都是由2、3或5的乘积构成的。 通过这个实验,不仅可以学习到如何用C语言实现判断丑数的逻辑,还能理解如何使用循环和函数...
代码中定义了一个名为`bases`的向量,存储了丑数可能的质因子2、3和5。接下来,创建了一个`priority_queue`,类型为`long`,并且设置为小顶堆。同时,我们使用了一个`unordered_set`来存储已经出现过的丑数,避免...
标题中的"ST25R3916可以跑的代码"指的是使用STMicroelectronics的ST25R3916 NFC(近场通信)控制器的软件代码,这是一款高性能、低功耗的设备,适用于接触式和非接触式通信应用。STM是STMicroelectronics的简称,是...
标题中的“python-leetcode面试题解之第263题丑数.zip”表明这是一个关于Python编程语言在LeetCode面试中的应用,具体聚焦于解决第263题——丑数的问题。LeetCode是一个在线平台,提供了大量的编程题目,帮助开发者...