i++引發的慘案

小胡哥哥發表於2018-09-24

導語

最近在做一個專案,該專案原來是由C++編寫的,現在需要翻譯成Java,在翻譯過程中,同事沒有理解清楚具體需求,在開發中進行了直譯,直到生產上出現故障了(測試居然沒測出來?)一起研究才發現...

需求

先看一下C++程式碼的樣子

    //vector<CorBillGroup>& vCorBillGroups 
	while(rs->next()){
		if(load(rs,bill)){
			//判斷分組
			corId = pm->getCorpId(bill.getPartyRoleId(),bill.getBillingCycleId());
			if (corId < 0)
				throw BusinessLogicException("找不到銀行劃扣分組ID", corId);
			int i=0;
			for ( i=0;i<vCorBillGroups.size();i++) {
				 if(vCorBillGroups[i].getCorperationId() == corId){
					 vCorBillGroups[i].add(bill);
					 break;
				 }
			}
			if(i == vCorBillGroups.size()) {
				CorBillGroup corBillGroup;
				corBillGroup.setCorperationId(corId);
				corBillGroup.add(bill);
				vCorBillGroups.push_back(corBillGroup);
			}
		}
	}
複製程式碼

然後看一下同事翻譯的第一版的樣子

 for (VirtualBillVO vo : bills) {
            long partyRoleId = vo.getPartyRoleId();
            Partner partner = getPartnerManager().getPartner(Integer.parseInt(partyRoleId + ""));
            corId = partner.getCorpId();

            if (corId < 0) {
                throw new BusinessLogicException("找不到銀行劃扣分組ID", corId);
            }
            int i = 0;
            for (CorBillGroupVO corVo : vCorBillGroups) {
                i++;
                if (corVo.getM_iCorperationId() == corId) {
                    corVo.add(vo);
                    break;
                }
            }
            if (i == vCorBillGroups.size()) {
                CorBillGroupVO corBillGroup = new CorBillGroupVO();
                corBillGroup.setM_iCorperationId(corId);
                corBillGroup.add(vo);
                vCorBillGroups.add(corBillGroup);
            }
        }
複製程式碼

分析

先不考慮這裡具體是幹啥的,單純從程式碼結構的角度來看,似乎程式碼是沒啥問題的。但是程式可不是看程式碼結構的! 由於C++程式碼有具體業務含義,簡單介紹一下這段程式碼的背景:給一組賬單(bills),按照運營商(corId)進行分組後返回。這麼簡單的需求,也不知道C++程式碼為啥寫的這麼複雜,這也導致翻譯這段程式碼的同事犯了糊塗。

  • 具體看C++: 迴圈賬單列表,取賬單的corId,i=0.迴圈分組vCorBillGroups,若分組裡有該corId就將該賬單新增到vCorBillGroups的第i個元素中,並跳出迴圈,判斷i 和vCorBillGroups長度是否相等,若在前面迴圈中有break,i是不可能與vCorBillGroups的長度相等的(迴圈vCorBillGroups.size()次,每次i++,則當迴圈正常結束後,i肯定與vCorBillGroups.size()相等,若有break,則i肯定小於vCorBillGroups.size()了);若前面迴圈中沒有break,說明該corId不在已經存在的組中,則新增到vCorBillGroups中。
  • 再看同事翻譯的Java:同樣迴圈賬單列表,但是採用了foreach的寫法遍歷,break條件滿足之前就i++,那麼在無break的時候是正常的,在有break的情況下就不一樣了,例如vCorBillGroups.size() = 1 時,遍歷一次vCorBillGroups,此時就算有break,i = 1,i ==vCorBillGroups.size();又會重新新增一次分組,那麼就與C++程式碼的含義不一樣了。
  • 區別在哪裡呢,仔細對比一下就會發現,兩處程式碼的區別就在i++的使用上,for(;;i++)中,若for迴圈中有break,是不會進行++操作的,同事翻譯時為了圖方便,隨意使用foreach,並將i++置於break條件之前,很明顯無法起到控制作用了!

解決

最簡單的解決方式就是如上述分析一樣,將i++放在break條件之後即可,但是分組程式碼真的要寫這麼晦澀嗎?見仁見智了...

總結

很少有人真的去注意for迴圈條件執行的順序,以及i++和++i的區別,我老大在看到這段程式碼的時候也猶豫了一下,後來才恍然大悟懷疑這個i++在break之後是否有執行,測試了一下才確定自己的想法。平時這種程式碼可能都是不會有問題的,但是一旦觸發了某種邊界條件,就不一定是你想象的樣子了。所以在寫程式碼之前,還是要明確自己要實現的功能是什麼,如果同事最開始翻譯程式碼的時候明確知道這是一段分組程式碼,我相信他絕對不會這樣去寫的!

相關文章