一個switch case引起的線上故障

故障過程
1、上午的時候,QA同學(xué)突然說,測試自動化的流程突然過不去了,問我是不是最近對線上做了某些修改。當(dāng)時第一反應(yīng)是不可能
2、通過QA同學(xué)提供的test case,在測試環(huán)境通過curl發(fā)送請求,發(fā)現(xiàn)果然廣告返回值跟預(yù)期不符。
3、通過git log對比,發(fā)現(xiàn)近期只有一個switch語句有修改。
4、嘗試在代碼中加入log語句,發(fā)現(xiàn)日志輸出果然跟QA的錯誤結(jié)果一致,至此原因找到。
故障原因
下面是錯誤代碼
switch?(dsp_res->bid_type())?{
????????case?0:
????????{
??????????auto?info?=?dsp_response->add_dsp_res_infos();
??????????info->set_dsp_id(item.dsp_id());
??????????if?(dsp_res->has_cache_duration())?{
????????????info->set_duration(dsp_res->cache_duration());
??????????}
??????????if?(dsp_res->has_quality())?{
????????????info->set_ratio(dsp_res->quality());
??????????}
????//?do?sth
????????}
????????case?1:
??????????break;
????????case?2:
????????{
??????????auto?info?=?dsp_response->add_dsp_res_infos();
??????????if?(dsp_res->has_cache_duration())?{
????????????info->set_duration(dsp_res->cache_duration());
??????????}
??????????if?(dsp_res->has_quality())?{
????????????info->set_ratio(dsp_res->quality());
??????????}
??????????info->set_dsp_id(item.dsp_id());
??????????std::unordered_set<std::string>?adids;
??????????for?(auto?elem?:?dsp_res->ad_ids())?{
????????????adids.insert(elem);
??????????}
????//?do?sth
??????????
????????}
????????case?3:?//?此case為新增
????????{
??????????auto?info?=?dsp_response->add_dsp_res_infos();
??????????info->set_dsp_id(item.dsp_id());
??????????if?(dsp_res->has_cache_duration())?{
????????????info->set_duration(dsp_res->cache_duration());
??????????}
??????????if?(dsp_res->has_quality())?{
????????????info->set_ratio(dsp_res->quality());
??????????}
????//?do?sth
????????}
????????default:
??????????break;
??????}
發(fā)現(xiàn),當(dāng)dsp_res->bid_type() == 2的時候,也會執(zhí)行 case 3的部分,然后嘗試在上面各個"do sth" 后面,加上break,結(jié)果符合預(yù)期,bug搞定。
深思
為什么在未增加新case之前,test case能通過呢?仔細找QA問了下case的邏輯,原來,case每次都會返回bid_type = 2。此處,我們再貼一次之前的代碼:
switch?(dsp_res->bid_type())?{
????????case?0:
????????{
??????????auto?info?=?dsp_response->add_dsp_res_infos();
??????????info->set_dsp_id(item.dsp_id());
??????????if?(dsp_res->has_cache_duration())?{
????????????info->set_duration(dsp_res->cache_duration());
??????????}
??????????if?(dsp_res->has_quality())?{
????????????info->set_ratio(dsp_res->quality());
??????????}
????//?do?sth
????????}
????????case?1:
??????????break;
????????case?2:
????????{
??????????auto?info?=?dsp_response->add_dsp_res_infos();
??????????if?(dsp_res->has_cache_duration())?{
????????????info->set_duration(dsp_res->cache_duration());
??????????}
??????????if?(dsp_res->has_quality())?{
????????????info->set_ratio(dsp_res->quality());
??????????}
??????????info->set_dsp_id(item.dsp_id());
??????????std::unordered_set<std::string>?adids;
??????????for?(auto?elem?:?dsp_res->ad_ids())?{
????????????adids.insert(elem);
??????????}
????//?do?sth
????????}
????????default:
??????????break;
??????}
由于switch每次都會進入case 2的子邏輯,該邏輯后面就是default,然后break,沒問題。但是增加了新case 3之后,因為case 2 和 case 3后面都沒有break,導(dǎo)致會把case 2 和 case 3的代碼都執(zhí)行了,直到退出循環(huán)或者遇到break。此處列下switch case的三個規(guī)則:switch...case的三個規(guī)則:
既無成功匹配,又無default子句,那么swtich語句塊什么也不做; 無成功匹配,但有default,那么swtich語句塊做default語句塊的事; 有成功匹配,沒有break,那么成功匹配后,一直執(zhí)行,直到遇到break。
擴展
語句體中不包含break
#include?
int?main()
{
????int?iChoice?=?0;
????printf("Enter?your?choice?=?");
????scanf(?"%d",&iChoice);
????switch?(iChoice)
????{
????case?1:
????????printf("case?1?!\n");
????case?2:
????????printf("case?2?!\n");
????case?3:
????????printf("case?3?!\n");
????default:
????????printf("default?!\n"?);
????}
????return?0;
}
當(dāng)輸入choice 為 1的時候
當(dāng)輸入choice 為 2的時候
原因:
在上面的示例中,如果iChoice等于1,則執(zhí)行主體的所有語句,因為在開關(guān)主體中沒有出現(xiàn)break語句。如果ichoice等于2,則由于沒有break語句,因此控制跳至情況2并執(zhí)行以下所有情況。
一個執(zhí)行語句被多個case命中
void?TestFunction(void)
{
????printf("Demo?code\n");
}
int?main()
{
????int?iChoice?=?0;
????printf("Enter?your?choice?=?");
????scanf(?"%d",?&iChoice);
????switch?(?iChoice?)
????{
????case?1:
????case?2:
????case?3:
????????//Calling?function
????????TestFunction();
????????break;
????case?4:
????????printf("Wow?Test?paas?!");
????????break;
????default:
????????printf("default?!\n"?);
????}
????return?0;
}
輸出為
原因:
對于case 1 2 3,都會執(zhí)行到TestFunction
存在一樣的case標(biāo)簽
#include?
int?main()
{
????int?iChoice???=?0;
????int?i?=?0;
????printf("Enter?your?choice?=?");
????scanf(?"%d",?&iChoice);
????switch?(?iChoice?)
????{
????case?1:
????????i++;
????????break;
????case?3:
????????i?=?i?+?2;
????????break;
????case?3:
????????i?=?i?+?3;
????????break;
????default:
????????printf("default?!\n"?);
????????break;
????}
????printf("Value?of?i?=?%d",i);
????return?0;
}
輸出
原因
case標(biāo)簽不能重復(fù),否則編譯器不能確定進入哪個標(biāo)簽
case值為浮點數(shù)
#include?
int?main()
{
????int?iChoice???=?0;
????int?i?=?0;
????printf("Enter?your?choice?=?");
????scanf(?"%d",?&iChoice);
????switch?(iChoice)
????{
????case?1:
????????i++;
????????break;
????case?2.5:
????????i?=?i?+?2;
????????break;
????case?3:
????????i?=?i?+?3;
????????break;
????default:
????????printf("default?!\n"?);
????}
????printf("Value?of?i?=?%d",i);
????return?0;
}
輸出:
原因:
switch 中的參數(shù)必須可以轉(zhuǎn)換成一個整數(shù)
將default 語句放在正文的其他地方
#include?
int?main()
{
????int?iChoice??=?0;
????printf("Enter?your?choice?=?");
????scanf(?"%d",?&iChoice);
????switch?(iChoice)
????{
????default:
????????printf("Bad?Input?!\n");
????????break;
????case?1:
????????printf("Your?enter?choice?is?1\n");
????????break;
????case?2:
????????printf("Your?enter?choice?is?2\n");
????????break;
????case?3:
????????printf("Your?enter?choice?is?3\n");
????????break;
????}
????return?0;
}
輸出:
case標(biāo)簽值必須為常量
#include?
int?main()
{
????int?iChoice??=?0;
????int?Label?=?1;
????printf("Enter?your?choice?=?");
????scanf(?"%d",?&iChoice);
????switch?(iChoice)
????{
????case?Label:
????????printf("Your?enter?choice?is?1\n");
????????break;
????case?2:
????????printf("Your?enter?choice?is?2\n");
????????break;
????case?3:
????????printf("Your?enter?choice?is?3\n");
????????break;
????default:
????????printf("Bad?Input?!\n");
????????break;
????}
????return?0;
}
輸出:
嵌套開關(guān)
#include?
void?nestedSwitchDemo(int?input1,?int?input2)
{
????switch?(input1)
????{
????case?1:
????????printf("Your?enter?choice?is?1\n");
????????switch?(input2?)
????????{
????????case?1:
????????????printf("Enter?Sub?choice?is?1\n");
????????????break;
????????case?2:
????????????printf("Enter?Sub?choice?is?2\n");
????????????break;
????????}
????????break;
????case?2:
????????printf("Your?enter?choice?is?2\n");
????????break;
????case?3:
????????printf("Your?enter?choice?is?3\n");
????????break;
????default:
????????printf("Bad?Input?!\n");
????????break;
????}
}
int?main()
{
????int?iChoice??=?1;
????int?iSubChoice?=?1;
????//Calling?function
????nestedSwitchDemo(iChoice,iSubChoice);
????return?0;
}
輸出:
心得
平時編碼中,一定要注意編碼規(guī)范,每個case都寫好對應(yīng)的break,不要學(xué)習(xí)這種騷操作,稍不注意就可能出現(xiàn)線上故障。
END
