Tips of Java4 简化条件表达式

参考资料:《重构改善既有代码的设计》第九章 简化条件表达式

9.1  分解条件表达式

程序中,复杂的条件逻辑是最常导致代码复杂度上升的地点之一。检查不同的条件分支,根据不同分支做不同的事情,很快就会得到一个相当长的函数。很长的函数使得可读性下降,带有复杂条件逻辑的代码,包括条件逻辑代码和执行功能的代码会告诉你实现了什么功能,但往往要仔细的阅读整段代码才能正确理解。这就表明代码的可读性是不高的。好的代码应该带有自解释性!!

大块头的代码可以将其分解为多个独立的函数,根据函数实现的功能为它们命名,再用这些函数重新实现功能。对于条件逻辑,将分支条件分解成新函数可以让条件逻辑更加清晰。

具体做法:将if 、else分支段落提炼出来,构成一个独立的函数

范例:假设我要计算购买某样商品的总价(总价=数量*单价),而这个商品在冬季和夏季的单价是不同的:

    if (date.before (SUMMER_START) || date.after(SUMMER_END))
         charge = quantity * _winterRate + _winterServiceCharge;
     else charge = quantity * _summerRate;

我把每个分支的判断条件都提炼到一个独立函数中,如下所示:

if (notSummer(date))
         charge = winterCharge(quantity);
else charge = summerCharge (quantity);
 
private boolean notSummer(Date date) {
     return date.before (SUMMER_START) || date.after(SUMMER_END);
}
private double summerCharge(int quantity) {
     return quantity * _summerRate;
}
private double winterCharge(int quantity) {
     return quantity * _winterRate + _winterServiceCharge;
}

通过这段代码你可以看出,整个重构带来的清晰性。实际工作中,我会逐步进行每一次提炼,并在每次提炼之后编译并测试。

像这样的情况下,许多程序员都不会去提炼分支条件。因为这些分支条件往往非常短,看上去似乎没有提炼的必要。但是,尽管这些条件往往很短,在代码意图和代 码自身之间往往存在不小的差距。哪怕在上面这样一个小小例子中,notSummer(date) 这个语句也能够比原本的代码更好地表达自己的用途。对于原来的代码,我必须看着它,想一想,才能说出其作用。当然,在我们这个简单的例子中,这并不困难。不过,即使如此,提炼出来的函数可读性也更高一些——它看上去就像一段注释那样清楚而明白。(这段话堪称经典了!)

Ps: 自己的写法如下。如前面说的,好的代码应该带有自解释性,看到代码的变量名或者参数名就应该知道这个变量代表什么意思,或者这个函数实现了什么功能。如果notSummer这个函数不是经常使用的话,没有必要特地写成函数,更推荐引入“解释性变量”,例如bSummer代表是否是夏天。解释性函数或者解释性变量都会比 if (date.before (SUMMER_START) || date.after(SUMMER_END)) 可读性来的好。

boolean bSummer = date.before (SUMMER_START) || date.after(SUMMER_END);
if (bSummer)
         charge = winterCharge(quantity);
else charge = summerCharge (quantity);
 
private double summerCharge(int quantity) {
     return quantity * _summerRate;
}
private double winterCharge(int quantity) {
     return quantity * _winterRate + _winterServiceCharge;
}

范例2: 游戏中人物满足一定等级,并且尚未获得该奖励,则在界面上开放奖励的按钮。错误写法:

uiReward.gameObject.SetActive(requireLevel <= FRole.getLevel(roleId) && FWelfare.getWelfareInfo(roleId).getRewardStatus())

正确写法:

boolean bSatisfiedLevel = requireLevel <= FRole.getLevel(roleId);
boolean bAlreadyGotReward = FWelfare.getWelfareInfo(roleId).getRewardStatus();
boolean bOpen = bSatisfiedLevel && (!bAlreadyGotReward);
uiReward.gameObject.SetActive(bOpen)

 

9.4 Remove Control Flag(移除控制标记)

在一系列条件表达式中,你常常会看到「用以判断何时停止条件检查」的控制标记(control flag):

set done to false

while not done

   if (condition)

       do something

       set done to true

   next step of loop

这样的控制标记带来的麻烦超过了它所带来的便利。人们之所以会使用这样的控制标记,因为结构化编程原则告诉他们:每个子程序(routines)只能有一个入口(entry) 和一个出口(exit)。我赞同「单一入口」原则(而且现代编程语言也强迫我们这样做),但是「单一出口」原则会让你在代码中加入讨厌的控制标记,大大降低条件表达式的可读性。这就是编程语言提供break 语句和continue 语句的原因:你可以用它们跳出复杂的条件语句。去掉控制标记所产生的效果往往让你大吃一惊:条件语句真正的用途会清晰得多。

作法(Mechanics

对控制标记(control flags)的处理,最显而易见的办法就是使用Java 提供的break 语句或continue 语句。

  • 找出「让你得以跳出这段逻辑」的控制标记值。
  • 找出「将可跳出条件式之值赋予标记变量」的那个语句,代以恰当的break 语句或continue 语句。
  • 每次替换后,编译并测试。

在未能提供break 和continue 语句的编程语言中,我们可以使用另一种办法:

  • 运用Extract Method,将整段逻辑提炼到一个独立函数中。
  • 找出「让你得以跳出这段逻辑」的那些控制标记值。
  • 找出「将可跳出条件式之值赋予标记变量」的那个语句,代以恰当的return 语句。
  • 每次替换后,编译并测试。

即使在支持break 和continue 语句的编程语言中,我通常也优先考虑上述第二方案。因为return 语句可以非常清楚地表示:不再执行该函数中的其他任何代码。 如果还有这一类代码,你早晚需要将这段代码提炼出来。

 

9.5 以卫语句取代嵌套条件式

以卫语句取代嵌套条件式的精髓是给某一条分支以特别的重视。如果使用if-then-else 结构,你对if 分支和else 分支的重视是同等的。 这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。卫语句(guard clauses)就不同了,它告诉阅读者:『这种情况很罕见,如果它真的发生了,请做 一些必要的整理工作,然后退出。』

范例:

想像一个薪资系统,其中以特殊规则处理死亡员工、驻外员工、退休员工的薪资。这些情况不常有,但的确偶而会出现。

假设我在这个系统中看到下列代码:

 double getPayAmount() {
   double result;
   if (_isDead) result = deadAmount();
   else {
       if (_isSeparated) result = separatedAmount();
       else {
           if (_isRetired) result = retiredAmount();
           else result = normalPayAmount();
       };
   }
 return result;
 };

在这段代码中,非正常情况的检查掩盖了正常情况的检查,所以我应该使用『卫语句」来取代这些检查,以提高程序清晰度。我可以逐一引入卫语句。

修改:

double getPayAmount() {
   if (_isDead) return deadAmount();
   if (_isSeparated) return separatedAmount();
   if (_isRetired) return retiredAmount();
  return normalPayAmount();
 };

9.6 以多态取代条件表达式

9.7 引入Null对象(本质是多态的使用,特殊类)

9.8 断言

 

 

 

 

3.1 重复代码

function Dialog:OnShow(params)
	local approveRecords = self.m_data:GetApproveRecords()
	local today = approveRecords:GetApproveRecordsToday()
	local earlier = approveRecords:GetApproveRecordsEarlier()
	local bShowTodayRecord = self:DrawTableView(self.m_Fields.UITableView_Today, self.m_Fields.Image_TodayBG, today)
	local bShowEarlierRecord = self:DrawTableView(self.m_Fields.UITableView_Earlier, self.m_Fields.Image_EarlierBG, earlier)
	local bShowUIGroupRecord = bShowTodayRecord or bShowEarlierRecord
	self.m_Fields.UIGroup_Record.gameObject:SetActive(bShowUIGroupRecord)
	self.m_Fields.UIGroup_NoInfo.gameObject:SetActive(not bShowUIGroupRecord)
end

function Dialog:DrawTableView(uiTableView,uiBG,dataRecord)
	local bShow = #dataRecord ~= 0
	uiBG.gameObject:SetActive(bShow)
	uiTableView.gameObject:SetActive(bShow)
	if bShow then
	  uiTableView:DrawList(#dataRecord,function(index,item)
	    local record = dataRecord[index + 1]
	    item.data = record
	    if not record then return end
	    item.m_Fields.Text_Record.text = self:ComposeEntry(record.time, record.otherrolename)
	  end)
	end
	return bShow
end

 

你可能感兴趣的:(Java)