iOS

代码规范

为什么要有代码规范?

对于团队,如果代码风格不统一,阅读或修改同事的代码会非常困难,造成潜在的风险。

对于个人,代码规范是对自身编码习惯的一种监督,如果没有这种监督,有时候因为偷懒,会写出难看的代码,时间长了自己都看不懂。这样对于代码的维护性是不利的。

代码规范的内容?

代码规范包含的范围十分广泛。从一个变量的命名到一个类的设计,我觉得都属于代码规范的范畴。从实践的角度,可以把代码规范分成两个部分:

第一部分是规则,即一定要这么做。这里面没有对错,但需要统一。包含变量的命名、函数的命名、模块的组织、代码块的组织、宏、枚举、常量的声明、函数的粒度。

第二部分是风格,即一种模式化的代码设计结构。我们实现某个功能时,往往不止一种实现方式。每一种实现方式没有绝对的高低之分,不同角度的解读,就会有不同的偏好。所以这个层面上的代码规范,只能求同存异。但是不管怎样,每个人必须要有一致性的风格。就像不同的小区可以有不同的风格,但同一个小区只能有一种设计风格。风格包含代码设计中的抽象概念,比如接口、继承等等。

代码的规则

规则如同法律,尽管从心底里很多人不认可,但每个人都必须遵守。看起来大家都受了束缚,但是对整个社会以及每一个人都是利大于弊。

变量命名

变量命名历史上产生过很多方法,比较著名的有匈牙利命名法、驼峰命名法、下划线命名法。

  • 匈牙利命名法:szUserName
  • 驼峰命名法:userName
  • 下划线命名法:user_name

用Objective-C开发,我们就参考苹果的官方命名方式即可,苹果采用的是驼峰命名法。

苹果的变量命名方式常常被其它语言的开发者所吐槽,因为它的名有时候是在太长了!!!下面是3个最长的Objective-C属性名:
automaticallyEnablesStillImageStabilizationWhenAvailable
availableMediaCharacteristicsWithMediaSelectionOptions
outputObscuredDueToInsufficientExternalProtection
变量声明最重要的是清晰其次才考虑长短,如果为了简短而含糊不清,是不可取的。

Objective-C项目的命名有多长

拿现有代码举例


























变量含义 Not Good Good
昵称输入框 tfNickName nickNameTextField
密码ImageView imvPass passwordImageView
我的搜索条 mysearchBar mySearchBar

类变量
前面加下划线,和系统框架保持一致

@interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer> {
    @package
    UIView           *_view;
    UITabBarItem     *_tabBarItem;
    UINavigationItem *_navigationItem;
    NSArray          *_toolbarItems;
    NSString         *_title;

    NSString         *_nibName;
    NSBundle         *_nibBundle;
    ...... 
`</pre>

现有的例子

<pre>`@interface CouponListTableViewCell : UITableViewCell
{
    UIImageView *imageIcon;
    UIImageView *imageCollect;
    UILabel *labelName;
    UILabel *labelPriceSale;
    UILabel *labelPriceOrigin;
    UILabel *labelSaleNumber;
    UIImageView *imageArrow;
}
`</pre>

**类变量与属性**
属性是Objective-C的一种高级语法,属性可以完全替换类变量,并且提供更强大的访问控制(strong、weak、nonatomic、readonly...),而且属性也只需要用一行就能搞定,所以为什么还要用类变量呢?
属性声明后不用写@synthesize,且会自动生成类变量。[资料>](http://stackoverflow.com/questions/12119284/xcode-4-automatically-generates-ivars-when-using-property-where-can-i-find-the)

**例外**
一些局部变量允许用最简单的方式命名:i、j、temp
一些常用的缩写可以不用驼峰:RMB,OBD

## 函数命名

Objective-C中的函数不同于其他语言,函数名是按参数隔开的。初看起来是有点怪,但是习惯了就会发现这样和命名方式可读性极好,就像一句话一样。所以当你的函数名不能流利的读出来的话,那么它的命名肯定就有问题。
还是先举系统的例子

<pre>`- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
`</pre>

这里面值得注意的几点:
- 最左边的(+、-)和返回类型的左括号之间有一个空格,返回类型的右括号与第一个参数之间没有空格;
- 函数名中每一段描述都以小写字母开头,描述的写法要考虑清晰明确,参考变量命名;
- 如果函数名过长,应该换行,按冒号对齐;
- 从描述中可以看出对应参数的意义。

再看看我们自己的命名

<pre>`
1、最后一个参数描述Selector首字母大写了
+(UIButton *)createButton:(MUButtonItemAttribute *)attr target:(id)target Selector:(SEL)sel;
2、有一些多余的空格
+ (AppDelegate *) appDelegate;
- (void)hideTabbar :(BOOL)status;
3、加减号和括号之间要有空格
-(void)pushEvent:(NSDictionary*)dictionary target:(id)target;
4、参数名和参数要对应,含义清晰
- (void)setNavBackButton:(NSString *)string;
- (id)init:(NSString*)nickName phoneNumber:(NSString *)phoneNumber;
- (void)setTextFieldBackGroud:(CGRect)rect;
- (id)init:(NSInteger)flag;
- (void)requestHealthRecord:(NSString *)typeStr;
5、函数名过长要换行
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier containingTableView:(UITableView *)containingTableView leftUtilityButtons:(NSArray *)leftUtilityButtons rightUtilityButtons:(NSArray *)rightUtilityButtons;
6、命名风格混乱
+ (NSInteger) GetServerBack:(NSString *)serverName
                 path_Param:(NSMutableDictionary*)path_Param
                query_Param:(NSMutableDictionary*)query_Param
                 body_Param:(NSMutableDictionary*)body_Param
                     method:(NSInteger)method
                returnValue:(NSMutableDictionary*)returnValue;
7、参数之间用and、with,多此一举
- (id)initWithFrame:(CGRect)frame andImageView:(UIImageView*)imageView withImageFrame:(CGRect)imageInitFrame withImageUrl:(NSURL*)imageUrl;
`</pre>

**命名很重要!!!**
良好的命名是写出高质量代码的前提,命名的时候除了符合以上的规范,还需要找到合适的单词来表达含义,不要为了一时之快用拼音,也千万不要出现数字。

## 代码模块组织

这个代码模块的组织是指文件级别的,即我们应该如何划分包。
现有的包结构是有些问题的,导致我们找某个功能的实现文件比较麻烦。
参考[别人的经验](http://www.cocoachina.com/industry/20140225/7879.html),个人觉得这样的划分还是不错的。

**文件的命名**
我们自己创建的文件,应该有统一的前缀。比如HJMainViewController,这也符合系统的命名规范。Objective-C没有namespace,所以这样做的好处是当你引用了第三方的代码时,防止重名。而且当我们自己的代码越来越大时,内部也会出现冲突。

**一个文件一个类**
好处:
- 减少单个文件的长度
- 定位文件更方便
- 便于重用

## 代码块的组织

作为一个程序员,大部分时间实在看代码而不是在写。所以代码的可读性严重影响到我们的工作效率。
当你看到超过1000行的实现文件时,你还有看下去的欲望吗?
所以千万不要写出超过1000行的代码,有的话也得[重构掉](http://www.objc.io/issue-1/)。

当代码控制到1000行以内后,只是具备了可读的基本条件。我们的屏幕一般只能一次显示50行代码,从头看到尾就是20屏,常常会看了后面忘了前面。所以我们要把一个文件里面的所有函数有秩序的组织起来,让人不用重头看到尾,就能很快定位到想看的地方。
这里提供一个小办法:用#pragma mark宏根据功能区分开:

<pre>`#pragma mark - Life Circle
#pragma mark - Public Interface
#pragma mark - UI Actions
#pragma mark - Business Logic
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - UIScrollViewDelegate
#pragma mark - Notification Handle
#pragma mark - Private Method
`</pre>

## 宏、枚举、常量的声明

宏的命名方式参考变量的命名方式,大写,用下划线分开

<pre>`TARGET_OS_IPHONE
`</pre>

枚举也有很多写法,这里推荐系统的方式

<pre>`typedef NS_ENUM(NSInteger, NSTextAlignment) {
    NSTextAlignmentLeft      = 0,    // Visually left aligned
    NSTextAlignmentRight     = 1,    // Visually right aligned
    NSTextAlignmentCenter    = 2,    // Visually centered
    NSTextAlignmentJustified = 3,    // Fully-justified. The last line in a paragraph is natural-aligned.
    NSTextAlignmentNatural   = 4,    // Indicates the default alignment for script
} 
`</pre>

常量以k开头,系统命名风格

<pre>`extern NSString *const kAPNetworkDidSetupNotification;     // 建立连接
extern NSString *const kAPNetworkDidCloseNotification;     // 关闭连接
extern NSString *const kAPNetworkDidRegisterNotification;  // 注册成功
extern NSString *const kAPNetworkDidLoginNotification;     // 登录成功
`</pre>

如果是数值常量,用宏的话,也可以用k开头

<pre>`#define kDistancePoint  10.0f   // 圆点、标志和正文的间距
`</pre>

## 函数的粒度

知道我们现在最长的函数有多少行吗?655行。

<pre>`- (void)getShopDetail
`</pre>

一个函数最好不超过一屏(50行),千万不能超过两屏(100行)。
因为从设计的角度来说,一个函数只需要干一件事,所以50行一般是够的,超长的函数往往干了很多事情,或者干了一件很大的事(拆成多件小事,交给子函数去干)。

## 其它
  • Log:不要使用NSLog,提交了会因想到别人,而且最后打包删起来麻烦。需要找一个Log工具,MyNSLog功能不够。
  • 代码中尽量不要出现数字:据上下文推测出来的,还是计算的出为好(现在界面里面很多地方确实要写死数字,适配iPhone6会很困难)

    `_contentBkg = [[UIView alloc] initWithFrame:CGRectMake(0, 1 / [UIScreen mainScreen].scale, 320, 68 - 1)];
    `
  • 避免复杂的表达式

    `    if (tfPassword.text!=nil &amp;&amp;
            ![tfPassword.text isEqualToString:@&quot;&quot;] &amp;&amp;
            tfRequestNumber.text!=nil &amp;&amp;
            ![tfRequestNumber.text isEqualToString:@&quot;&quot;]
            )
        {
        }
    `
  • 一个变量不在多个函数中出现,不要作为类变量:

    `MBProgressHUD* hudProgress;
    int result;
    `
  • 每个类的.h开头的地方要有注释,说明这个类是干嘛的

    `/**
     * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
     * asynchronous so it doesn’t add unnecessary latency to the UI.
     */
    @interface SDImageCache : NSObject
    `
  • if后面的语句哪怕只有一行,都加括号

    `if ([key rangeOfString:@&quot;热&quot;].location != NSNotFound) {
                titleLabel.text = @&quot;热门城市&quot;;
         }
         else
                titleLabel.text = key;
    `

    代码的风格

    风格如同习俗,不同地区有不同的习俗。你觉得入乡随俗好,就入乡随俗,这样避免了一些无谓的麻烦;但是你若坚持原有的习俗,社会也是能接受的,毕竟这些东西都是发展变化的,没有固定的模式,只有更好的模式。

    接口

    这里的接口指的是一个类公开的一组方法,良好设计的类是高内聚低耦合的,所以接口必须是简洁的。

    `@interface PaySucceedViewController : LCBaseViewController

    @property (nonatomic,retain) NSString *isSuccess;

    @property (nonatomic,retain) NSString *shopName;

    @property (nonatomic,retain) NSString *detailinfo;

    @property (nonatomic,retain) NSString *amount;

    @property (nonatomic,retain) NSString omsOrderId;
    /
    !

    • 支付订单类型
      *
    • @since
      */
      @property (nonatomic) ORDER_CATEGORY mCategory;

    @end

上面的例子是一个ViewController,但是它暴露了太多属性,属性很零碎,传值的时候要一个一个传。其实这里属性应该属于一个叫订单Model的对象,把这些属性封装在OrderModel中,复用性和可维护性都会好很多。(但是现在代码整体结构没有Model层,这里需要很大的重构)

继承

继承不是为了少写代码,它的目的是抽象,顺便少写了代码。
举NavigationBar的例子,用宏可以实现,用基类也可以实现,但基类抽象出了一个共有的接口,以后发生共性的变化够可以通过修改基类来实现,你可以在基类里加一组方法,而宏做不到。

其它

代码规范的路还长着,先走出第一步吧!

参考资料

Apple代码规范