iOS代码里逻辑分支的处理

移动开发
我们大致上可以将代码按执行方式分解为三类:Sequence,Selection,Iteration。Selection即为条件模式,说的简单一点就是平常我们写代码时所用的 if else,switch。这是我们代码的逻辑产生分支的地方,也是这篇文章的主题。

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

Sequence

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

 

  1. NSString *name = @"default"; //definition 
  2. name = @"peak"; //assignment 
  3. NSLog(@"name is %@"name); //send message 

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

Selection

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

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

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

 

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

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

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

 

  1. typedef enum : NSUInteger { 
  2.   EUserLoginStatusLoggedIn, 
  3.   EUserLoginStatusLoggedOut, 
  4.   EUserLoginStatusKickedOut, 
  5. } EUserLoginStatus; 
  6.  
  7. EUserLoginStatus userStatus; 
  8. ... 
  9. if (userStatus == EUserLoginStatusLoggedIn) { 
  10.     ... 
  11. else if (userStatus == EUserLoginStatusLoggedOut) { 
  12.     ... 

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

Iteration

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

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

Selection 第一个所依赖状态不稳定的问题,多注意数据或者对象的生命周期,不可变性,多线程安全即可。

分支遗留

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

一种常见的做法是针对多分支的逻辑处理,尽量使用 switch 而非 if else,比如工程师 A 先写了如下代码:

 

  1. // File A 
  2. typedef enum : NSUInteger { 
  3.   EUserLoginStatusLoggedIn, 
  4.   EUserLoginStatusLoggedOut, 
  5. } EUserLoginStatus; 
  6.  
  7. // File B 
  8. EUserLoginStatus userStatus; 
  9. ... 
  10. switch (userStatus) { 
  11.   case EUserLoginStatusLoggedIn: 
  12.   { 
  13.  
  14.   } 
  15.   break; 
  16.   case EUserLoginStatusLoggedOut: 
  17.   { 
  18.  
  19.   } 
  20.   break; 

之后工程师 B 在 File A 中又加了一种 enum 值 EUserLoginStatusKickedOut,那么此时编译器会以警告的方式,帮助我们检查遗漏的类型,这里的关键在于写 switch 时不要写 default case,否则编译器会认为新增的 enum 值有默认的处理逻辑了。

如果没写 default case,Xcode 会给出如下警告:

iOS代码里逻辑分支的处理

这几乎可以看做是 iOS 下处理逻辑分支的 best practice 了。

Match

除此之外,我们还有另一种更“激进”的方式来避免这类问题,match pattern。过去一年看到越来越多的代码采用这种方式。使用 match pattern 代码如下:

 

  1. // File A 
  2. typedef enum : NSUInteger { 
  3.   EUserLoginStatusLoggedIn, 
  4.   EUserLoginStatusLoggedOut, 
  5. } EUserLoginStatus; 
  6.  
  7. // File B 
  8. typedef void (^UserLoggedInBlock)(void); 
  9. typedef void (^UserLoggedoutBlock)(void); 
  10.  
  11. - (void)someMatchUserStatusLogic 
  12.   [self matchUserStatusLoggedIn:^{ 
  13.     //... 
  14.   } loggedOut:^{ 
  15.     //... 
  16.   }]; 
  17.  
  18. - (void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlock loggedOut:(UserLoggedoutBlock)loggedoutBlock 
  19.   EUserLoginStatus userStatus = EUserLoginStatusLoggedIn; 
  20.   switch (userStatus) { 
  21.     case EUserLoginStatusLoggedIn: 
  22.     { 
  23.       loggedInBlock(); 
  24.     } 
  25.       break; 
  26.     case EUserLoginStatusLoggedOut: 
  27.     { 
  28.       loggedoutBlock(); 
  29.     } 
  30.       break; 
  31.   } 

这种方式在 switch 的基础之上再封装了一层函数调用,将分支的处理写进函数签名里面,好处很明显,当你新增 EUserLoginStatusKickedOut case 的时候,只要更改 matchUserStatusLoggedIn 函数,新增一个参数:

 

  1. // File B 
  2. typedef void (^UserLoggedInBlock)(void); 
  3. typedef void (^UserLoggedoutBlock)(void); 
  4. typedef void (^UserKickedoutBlock)(void); 
  5.  
  6. - (void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlock loggedOut:(UserLoggedoutBlock)loggedoutBlock kickedOut:(UserKickedoutBlock)kickedoutBlock; 

那么所有被影响的代码只要一编译都会报错,改起来相当方便,相比较于 warning,compile error 显然更能借助编译器来避免我们代码上的分支遗漏。即使代码被第二个人接手,改动起来也一目了然。

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

责任编辑:未丽燕 来源: MrPeak技术分享
相关推荐

2017-07-25 09:55:10

iOS横竖屏旋转

2015-05-20 10:11:11

微软汉拿山Office 365

2011-03-30 10:50:55

GitLinux 版本控制

2019-08-15 10:25:02

代码开发工具

2012-01-10 09:32:12

iOS 5.1设备四核处理器

2010-08-25 11:29:07

代码

2010-08-25 10:27:35

代码

2011-11-03 15:44:10

程序员

2017-11-16 15:21:06

代码taste方法

2010-07-19 10:55:07

SQL Server

2024-08-20 08:19:43

2021-05-13 10:40:16

ThreadLocal代码Java

2011-08-08 13:26:48

iOS开发 Twitter

2021-03-25 07:30:24

代码开发数据

2022-02-15 08:38:04

错误逻辑异常编程程序

2021-05-31 18:56:56

代码编码开发

2022-05-25 16:51:41

Git 分支重命名开发者

2013-03-28 11:00:40

服务器数据虚拟化

2013-06-08 10:40:49

代码规则命名规则代码命名

2018-05-16 07:41:29

图片代码资源
点赞
收藏

51CTO技术栈公众号