iOS 代码里逻辑分支的处理

我们大致上可以将代码按执行方式分解为三类:Sequence,Selection,Iteration。

Sequence

Sequence 即为按前后顺序依次执行,从第一行按序一直执行到第 n 行。比如:

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

3 行代码包含 Definition,Assignment,Send Message 不同类型的指令,但他们被运行的时候作为一个整体是依照 Sequence 模式依次执行。

Selection

Selection 即为条件模式,说的简单一点就是平常我们写代码时所用的 if else,switch。这是我们代码的逻辑产生分支的地方,也是这篇文章的主题。记得之前读到过一句话,大意说是当我们想要重构代码的时候,if else 总会是个好的着手点,或者说 if else 是我们代码最容易出错的地方。

按我个人理解,逻辑分支之所以容易出错在于两点。

其一是所依赖的条件不确定,或者不稳定。比如:

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

看似简单的条件代码 [users objectAtIndex:0] == currentUser 会在各种情况下出错,比如 users 当中没有任何元素会发生越界,比如 users 已被释放导致内存访问异常,同样的情况也会发生在 currentUser 身上,一个条件语句所包含的状态越多,出错的可能性也就越大。

其二是遗漏某个条件分支。比如:

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

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

比如上面代码忘记处理 EUserLoginStatusKickedOut, 当然如果代码是同一个人所写,一般不会遗漏。但如果代码交由后面的人维护,EUserLoginStatus 新增了 status,而 if else 的处理有散落的工程的各个角落,忘记处理新的分支就很容易发生了。

Iteration

Iteration 发生在我们需要循环或多次处理某些数据的时候,比如我们常见的 while,for 循环。iteration 有时也会依赖某些数据或者某些条件语句,在处理的时候也会存在 Selection 语句容易遇到的状态不稳定问题。

Sequence,Selection,Iteration 可以概括我们所写的全部代码。其中 Selection 是最容易出错的地方,也是我个人平时 review 代码的重点。

Selection 第一个所依赖状态不稳定的问题,多注意数据或者对象的生命周期,不可变性,多线程安全即可。可以参考下我之前的两篇文章 [书写高质量代码之状态维护], [iOS多线程到底不安全在哪里?],里面有一些我的相关思考和总结,或许会对你有一些帮助。

分支遗留

第二个分支遗漏的问题,出现的概率比大多数人想象的要高,尤其是随着项目代码的膨胀,工程师的更替。所以从代码层面做一些限制可以有效的避免这一问题出现。

一种常见的做法是针对多分支的逻辑处理,尽量使用 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 的基础之上再封装了一层函数调用,将分支的处理写进函数签名里面,好处很明显,当你新增 EUserLoginStatusKickedOut case 的时候,只要更改 matchUserStatusLoggedIn 函数,新增一个参数:

// 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 显然更能借助编译器来避免我们代码上的分支遗漏。即使代码被第二个人接手,改动起来也一目了然。

这种写法如果不明白目的所在,第一眼看上去显得笨重且多余。我个人感觉,有时候如果多写的代码模式固定且简单容易理解,同时这种多出来的代码可以让逻辑更健壮,那么这些多余的代码就并不多余。尤其是当项目代码量过于庞大且参与人数众多的情况下,优质的代码书写避免代码产生意料之外的降级。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容