iOS 代碼里邏輯分支的處理

我們大致上可以將代碼按執(zhí)行方式分解為三類:Sequence弹渔,Selection,Iteration溯祸。

Sequence

Sequence 即為按前后順序依次執(zhí)行肢专,從第一行按序一直執(zhí)行到第 n 行。比如:

NSString *name = @"default"; //definition
name = @"peak"; //assignment
NSLog(@"name is %@", name); //send message

3 行代碼包含 Definition博杖,Assignment,Send Message 不同類型的指令狈醉,但他們被運行的時候作為一個整體是依照 Sequence 模式依次執(zhí)行班巩。

Selection

Selection 即為條件模式逊桦,說的簡單一點就是平常我們寫代碼時所用的 if else,switch宝穗。這是我們代碼的邏輯產(chǎn)生分支的地方,也是這篇文章的主題。記得之前讀到過一句話,大意說是當我們想要重構代碼的時候,if else 總會是個好的著手點不铆,或者說 if else 是我們代碼最容易出錯的地方。

按我個人理解,邏輯分支之所以容易出錯在于兩點涝开。

其一是所依賴的條件不確定,或者不穩(wěn)定捐腿。比如:

if ([users objectAtIndex:0] == currentUser) {
    ...
}

看似簡單的條件代碼 [users objectAtIndex:0] == currentUser 會在各種情況下出錯嘁锯,比如 users 當中沒有任何元素會發(fā)生越界蝗羊,比如 users 已被釋放導致內(nèi)存訪問異常,同樣的情況也會發(fā)生在 currentUser 身上蓄愁,一個條件語句所包含的狀態(tài)越多效诅,出錯的可能性也就越大。

其二是遺漏某個條件分支剑刑。比如:

typedef enum : NSUInteger {
  EUserLoginStatusLoggedIn,
  EUserLoginStatusLoggedOut,
  EUserLoginStatusKickedOut,
} EUserLoginStatus;

EUserLoginStatus userStatus;
...
if (userStatus == EUserLoginStatusLoggedIn) {
    ...
} else if (userStatus == EUserLoginStatusLoggedOut) {
    ...
}

比如上面代碼忘記處理 EUserLoginStatusKickedOut, 當然如果代碼是同一個人所寫狸驳,一般不會遺漏辩昆。但如果代碼交由后面的人維護术辐,EUserLoginStatus 新增了 status,而 if else 的處理有散落的工程的各個角落,忘記處理新的分支就很容易發(fā)生了襟企。

Iteration

Iteration 發(fā)生在我們需要循環(huán)或多次處理某些數(shù)據(jù)的時候,比如我們常見的 while木羹,for 循環(huán)脐瑰。iteration 有時也會依賴某些數(shù)據(jù)或者某些條件語句,在處理的時候也會存在 Selection 語句容易遇到的狀態(tài)不穩(wěn)定問題掠剑。

Sequence朴译,Selection,Iteration 可以概括我們所寫的全部代碼盯拱。其中 Selection 是最容易出錯的地方蛮艰,也是我個人平時 review 代碼的重點。

Selection 第一個所依賴狀態(tài)不穩(wěn)定的問題窒盐,多注意數(shù)據(jù)或者對象的生命周期,不可變性,多線程安全即可梅肤∈谂粒可以參考下我之前的兩篇文章 [書寫高質(zhì)量代碼之狀態(tài)維護], [iOS多線程到底不安全在哪里缝裤?],里面有一些我的相關思考和總結锰瘸,或許會對你有一些幫助。

分支遺留

第二個分支遺漏的問題,出現(xiàn)的概率比大多數(shù)人想象的要高调鬓,尤其是隨著項目代碼的膨脹缀踪,工程師的更替。所以從代碼層面做一些限制可以有效的避免這一問題出現(xiàn)。

一種常見的做法是針對多分支的邏輯處理咒彤,盡量使用 switch 而非 if else鞋屈,比如工程師 A 先寫了如下代碼:

// File A
typedef enum : NSUInteger {
  EUserLoginStatusLoggedIn,
  EUserLoginStatusLoggedOut,
} EUserLoginStatus;

// File B
EUserLoginStatus userStatus;
...
switch (userStatus) {
  case EUserLoginStatusLoggedIn:
  {

  }
  break;
  case EUserLoginStatusLoggedOut:
  {

  }
  break;
}

之后工程師 B 在 File A 中又加了一種 enum 值 EUserLoginStatusKickedOut,那么此時編譯器會以警告的方式坤邪,幫助我們檢查遺漏的類型,這里的關鍵在于寫 switch 時不要寫 default case,否則編譯器會認為新增的 enum 值有默認的處理邏輯了影所。

如果沒寫 default case蹦肴,Xcode 會給出如下警告:

enumcompile.png

這幾乎可以看做是 iOS 下處理邏輯分支的 best practice 了。

Match

除此之外猴娩,我們還有另一種更“激進”的方式來避免這類問題阴幌,match pattern。過去一年看到越來越多的代碼采用這種方式卷中。使用 match pattern 代碼如下:

// File A
typedef enum : NSUInteger {
  EUserLoginStatusLoggedIn,
  EUserLoginStatusLoggedOut,
} EUserLoginStatus;

// File B
typedef void (^UserLoggedInBlock)(void);
typedef void (^UserLoggedoutBlock)(void);

- (void)someMatchUserStatusLogic
{
  [self matchUserStatusLoggedIn:^{
    //...
  } loggedOut:^{
    //...
  }];
}

- (void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlock loggedOut:(UserLoggedoutBlock)loggedoutBlock
{
  EUserLoginStatus userStatus = EUserLoginStatusLoggedIn;
  switch (userStatus) {
    case EUserLoginStatusLoggedIn:
    {
      loggedInBlock();
    }
      break;
    case EUserLoginStatusLoggedOut:
    {
      loggedoutBlock();
    }
      break;
  }
}

這種方式在 switch 的基礎之上再封裝了一層函數(shù)調(diào)用裂七,將分支的處理寫進函數(shù)簽名里面,好處很明顯仓坞,當你新增 EUserLoginStatusKickedOut case 的時候背零,只要更改 matchUserStatusLoggedIn 函數(shù),新增一個參數(shù):

// File B
typedef void (^UserLoggedInBlock)(void);
typedef void (^UserLoggedoutBlock)(void);
typedef void (^UserKickedoutBlock)(void);

- (void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlock loggedOut:(UserLoggedoutBlock)loggedoutBlock kickedOut:(UserKickedoutBlock)kickedoutBlock;

那么所有被影響的代碼只要一編譯都會報錯无埃,改起來相當方便徙瓶,相比較于 warning,compile error 顯然更能借助編譯器來避免我們代碼上的分支遺漏嫉称。即使代碼被第二個人接手侦镇,改動起來也一目了然。

這種寫法如果不明白目的所在织阅,第一眼看上去顯得笨重且多余壳繁。我個人感覺,有時候如果多寫的代碼模式固定且簡單容易理解荔棉,同時這種多出來的代碼可以讓邏輯更健壯闹炉,那么這些多余的代碼就并不多余。尤其是當項目代碼量過于龐大且參與人數(shù)眾多的情況下润樱,優(yōu)質(zhì)的代碼書寫避免代碼產(chǎn)生意料之外的降級渣触。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市壹若,隨后出現(xiàn)的幾起案子嗅钻,更是在濱河造成了極大的恐慌皂冰,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件养篓,死亡現(xiàn)場離奇詭異秃流,居然都是意外死亡,警方通過查閱死者的電腦和手機柳弄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門舶胀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人语御,你說我怎么就攤上這事∠郑” “怎么了应闯?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挂捻。 經(jīng)常有香客問我碉纺,道長,這世上最難降的妖魔是什么刻撒? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任骨田,我火速辦了婚禮,結果婚禮上声怔,老公的妹妹穿的比我還像新娘态贤。我一直安慰自己,他們只是感情好醋火,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布悠汽。 她就那樣靜靜地躺著,像睡著了一般芥驳。 火紅的嫁衣襯著肌膚如雪柿冲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天兆旬,我揣著相機與錄音假抄,去河邊找鬼。 笑死丽猬,一個胖子當著我的面吹牛宿饱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脚祟,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼刑棵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愚铡?” 一聲冷哼從身側響起蛉签,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤胡陪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后碍舍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柠座,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年片橡,在試婚紗的時候發(fā)現(xiàn)自己被綠了妈经。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡捧书,死狀恐怖吹泡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情经瓷,我是刑警寧澤爆哑,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站舆吮,受9級特大地震影響揭朝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜色冀,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一潭袱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锋恬,春花似錦屯换、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至癣防,卻和暖如春蜗巧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕾盯。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工幕屹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人级遭。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓望拖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挫鸽。 傳聞我的和親對象是個殘疾皇子说敏,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容