代码之丑(一)——让判断条件作真正的选择java
if (0 == retCode) { SendMsg("000", "Process Success", outResult); } else { SendMsg("000", "Process Failure", outResult); }
看出来问题了吗?通过仔细的对比,咱们发现,如此华丽的代码,if/else的执行语句真正的差别只在于一个参数。第一段代码,两者的差别只是发送的消息,第二段代码,差别在于最后那个参数。
看破这个差别以后,新的写法就呼之欲出了,以第一段代码为例:
Java代码 程序员
String msg = (0 == retCode ? "Process Success" : "Process Failure");
SendMsg("000", msg, outResult);
由这段代码调整过程,咱们得出一个简单的规则:
让判断条件作真正的选择。
对于前面调整的代码,判断条件真正判断的内容是消息的内容,而不是消息发送的过程。通过咱们的调整,获取消息内容和发送消息的过程严格分离开来。
消除了代码中的冗余,代码也更容易理解,同时,给将来留出了可扩展性。若是未来retCode还有更多的情形,咱们只要调整消息获取的部分进行调整就行了。固然,封装成函数是一个更好的选择,这样代码就变成了:数组
SendMsg("000", msgFromRetCode(retCode),outResult);
代码之丑(二)——长长的条件
这是一个长长的判断条件:安全
Java代码多线程
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)
之因此注意到它,由于最后两个条件是在最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是能够接受的。但这是遗留代码,每次可能只改了一两行,一般咱们会不仅一次踏入这片土地。经年累月,代码成了这个样子。函数
为了让这段代码能够接受一些,咱们不妨稍作封装:spa
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); }
Java代码线程
if (shouldExecute(type)) { ... }
如今,虽然条件依然仍是不少,但比起原来庞大的函数,至少它已经被控制在一个相对较小的函数里了。设计
虽然提取函数把这段代码混乱的条件分离开来,它仍是能够继续改进的。好比,咱们把判断的条件进一步提取:code
Java代码
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,只要在数组中增长一个新的元素便可。
代码之丑(三)——不受欢迎的大心脏
Java代码
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里面。什么叫峰回路转,这就是。
既然它给了咱们这么有趣的体验,必然先杀后快。下面重构了这个函数:
Java代码
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; }
Java代码
rules.Add(CreateNewRule(oldRule));
把这一堆设值操做提取了出来,整个函数看上去一会儿就清爽了。不是由于代码变少了,而是由于代码按照它职责进行了划分:建立的归建立,加入的归加入。以前的代码之因此让我不安,多重职责是罪魁祸首。一旦把这个函数提取出来,作完这步操做,咱们就不难发现这个函数应该成为CodeRule类的一部分。
代码之丑(四)——退让的缩进
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循环功不可没。出现这种循环,不少状况下,都是对一个集合进行处理,而循环里的内容,就是对集合里的每个元素进行处理。这里也不例外。因此,咱们先作一次提取:
Java代码
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进一步分拆:
Java代码
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)。这意味着,下面的代码是错误的:
Java代码
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字段,因此,它是有状态的。 写程序,咱们要尽可能编写无状态方法。