我们可以将代码按照执行方式大致分为三类:Sequence、Selection、Iteration。SequenceSequence从第一行到第n行依次执行。例如:NSString*name=@"default";//definitionname=@"peak";//assignmentNSLog(@"nameis%@",name);//sendmessage3行代码包括不同的Definition、Assignment、SendMessage指令类型,但是当它们作为一个整体执行时,它们是按照Sequence模式顺序执行的。选择选择是一种条件模式。简单点说就是我们平时写代码的时候用到的ifelse,switch。这是我们代码分支的逻辑所在,也是本文的主题。记得之前看过一句话,大意是当我们要重构代码的时候,ifelse总是一个很好的起点,或者说ifelse是我们代码中最容易出错的地方。个人理解,逻辑分支之所以容易出错,原因有两点。一是它所依赖的条件是不确定的,或者说是不稳定的。例如:if([usersobjectAtIndex:0]==currentUser){...}看似简单的条件代码[usersobjectAtIndex:0]==currentUser在各种情况下都会出错,比如users中没有元素会越界,比如用户被释放而导致内存访问异常。同样的情况也会发生在currentUser身上。条件语句包含的状态越多,出错的可能性就越大。第二个缺少条件分支。例如:typedefenum:NSUInteger{EUserLoginStatusLoggedIn,EUserLoginStatusLoggedOut,EUserLoginStatusKickedOut,}EUserLoginStatus;EUserLoginStatususerStatus;...if(userStatus==EUserLoginStatusLoggedIn){...}elseif(userStatus==EUserLoginStatusLoggedOut){...}例如上面的代码忘记处理了EUserStatusLoggedIn,当然如果代码是同一个人写的,一般不会遗漏。但是如果代码由后面的人维护,EUserLoginStatus有添加status,ifelse的处理分散在项目的各个角落,很容易忘记处理新的分支。Iteration迭代发生在我们需要多次循环或者处理一些数据的时候,比如我们常见的while,for循环。迭代有时依赖于一些数据或某些条件语句,还有选择语句在处理过程中容易遇到的状态不稳定的问题。Sequence、Selection、Iteration可以概括我们写过的所有代码。其中Selection是最容易出错的地方,也是我个人审查代码的重点。Selection依赖的第一个问题是状态不稳定。更加关注数据或对象的生命周期、不变性和多线程安全。第二个分支被分支遗留的问题比大多数人想象的更容易发生,尤其是随着项目代码的扩展和工程师的更换。因此,从代码层面进行一些限制,可以有效避免这个问题。通常的做法是使用switch代替ifelse进行多分支逻辑处理。例如A工程师首先写了如下代码:,工程师B在文件A中添加了一个enum值EUserLoginStatusKickedOut,然后编译器会提示我们检查缺少的Type,这里的关键是写switch的时候不要写defaultcase,否则编译器会认为新添加的enum值有default处理逻辑。如果不写defaultcase,Xcode会给出如下警告:这几乎可以算是iOS下处理逻辑分支的最佳实践。除了Match,我们还有另一种更“激进”的方式来避免这种问题,matchpattern。在过去的一年里,越来越多的代码采用了这种方法。使用匹配模式代码如下://FileAtypedefenum:NSUInteger{EUserLoginStatusLoggedIn,EUserLoginStatusLoggedOut,}EUserLoginStatus;//FileBtypedefvoid(^UserLoggedInBlock)(void);typedefvoid(^UserLoggedoutBlock)(void);-(void)someMatchUserStatusLogic{[selfLogmatchedUserStat://...}loggedOut:^{//...}];}-(void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlockloggedOut:(UserLoggedoutBlock)loggedoutBlock{EUserLoginStatususerStatus=EUserLoginStatusLoggedIn;switch(userStatus){caseEUserLoginStatusLoggedIn:{loggedInBlock();}break;caseEUserLoginStatusLoggedOut:{loggedoutBlock();}break;}}该方法在switch的基础上封装了一层函数调用,将分支处理写到函数签名中。好处是显而易见的。当你在new添加EUserLoginStatusKickedOut的情况下,只需更改matchUserStatusLoggedIn函数并添加一个参数://FileBtypedefvoid(^UserLoggedInBlock)(void);typedefvoid(^UserLoggedoutBlock)(void);typedefvoid(^UserKickedoutBlock)(void);-(void)matchUserStatusLoggedIn:(UserLoggedInBlock)loggedInBlockloggedOut:(UserLoggedoutBlock)loggedoutBlockkickedOut:(用户被踢出块)被踢出块;那么所有受影响的代码一编译就会报错,改起来还是挺方便的。与warning相比,compileerror显然可以利用编译器来避免我们代码中的分支遗漏。即使代码由第二个人接手,变化也很明显。这种文风,如果不明白其用意,乍一看显得冗长冗长。个人感觉有时候如果多出来的代码模式是固定的,简单易懂,多出来的代码可以让逻辑更加健壮,那么这些多出来的代码并不是多余的。尤其是在项目代码量过大、参与人数较多的情况下,高质量的代码编写可以避免代码的意外退化。
