代码不规范,同事两行泪

4,233 阅读12分钟

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。 —— 佚名

本文是笔者结合公司代码规范要求,和之前看的《禅与 Objective-C 编程艺术》与《Effective Objective-C 2.0》书籍,以及参考相关博客,总结出的一套iOS开发规范。有不足的地方,欢迎博客下留言。

图片发自简书App

大括号

除了 .m 文件中方法,其他的地方大括号"{"不需要另起一行。推荐:

if (!error) {
    return success;
}

- (void)doHomework
{
    if (self.hungry) {
        return;
    }
    //doSomething
}

运算符

1. 一元运算符与变量之间没有空格:

!aValue
-aValue  //负号
~aValue  //位非
++iCount
*strSource

2. 二元运算符与变量之间必须有空格

fWidth = 5 + 5;
fLength = fWidth * 2;
for(int i = 0; i < 10; i++)

3.三元运算符
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:

result = object ? : [self createObject];

不推荐:

result = object ? object : [self createObject];

if语句

1. 尽量列出所有的情况,且给出明确的结果。

推荐:

var hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}

2. 黄金大道
在使用条件语句编程时,代码的左边距应该是一条“黄金”或者“快乐”的大道,也就是说善于使用return来提前返回不符合的情况。

推荐:

- (void)someMethod {
    if (![someOther boolValue]) {
        return;
    }
    // Do something important
}

3. 复杂的表达式
条件表达式如果比较复杂,则需要将他们提取出来赋给一个BOOL变量。 推荐:

BOOL nameContainsSwift  = [sessionName containsString:@"Swift"];
BOOL isCurrentYear      = [sessionDateCompontents year] == 2019;
BOOL isSwiftSession     = nameContainsSwift && isCurrentYear;

if (isSwiftSession) {
    // Do something very cool
}

4.尤达表达式
尤达表达式是指,拿一个常量去和变量比较而不是拿变量去和常量比较。 推荐:

if (count == 6) {
}
if (myValue == nil) {
}
if (!object ) {
}

不推荐:

if ( 6 == count) {
}
if ( nil == object ) {
}

5. 条件语句体应该总是被大括号包围
尽管有时候你可以不使用大括号(比如,条件语句体只有一行内容),但是这样做会带来问题隐患。 推荐:

if (!error) {
  return success;
}

不推荐:

if (!error)
    return success;
if (!error) return success; 

Switch语句

1. 每个分支都必须用大括号括起来

推荐:

switch (integer) {  
  case 1:  {
    // ...  
    break;  
  }
  case 2: {  
    // ...  
    break;  
  }  
  case 3: {
    // ...  
    break; 
  }
  default:{
    // ...  
    break; 
  }
}

2.除了使用枚举类型以外,都必须有default分支

switch (menuType) {  
  case menuTypeLeft: {
    // ...  
    break; 
   }
  case menuTypeRight: {
    // ...  
    break; 
  }
  case menuTypeTop: {
    // ...  
    break; 
  }
  case menuTypeBottom: {
    // ...  
    break; 
  }
}

在Switch语句使用枚举类型的时候,如果使用了default分支,在将来就无法通过编译器来检查新增的枚举类型了。


函数

1. 一个函数的长度尽量限制在50行以内
如果一个方法里面的代码行数过多,代码的阅读体验极差。

2. 一个函数只做一件事(单一原则)
每个函数的职责都应该划分的很明确(就像类一样)。

3. 对于有返回值的函数,确保每个分支都有返回值

推荐:

int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}

4. 外部传入的参数需要检验参数的非空、数据类型的合法性,参数错误立即返回或断言

推荐:

void function(param1,param2)
{
      if(!param1){
           return;
      }
      if(!param2){
           return;
      }
     //Do some right thing
}

5. 多个函数如果有逻辑重复的代码,建议将重复的部分抽取出来,成为独立的函数进行调用

6. 如果方法参数过多过长,建议多行书写,每个参数占用一行,用冒号进行对齐 推荐:

- (void)initWithAge:(NSInteger)age
               name:(NSString *)name
             weight:(CGFloat)weight;

7. 方法名中不应使用and,而且签名要与对应的参数名保持一致

推荐:

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推荐:

- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;

注释

1.类的注释
对于类的注释写在当前类文件的顶部。

2.属性注释
对于属性的注释建议写在属性上面,用的时候,会有提示功能。

/// 刷新按钮
@property (nonatomic, strong) UIButton *refreshBtn;

3.方法注释
对于.h文件中方法的注释,通过快捷键command+option+/快速注释; 对于.m文件中方法的注释,在方法的上边添加//,注释符和注释内容需要间隔一个空格。例如

// load network data

4.功能注释
版本迭代中,在同事写的代码基础上开发,一定要写上版本功能注释,方便询问具体功能。推荐

// 这是一个新加的功能 v5.20.0 by minjing.lin

变量

1. 变量名必须使用驼峰格式
类,协议使用大驼峰:

HomePageViewController.h
<HeaderViewDelegate>

对象等局部变量使用小驼峰:

NSString *personName = @"";
NSUInteger totalCount = 0;

2.变量的名称必须同时包含功能与类型

UIButton *addBtn 
UILabel *nameLbl 
NSString *addressStr

3. 系统常用类作实例变量声明时加入后缀

类型 后缀
UIViewController VC
UIView View
UILabel Lbl
UIButton Btn
UIImage Img
UIImageView ImagView
NSArray Arr
NSMutableArray Marr
NSDictionary Dict
NSMutableDictionary Mdict
NSString Str
NSMutableString Mstr
NSSet Set
NSMutableSet Mset

常量

1. 常量以相关类名作为前缀

推荐:

static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;

不推荐:

static const NSTimeInterval fadeOutTime = 0.4;

2. 建议使用类型常量,不建议使用#define预处理命令

首先比较一下这两种声明常量的区别:

  • 预处理命令:简单的文本替换,不包括类型信息,并且可被任意修改。
  • 类型常量:包括类型信息,并且可以设置其使用范围,而且不可被修改。

推荐:

static const CGFloat ZOCImageThumbnailHeight = 50.0f;

不推荐:

#define CompanyName @"Apple Inc." 
#define magicNumber 42 

3. 对外公开某个常量

如果我们需要发送通知,那么就需要在不同的地方拿到通知的“频道”字符串(通知的名称),那么显然这个字符串是不能被轻易更改,而且可以在不同的地方获取。这个时候就需要定义一个外界可见的字符串常量。

推荐:

//.h
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
//.m
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";

1. 字母全部大写,单词与单词之间用_分割

#define URL_GAIN_QUOTE_LIST @"/v1/quote/list"
#define URL_UPDATE_QUOTE_LIST @"/v1/quote/update"

2. 宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来

#define MY_MIN(A, B)  ((A)>(B)?(B):(A))

枚举

当使用 enum 的时候,建议使用新的固定的基础类型定义,因为它有更强大的类型检查和代码补全。

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter  = 0,
    UIControlContentVerticalAlignmentTop     = 1,
    UIControlContentVerticalAlignmentBottom  = 2,
    UIControlContentVerticalAlignmentFill    = 3,
};

范型

建议在定义NSArray和NSDictionary时使用泛型,可以保证程序的安全性:

NSArray<NSString *> *testArr =@[@"hello",@"world"];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};

NSMutableArray

1. addObject之前要非空判断。

2. 取下标的时候要判断是否越界。

3. 取第一个元素或最后一个元素的时候使用firtstObject和lastObject


字面量语法

尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变对象:

推荐:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018; 

不推荐:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill" ];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018]; 


Block

为常用的Block类型创建typedef
如果我们需要重复创建某种block(相同参数,返回值)的变量,我们就可以通过typedef来给某一种块定义属于它自己的新类型。

例如:

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
     // Implementation
     return someInt;
}

这个Block有一个bool参数和一个int参数,并返回int类型。我们可以给它定义类型:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

再次定义的时候,就可以通过简单的赋值来实现:

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

定义作为参数的Block:

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;

这里的Block有一个NSData参数,一个NSError参数并没有返回值

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”

通过typedef定义Block签名的好处是:如果要某种块增加参数,那么只修改定义签名的那行代码即可。


属性

1.书写规则
@property、空格、括号、线程修饰词、内存修饰词、读写修饰词、空格、类、对象名称; 根据不同的场景选择合适的修饰符。

推荐:

@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, strong, readwrite) UIView *headerView;
@property (nonatomic, weak) id<#delegate#> delegate;

2. Block属性应该使用copy关键字

推荐:

typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, copy) ErrorCodeBlock errorBlock;

@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);

3. 形容词性的BOOL属性的getter应该加上is前缀

推荐:

@property (nonatomic, assign, getter=isEditable) BOOL editable;

4. 对外尽量使用不可变对象

尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写。具体做法是:

  • 在头文件中,设置对象属性为readonly
  • 在实现文件中设置为readwrite

这样一来,在外部就只能读取该数据,而不能修改它,使得这个类的实例所持有的数据更加安全。而且,对于集合类的对象,更应该仔细考虑是否可以将其设为可变的。

如果在公开部分只能设置其为只读属性,那么就在非公开部分存储一个可变型。所以当在外部获取这个属性时,获取的只是内部可变型的一个不可变版本,例如:

在公共API中:

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公开的不可变集合

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;

@end

在这里,我们将friends属性设置为不可变的set。然后,提供了来增加和删除这个set里的元素的公共接口。

在实现文件里:


@interface EOCPerson ()

@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation EOCPerson {
     NSMutableSet *_internalFriends;  //实现文件里的可变集合
}

- (NSSet*)friends 
{
     return [_internalFriends copy]; //get方法返回的永远是可变set的不可变型
}

- (void)addFriend:(EOCPerson*)person 
{
    [_internalFriends addObject:person]; //在外部增加集合元素的操作
    //do something when add element
}

- (void)removeFriend:(EOCPerson*)person 
{
    [_internalFriends removeObject:person]; //在外部移除元素的操作
    //do something when remove element
}

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName 
{

     if ((self = [super init])) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFriends = [NSMutableSet new];
    }
 return self;
}

我们可以看到,在实现文件里,保存一个可变set来记录外部的增删操作。

这里最重要的代码是:

- (NSSet*)friends 
{
   return [_internalFriends copy];
}

这个是friends属性的获取方法:它将当前保存的可变set复制了一不可变的set并返回。因此,外部读取到的set都将是不可变的版本。


代理方法

1. 代理方法的第一个参数必须为委托者

代理方法必须以委托者作为第一个参数(参考UITableViewDelegate)的方法。其目的是为了区分不同委托着的实例。因为同一个控制器是可以作为多个tableview的代理的。例如:

-  (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath

2.向代理发送消息时需要判断其是否实现该方法

推荐:

if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) { 
 [self.delegate signUpViewControllerDidPressSignUpButton:self]; 
} 

3. 遵循代理过多的时候,换行对齐显示 推荐:

@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>

4. 代理的方法需要明确必须执行和可不执行

  • @required:必须实现的方法
  • @optional:可选是否实现的方法
@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries; 
@end 


1. 类的名称
应该以三个大写字母为前缀;创建子类的时候,应该把代表子类特点的部分放在前缀和父类名的中间。

推荐:

//父类
ZOCSalesListViewController

//子类
ZOCDaySalesListViewController
ZOCMonthSalesListViewController

2. 所有返回类对象和实例对象的方法都应该使用instancetype

将instancetype关键字作为返回值的时候,可以让编译器进行类型检查,同时适用于子类的检查,这样就保证了返回类型的正确性(一定为当前的类对象或实例对象)

推荐:

- (instancetype)init 
{ 
    self = [super init]; // call the designated initializer 
    if (self) { 
        // Custom initialization 
    } 
    return self; 
} 

@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name; 
@end 

不推荐:

@interface ZOCPerson
+ (id)personWithName:(NSString *)name; 
@end 

3. 在类的.h文件中尽量少引用其他头文件

有时,类A需要将类B的实例变量作为它公共API的属性。这个时候,我们不应该引入类B的头文件,而应该使用向前声明(forward declaring)使用class关键字,并且在A的实现文件引用B的头文件。

// EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer;

@interface EOCPerson : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;//将EOCEmployer作为属性

@end

// EOCPerson.m
#import "EOCEmployer.h"

优点:

  • 不在A的头文件中引入B的头文件,就不会一并引入B的全部内容,这样就减少了编译时间。

  • 可以避免循环引用:因为如果两个类在自己的头文件中都引入了对方的头文件,那么就会导致其中一个类无法被正确编译。

但是个别的时候,必须在头文件中引入其他类的头文件:

  • 该类继承于某个类,则应该引入父类的头文件。
  • 该类遵从某个协议,则应该引入该协议的头文件。而且最好将协议单独放在一个头文件中。

4. 类的布局

#pragma mark - Life Cycle Methods
- (instancetype)init
- (void)dealloc

- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated

#pragma mark - Override Methods

#pragma mark - Network Methods

#pragma mark - Target Methods

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  

#pragma mark - Setters and Getters

可以使用代码块一键生成,参考Xcode 快速开发 代码块


相等性的判断

判断两个person类是否相等的合理做法:

-  (BOOL)isEqual:(id)object 
{

    if (self == object) {  
        return YES; //判断内存地址
    } 

    if (![object isKindOfClass:[ZOCPerson class]]) { 
        return NO; //是否为当前类或派生类 
     } 

     return [self isEqualToPerson:(ZOCPerson *)object]; 

}

//自定义的判断相等性的方法
-  (BOOL)isEqualToPerson:(Person *)person 
{ 
        if (!person) {  
              return NO;
        } 
        BOOL namesMatch = (!self.name && !person.name) || [self.name isEqualToString:person.name]; 
        BOOL birthdaysMatch = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday]; 
        return haveEqualNames && haveEqualBirthdays; 
} 


图片命名

1.命名规范:不能有中文、大写、特殊符号、空白
2.命名格式(推荐): fileType[function]project[pageName]imageName[status]{.png,@2x.png,@3x.png}
万能公式:类别_功能_模块_页面_名称_状态.png

icon_tab_bookshelf_sel@2x.png

面试题(风格纠错)

typedef  enum{
    UserSex_Man,
    UserSex_Woman
}UserSex;
@interface UserModel :NSObject

@property(nonatomic, strong) NSString *name;
@property (assign,nonatomic) int age;
@property (nonatomic,assign) UserSex sex;

-(id)initUserModelWithUserName: (NSString*)name withAge(int age);

-(void)doLogIn;
@end

参考文献:

禅与 Objective-C 编程艺术
iOS 代码规范
看完这个你们团队的代码也很规范
《Effective Objective-C 2.0》