Objective-C 与代码整洁之道

栏目: Objective-C · 发布时间: 6年前

内容简介:代码的整洁与否是一个程序员的个人卫生问题。一个程序员穿着可以稍邋遢一些,但是代码要写的干净、利落。如果你想成为一个更好的程序员,除了要学习语法、设计模式之外,还要学习如何写出整洁有效的代码,这本书会教你如何写出这样的代码。书中代码全部为 Java 语言,如果没有接触过 Java 语言,读起来可能会有点困难。这本书大致可以分为三个部分:第一部分占据了大约一半的章节,主要介绍编写整洁代码的原则、模式和实践,读起来比较容易理解。如果你读完这部分感觉已经掌握了如何写好整洁代码,那么你要失望了,其实你只学到一点皮毛

代码的整洁与否是一个 程序员 的个人卫生问题。一个程序员穿着可以稍邋遢一些,但是代码要写的干净、利落。

如果你想成为一个更好的程序员,除了要学习语法、 设计模式 之外,还要学习如何写出整洁有效的代码,这本书会教你如何写出这样的代码。书中代码全部为 Java 语言,如果没有接触过 Java 语言,读起来可能会有点困难。

这本书大致可以分为三个部分:第一部分占据了大约一半的章节,主要介绍编写整洁代码的原则、模式和实践,读起来比较容易理解。如果你读完这部分感觉已经掌握了如何写好整洁代码,那么你要失望了,其实你只学到一点皮毛。要知道, 容易学会的东西一般价值都不高 。当然如果你是天才,学什么都快,就当我没说。

第二部分主要是对几个复杂性不断增加的案例的研究,这是最有价值的一部分,也是最难读的一部分。在这部分你会读大量的 Java 代码,然后逐渐掌握如何写出整洁有效的代码。

第三部分是从研究案例得到的一些启示与灵感。如果你仔细读了第二部分,这部分将是对你的回报,否则这部分对你来说可能所值无几。

这本书是基于 Java 语言写的,因为语法的差异性,有些规则并不适合 OC。下面我主要针对 OC 总结的一些 Tips。

命名

命名是令程序员最头疼的事之一,良好的命名习惯是写好整洁代码的基本素养,命名遵循的原则就是: 有意义且不误导读者 。在 Apple 的官方文档 Coding Guidelines for Cocoa 中,有规范的命名规则。作为一名 iOS 开发者,有必要读一下这篇文档。如果说 Apple 官方文档是针对 OC 的定制规则,那么下面说的将是一些通用规则。

1.有意义的命名

只有编程初学者才会用 a , b , sss , tyq 去做变量或常量名,这点相信大家都不会了。在命名的时候,想要表达什么,就去用什么样的名字。在 OC 的编码习惯中,基本都是用完整的单词,尽量 不要用缩写 ,多个单词采用"驼峰标记法"。如果你深受 Windows 的 C 语言 API 毒害的话,赶快忘掉那该死的"匈牙利标记法"吧。例如下面的代码中:

UILabel *nameLbl = [UILabel new];  // nameLbl 中不应把 'Label' 缩写
NSString *logStr = @"this is log";  // 即使是 NSString 类型,也不应该缩写为 Str
UIViewController *loginVC = [UIViewController new];  // 不建议把 ViewController 所以为 VC

Apple 官方指定的一些缩写 ,在命名时这些变量可以使用缩写。

尽量避免使用一些无意义的"魔法数字",可以使用枚举、常量、宏或者其他方式代替,在进行全局搜索时也很方便。例如下面例子:

// 1,2,3 各代表什么?
if (state == 1) {...}
if (state == 2) {...}
if (state == 3) {...}

// 如果写成这样,则很明确
typedef NS_ENUM(NSUInteger, VideoState) {
    VideoStateOpen = 1,
    VideoStatePause,
    VideoStateClose
}

if (state == VideoStateOpen) {...}
if (state == VideoStatePause) {...}
if (state == VideoStateClose) {...}  // 换成 switch 或许更好一些

// 还有这种的,40.0f 和 23.0f 是什么鬼?为什么不定义为有名字的常量?
CGFloat viewHeight = 40.0f + 23.0f;

2.避免误导

有意义的命名是先决条件,在有意义的基础上,还要做到不能误导读者。不要使用关键字或者一些专属名词命名;不要使用双关语;不要单个使用 'o'、'l' 这种字母,以免与 '0' 、 '1' 混淆读者。下面示例引以为戒:

// list 在很多语言中是一种容器类型,尽量不要作为变量名
NSArray *list = [NSArray new];   

// o or 0,1 or l 很容易混淆,尽管现在的 IDE 会对数字和字母有高亮区分,还是不建议这样写
int a = 1;
if (O == 1) {
    a = O1;
} else {
    l = 01;
}

//

OC 中变量、属性、方法、类的命名

在进行团队合作时,通常团队内部会统一一套编码风格,否则在定义"个人信息"时有的用 'personData',有的用 'personInfo' 岂不是乱套了。在没有统一的时候,应按照如下规则。

在对基本类型的变量或属性进行命名时,遵循有意义且不误导原则;在对对象类型的变量或属性进行命名时,经常在名字后面加上对象类型。不要觉得长,OC 的语法命名一向都很长,看 API 就知道了。

// 基本类型命名
CGRect avatarViewFrame = {0,0,40,40};

// 对象类型命名,多以 '名称+类型后缀' (nonatomic, strong) UILabel *titleLabel; (nonatomic, strong) UIView *tagView; (nonatomic, copy)    NSDictionary *namesArray;

// 也有一些特例,例如 NSString 类型有时候就不带后缀 (nonatomic, copy) NSString *name;

方法命名,要遵循一下几条原则:

  • 以小写字母开始,之后单词的首字母大写,即"驼峰标识"。
  • 如果方法代表对象接收的动作,以动词开始。尽量不要使用 'get'、'set' 命名,set 方法可以使用 'set' 开头命名。
  • 如果方法返回接收者的属性,以 接收者 + 接收的属性 命名。
  • 参数名以小写字母开始,之后的单词首字母大写,不要使用缩写。
// 动词开头
- (void)pushToLoginViewController;
- (void)downloadImageWithURLString:(NSString *)imageURL;
- (CGSize)logoViewSize;  // 接受者 + 属性

// set 方法
- (void)setUserName:(NSString *)userName;

// 一些反例
— (NSInteger)getNumber;
- (void)showimage;
- (instancetype)initWithRequest:(NSURLRequest *) req;

类命名,一般在开发项目时,会规定类得前缀。因为 Apple 的 API 前缀一般为两个大写字母,为了避免冲突,自定义类名一般前缀为三个大写字母。例如:

// 所有前缀为 BLC(BoolChow) 缩写
BLCLoginViewController.h
BLCLoginViewController.m

BLCNetWork.h
BLCNetWork.m

在 Xcode 中,选中项目在右侧 工具 栏中可以设置类的前缀,这样在新建类时会默认加上前缀。如下图:

Objective-C 与代码整洁之道

Objective-C 与代码整洁之道

注释

好的代码是不用注释的,好的注释是你想办法不去写注释,听着有点废话,但确实如此。之因为写注释,是因为代码写的比较乱,怕其他人看不懂,因为标上清晰的注释就能掩盖代码的丑陋。但是往往注释写太多,代码显得就 low 了。

OC 的命名一般都使用单词全拼,很少使用缩写。有时候一个方法的名字特别长,读起来像一句话。所以在 OC 中,如果名字起得好,很少用的到注释。当然,在一些开源框架、SDK 或者官方 API 中,头文件中很多方法都会标有大量注释,方便使用者理解,这并不矛盾。为了注释到恰到好处,下面从"什么时候写","怎么写","在哪写"三个方面简述几条注释的规则。

1.当代码表述不清,或者容易误导读者的时候,加以注释。

最好情况下是将代码表述清楚,但是有时候想要表述的内容太多,代码无法表述,这时候要加上注释进行辅助表述。例如 ViewController 的生命周期方法:

// 想要表述的内容太多,以至于方法名字不能表述出来。
- (void)loadView; // This is where subclasses should create their custom view hierarchy if they aren't using a nib. Should never be called directly.

- (void)viewDidLoad; // Called after the view has been loaded. For view controllers created in code, this is after -loadView. For view controllers unarchived from a nib, this is after the view is set.

有时候代码表述的有歧义,可能会误导读者,这时候要加上注释进行辅助表述。而且尽量不要写误导性代码。

在写 SDK 或者其他开源框架时,需要在头文件中添加注释,表述每个方法的作用。

// Mantle 中,MTLJsonAdapter.h 中对每个方法都做了详细的注释,方便使用者理解 MTLJSONAdapter : NSObject

/// Attempts to parse a JSON dictionary into a model object.
///
/// modelClass     - The MTLModel subclass to attempt to parse from the JSON.
///                  This class must conform to <MTLJSONSerializing>. This
///                  argument must not be nil.
/// JSONDictionary - A dictionary representing JSON data. This should match the
///                  format returned by NSJSONSerialization. If this argument is
///                  nil, the method returns nil.
/// error          - If not NULL, this may be set to an error that occurs during
///                  parsing or initializing an instance of `modelClass`.
///
/// Returns an instance of `modelClass` upon success, or nil if a parsing error
/// occurred.
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error;

/// Attempts to parse an array of JSON dictionary objects into a model objects
/// of a specific class.
///
/// modelClass - The MTLModel subclass to attempt to parse from the JSON. This
///              class must conform to <MTLJSONSerializing>. This argument must
///              not be nil.
/// JSONArray  - A array of dictionaries representing JSON data. This should
///              match the format returned by NSJSONSerialization. If this
///              argument is nil, the method returns nil.
/// error      - If not NULL, this may be set to an error that occurs during
///              parsing or initializing an any of the instances of
///              `modelClass`.
///
/// Returns an array of `modelClass` instances upon success, or nil if a parsing
/// error occurred.
+ (NSArray *)modelsOfClass:(Class)modelClass fromJSONArray:(NSArray *)JSONArray error:(NSError **)error;

2.注释要求简洁、明确、有意义。

既然是辅助表述,就不要啰嗦一顿还没说清楚,也不要喃喃自语。不需要注释的不要画蛇添足,强行注释;需要注释的,应以最简洁的语言将想要表达的意思表述清楚。

// 例如下面的注释,纯属多余。因为命名已经将意思表达的很清楚。 (nonatomic, strong) UIImageView *avatarImageView;  ///< 头像视图 (nonatomic, strong) NSString *userName;  ///< 用户名

/** 播放视频 */
- (void)playVideo {
    ...
}

3.在正确的位置注释

一般在类的头文件中注释,在源文件中不需要再次注释。例如下面代码,为什么要注释两次?

CodeReview : NSObject
/**
 *  更新用户信息
 * userModel 用户信息 model
 */
- (void)updateUserInfoWithModel:(UserModel *)userModel;
 CodeReview
/** 更新用户信息 */
- (void)updateUserInfoWithModel:(UserModel *)userModel {
    ...
}

尽量不要在过程代码中添加注释。除非在过程中有一段不易于理解的代码,可以加注释阐述一下。尤其是有一段代码需要其他开发者注意,这时候可以 加注释起到放大、警示作用

// 下面的方法中有两个问题,一是过程代码中太多的注释;二是方法中执行事件太多,一个方法原则上只执行一件事,后面会说到。
- (void)func {
    // 暂停播放
    [self.player pause];

    // 获取视频播放进度
    CGFloat totalTime = self.player.currentItem.Duration.value /
                             self.player.currentItem.Duration.value.timescale;
    CGFloat currentTime = self.player.currentTime.value /
                                self.player.currentTime.timescale;
    NSString *playerProgress = [NSString stringWithFormat:@"%.2f",currentTime / currentTime];

    // 上传视频播放进度
    NetWorkManager *manager = [NetWorkManager new];
    [manager uploadViewProgress:playerProgress];

    ...
}

注释的样式因注释的内容不同,有着不同的格式。有些格式是能 被 Xcode 识别的 ,有些格式是不能被识别的。在使用 Xcode 编写代码时,会有代码自动提示功能,同时如果一个如果这个变量或方法的注释被识别,则也会显示在代码提示栏中,例如下面的示例:

Documentation : NSObject
 (nonatomic, strong) NSString *personalInfo;    ///< 个人信息


#import "ViewController.h"
#import "Documentation.h"
 ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Documentation *documentation = [Documentation new];
    documentation.pers
}

Objective-C 与代码整洁之道

swift 的注释与 OC 略有不同,有兴趣的可以看一下这篇 文章 对于 OC ,常用的注释方式有一下几种:

1.文件信息的注释,在创建文件时编译器会为我们生成,不可被识别
//
//  CodeReivew.h
//  BlogTest
//
//  Created by boolChow on 17/2/18.
//  Copyright © 2016年 xxx All rights reserved.
//

2.头文件(.h)中方法的注释,Xcode8 快捷键 'option + command + /',可以被识别。
/**
 * <#description#>
 *
 * param <#param description#>
 * return <#return value description#>
 */

3.源文件(.m)中私有方法的注释,可以和上面一样,也可以按照如下方式。
/** <#descriotion#> */。
- (void)func {...}

4.属性的注释,常用两种 (nonatomic, strong) UIView *view; ///< <#description#> (nonatomic, strong) UIButton *button; //!< <#description#>

5.过程代码注释,一般使用 '// <#description#>'。
6.枚举类型注释,一般使用 '/** <#descriotion#> */'。

在 Xcode 中, // 这种注释是不能被识别的,能识别的一般有 /** *//////!///<//!< 。另外,对于因为废弃而注释掉的代码,或者在调试过程中注释掉的代码, 在进行代码提交之前一定要删掉!

格式

排版格式是影响代码整洁度的一个重要因素,虽然现在大部分 IDE 都对代码进行了排版,但是还有一些地方是 IDE 不能做到的,这需要我们手动去排版。我下面将"从小->大"讲述一下格式的细节。

1.大括号 '{}'

对于大括号,有的习惯将在方法后面紧跟大括号的左半部分 '{',有的习惯换一行在写 '{',我习惯前者。

// 普通方法
- (void)func {
    ...
}

// if else 等类似语句,else 跟在 if 语句结束后面,而不是换行。如果 if 语句中只有一句代码,也建议加上大括号。
if (condition) {
    ...
} else {

}

2.空格

在适当的地方使用空格,能够使代码显得更加清晰。

定义方法时,方法的 '-' 与方法返回值间添加空格,参数之间添加空格,方法名与方法开头的大括号之间添加空格

// 建议
- (UIView *)createBannerWithImage:(UIImage *)image frame:(CGRect)frame {
    ...
}

// 不建议
-(UIView *)createBannerWithImage : (UIImage *)image frame:(CGRect)frame{
    ...
}

定义属性时, @ property 与属性关键字之间空格,多个属性关键字之间使用空格,属性类型与属性名称之间使用空格,'*'紧跟属性名称。

// 建议 (nonatomic, strong, readonly) NSString *password;

// 不建议 (nonatomic,strong,readonly)NSString*password; (nonatomic, strong, readonly) NSString * password;

运算符两侧之间添加空格。

// 建议
if (isOpen == YES) {
    ...
}

if (username != nil) {
    ...
}

self.backgroundColor = [UIColor clearColor];
self.state = [value isEqualString:@""] ? @"close" : @"open";

添加注释时,'//'与内容之间添加空格,注释之间如果有英文单词,中英文之间添加空格。

// 建议
// 根据 URL 对内容进行预加载
- (void)prelaodDataWithURL:(NSString *)url;

3.空行

恰当是用空行能够使代码结构更加清晰,但是滥用空行会使代码显得更加糟糕。加空行的原则是:相关的代码组成一个"代码块",两个代码块之间添加空行。

// 建议范例
- (instancetype)initWithUserModel:(UserModel *)userModel {
    self = [super init];

    if (self) {
        _userModel = userModel;
        _imageArray = [NSArray array];
        _attribute = [NSDictionary dictionary];

        [self loadData];
    }

    return self;
}

// 不建议
- (UIView *)func {
    CGFloat height = self.avatarImageView.frame.size.height;
    CGFloat width = self.avatarImageView.frame.size.width;

    UILabel *descriptionLabel = [UILabel new];

    descriptionLabel.frame = CGRectMake(20.f,30.f,height,width);

    ...

    return newView;

}

4.模块分类

对于头文件,可以分为系统 API、Pod文件、自定义 Model、自定义 View、自定义 Controller、自定义 Utils 等。具体分类依个人情况,只要合理即可。另外,个人习惯按照头文件长度从小到大排列。虽然现在有一些插件可以管理头文件,但是一开始就写清晰更好。

// System
#import <CoreData/CoreData.h>
#import <CoreMedia/CoreMedia.h>
#import <AVFoundation/AVFoundation.h>

// Pod
#import <Mantle/Mantle.h>
#import <YYImage/YYImage.h>
#import <CocoaLumberjack/DDLog.h>
#import <AFNetworking/AFNetworking.h>

// Model
#import "FeedModel.h"
#import "UserModel.h"
#import "CommentModel.h"

// View
#improt "BannerView.h"
#import "PersonalInfoView.h"

// Controller
#import "VideoDetailViewController.h"
#import "PersonalInfoViewController.h"

对于属性分类,类似于头文件,例如按照 Data、View、Bool、Custom Class 等类型。依个人喜好,合理即可。

// Data (nonatomic, assign) CGFloat maxHeight; (nonatomic, strong) NSString *username; (nonatomic, strong) NSString *password; (nonatomic, copy) NSDictionary *params;

// View (nonatomic, strong) UIView *subView; (nonatomic, strong) UIView *leftLine; (nonatomic, strong) UITableView *feedTableView;

// Custom Class (nonatomic, strong) TimelineRequest *request; (nonatomic, strong) UserInfoManager *userInfoManager;

一个类中,方法之间通过 '#pragma mark - xxx' 进行模块划分。例如在一个 ViewController 中,按照 init Method、Setup Method、LifeCycle Method、Public Method、Private Method等。例如下面途中即按照模块进行分割。

Objective-C 与代码整洁之道

5.一些原则

对于代码的书写格式,需要遵循一些原则。类之间如何进行归类划分,属于设计模式的范畴,这里只从一个类说起。对于一个类文件,垂直方向代码长度最多建议 700~800 行 (超过 500 行就有点不能忍了),如果超过了 1000 行,则应进行拆分抽取,否则可阅读行会很差;水平方向,建议最多不要超过 80 个字符 ,如果超过,建议进行换行。例如下面注册 Notification 时:

// 不建议
[[NSNotificationCenter defaultCenter] addObserver:self selector(updateData) name:ZHIDidDeleteMediaNotification object:nil];

// 建议
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector(updateData)
                                             name:UpdateDataNotification
                                           object:nil];

为了提高可阅读行,一个类中的方法,最好按照 调用顺序 进行编写,并进行模块分类。这样在其他人阅读代码时不用跳来跳去。

按照正常人的审美观,适当的缩进、空格、空行、对齐,可以提高代码整洁度与美感。按照正常人的逻辑思维,例如从小到大、由表及里、从短到长等一些逻辑顺序去划分模块,编写代码,可以提高代码可阅读性。按照正常人的单元理解能力,垂直方向编写适当范围行数代码,水平方向按照适当范围换行,也可以提高代码的可阅读行。

方法与数据结构

1.只做一件事

方法是对一段过程代码的封装,为了保证代码的整洁性,这段代码最好只执行一个事件,即一个方法最好只做一件事。先理解怎样才算是"一件事",请看下面的代码:

- (void)viewDidLoad {
    [self setupNotification];
    [self setupNavgationItme];
    [self setupTableView];
}

- (void)setupTableView {
    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, -64.0f, MTScreenWidth, MTScreenHeight) style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.tableView.tableFooterView = [UIView new];
    self.tableView.tableHeaderView = self.tableHeaderView;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.tableView.backgroundColor = [UIColor colorWithHexString:@"f2f2f2"];
    [self.view addSubview:self.tableView];

    [self.tableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];
}

上面代码中, viewDidLoad 方法做了三个操作:注册通知、初始化导航栏、初始化 tableView ,这是三件事还是一件事呢?写过 OC 代码的都知道,这些操作"均属于初始化一些基本信息的操作",这三个操作都在 同一个抽象层级 上,因此算是一件事。

同样的 setupTableView 方法中,分别进行了:新建 tableView 对象,设定 tableView 代理、数据、 footerView 等一系列信息,注册 cell 。那么这算几件事呢?这些操作均属于初始化 tableView ,因此这个方法也就只做了"初始化 tableView "这一件事。

刚才提到了"同一个抽象层级",一个方法中应该只有一个抽象层级。如果混杂不同的抽象层级,会让人迷惑,方法看起来就像一个垃圾桶。例如下面的代码:

- (void)viewDidLoad {
    self.dataURL = [NSString stringWithFormat:@"%@%@%@",host,baseURL,userID];
    [self setupView];
    [self setupNotification];

    [self uploadData];
}

上述代码虽然只有几行,确混论不堪。拼接 dataURL 属于较低抽像层级;初始化视图和注册通知属于中间抽象层级;上传数据属于较高抽象层级。短短几行尚且能够读懂,多了之后读起来会乱七八糟。不建议这样写。

一个好的方法应该遵循这样的原则:在尽量 短小 的情况下,所有过程代码属于 同一抽象层级 ,按照 自顶向下 的顺序书写代码。如果方法还能拆分,证明不合格。

2.逻辑语句

每种语言基本都会有 if-elsefor循环switch 等这些逻辑语句,过多的使用这些语句,会使代码变得冗长、丑陋。当你的一段代码嵌套2~3个 if-else 语句或者 for循环 时,你就应该考虑一下是否有更优雅的写法呢。下面通过代码来分析一下:

// 给一个变量赋值需要这么多行代码吗?
if (self.isFriend) {
    self.permission = @"YES";
} else {
    self.permission = @"NO"
}

for (int i=0; i<objcArray.count; i++) {
    Object *obj = objcArray[i];
    NSLog(@"%@",[obj description]);
}

// 或许这种写法更简洁呢
self.permission = self.isFriend ? @"YES" : @"NO";

for (Object *obj in objcArray) {
        NSLog(@"%@",[obj description]);
}
// if 判断中包含多个条件
if (name != nil && password != nil && phone != nil && sex != nil) {...}

// 这种情况最好封装成方法,if 判断中不要有太多的判断参数
if ([self userInfoIsEmpty]) {...}

- (BOOL)userInfoIsEmpty {
    return name != nil && password != nil && phone != nil && sex != nil;
}

这些语句是一些基本的语句,用起来简单,不用太走心,同时容易把代码写的冗长不堪。因此在写代码时要尽量减少这些语句的使用,用其他更加优雅的方式代替。

使用这些语句也暴露出一个问题,证明方法中有多处逻辑判断,每种条件下对应一个事件,是这个方法中包含太多的事件,极易违反"只做一件事"这一原则,也不易于阅读。

3.参数

一个方法,好情况下是没有参数,其次是一个、两个、三个(init 方法稍有特殊,可能会有多个参数)。当一个方法参数超过三个,那说明其中的一些参数可以封装为类了。

方法是对过程代码的封装,参数越多,暴露的内容就越多,封装性就越差。过多的参数,也不易于理解。

不建议向一个方法中传入布尔类型参数,否则的话就说明这个方法很有可能不只做一件事。YES 的时候会这样做,NO 的时候会那样做。

4.结构化编程

每个方法,每个方法中的每个代码块都应该有一个入口、一个出口。遵循这个原则,意味着每个方法中只有一个 return 语句,循环中不能有 breakcontinue 语句,更不能有 goto 语句。结构化编程规范,对于小方法助易不大,只有在大的方法中,这些规范才会有明显的好处。所以,在保持方法短小的前提下,偶尔出现 returnbreakcontinue 语句没有坏处,甚至比单入单出原则更有表达力。

5.时序性耦合

一个方法的过程代码中,有时会出现时序性耦合。例如下面代码:

- (void)viewDidLoad {
    [self registerCell];
    [self registerNotification];
    [self setupData];
    [self setupTableView];
}

- (void)registerCell {
    [self.tableView registerClass:[EmptyTableViewCell class] forCellReuseIdentifier:kEmptyCellReuseIdentifier];
    [self.TableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];
}

- (void)setupTableView {
    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, -64.0f, MTScreenWidth, MTScreenHeight) style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.tableView.tableFooterView = [UIView new];
    self.tableView.tableHeaderView = self.tableHeaderView;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    [self.view addSubview:self.tableView];
}

上述代码中,在 viewDidLoad 方法中进行一些初始化操作。但是细心一些你会发现,"注册 cell "在"初始化 tableView "之前。但是在注册 cell 的时候会用到 tableView ,这时候 tableView 还是 nil 。所以这两个方法调用是有顺序的,否则就会出错。但是如果这样写,即使写对了,后面再修改代码时也容易颠倒位置,导致错误。那么最合适的做法应是这样:

// 将两个方法合并
- (void)setupTableView {
    self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, -64.0f, MTScreenWidth, MTScreenHeight) style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.tableView.tableFooterView = [UIView new];
    self.tableView.tableHeaderView = self.tableHeaderView;
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    [self.view addSubview:self.tableView];

    [self.tableView registerClass:[EmptyTableViewCell class] forCellReuseIdentifier:kEmptyCellReuseIdentifier];
    [self.TableView registerClass:[CommonCell class] forCellReuseIdentifier:kCommonCellReuseIdentifier];
}

类与对象

面向对象语言的特性就是抽象、封装、继承、多态,而"类"则将这些特性全部包含在内。所以一个好的类,便是将这些特性展现出来。

1.类应该短小

在前面已经提过,一个类文件代码长度再好保持在 700~800 行以内。虽然一个类文件可能会有多个类与分类,但是具体到每个类,也应该保持短小,方便理解和阅读。

这里说的短小,并不单指代码行数方面的衡量。对于一个类,最主要的衡量方式是: 权责 。一个类不能有太多的权责,即使代码行数比较短,但是负责了太多事情,这个类仍然是一个臃肿的类。如果一个类有了太多权责,那么这个类就需要拆分,从而保持整洁性。这样会带了另外一个问题:类爆炸,似的项目中有太多短小单一的类。然而每达到一定规模的系统都会包括大量逻辑和复杂性,系统应该由许多短小的类而不是少量巨大的类组成。

2.封装

程序设计的原则是高内聚、低耦合。因此,一个类不应该暴露太多的属性(变量)与方法。下面是在使用 OC 进行程序设计时的一些建议。

对于头文件的引入,大多在 .m 文件中引入。如果 .h 文件中需要使用到其他的类,优先使用 @class 关键字引入,不能解决需求的情况下,再在 .h 文件中引入其他类的头文件。

对于属性,除了使用 nonatomic/atomicstrong/weak/copy 关键字修饰之外,最好还要标明读写属性。如果一个属性可以是 readonly ,就不要写成 readwrite ,不要让其他的类随意修改本类的属性。

对于方法,尽量不要暴露太多的方法。一个类不要提供给外部太多乱七八糟的方法,以保证类的封装性。在提供方法时,方法的参数最好标明 nonull/nullable 属性,而且一个方法不要有太多参数。

3.内聚

类应该只有少量的实体变量。类中的每个方法都应该操作一个或多个这种变量。通常而言,方法操作的变量越多,就越黏聚到类上。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。在 OC 中,如果一个属性在多个方法中被使用,是比较合理的;但是如果一个属性只在一个方法中使用,那么就有必要考虑一下这个属性的存在性,是否可以用局部变量代替。

本书精华

本书的第 14~16 章是对几个案例分析,第 17 张是一些总结性知识点,是本书的精华所在。如果用烹饪做比喻,那么前面的一些章节只是告诉你该用什么原料,油盐酱醋放的剂量以及火候;后面三章才是给你演示一遍整个烹饪过程,真正的授之以渔。

第 14 章《逐步改进》,是对一个自定义 Args 类的重构。通过逐步改进的方式,对原有类进行一步步分解。在这一过程中,遵循前面叙述的一些原则,对方法、变量进行大规模修改。其中有一点很值得学习,在进行重构之前,作者先写了一个覆盖这个类所有方法的单元测试。每次修改一些代码之后,都会跑一遍测试,如果测试通过,则继续修改;否则就需要找出问题,修复之后再继续。这非常值得我们学习,想象一下如果你不这样做,在重构完代码后,发现无法编译了,也不知道在哪个阶段修改出了问题,那会是多么糟糕的场景!

第 15 章《JUnit内幕》,是对 JUnit 框架部分代码的重构。同样,通过对代码的层层剖析,对代码的命名、函数的结构、模块的划分进行了一些改进,将前面讲的一些原则进行了推演。

第 16 章《重构SerialDate》,是对开源框架 SerialDate 的重构过程。先让代码跑通,然后从代码的注释开始,对代码进行修剪、结构调整。如书中所写,这一章分了两部分:"首先,让它工作";"让它作对"。

第 17 章《味道与启发》,是一个总结性章节。对前面的一些规则,以及在重构过程中的一些启发的总结。如果你仔细阅读的前面的章节,尤其是第 14~16 章,这一章的内容你会充分的吸收。

总结

以上,是我对这本书做的一个总结。本书值得学习的地方远不止这些,如果能用一篇文章说清楚,作者干嘛还要写一本书。因此,如果你对代码的整洁性要求十分严苛,想成为一个更好的 coder,建议你读一读这本书。

参考资料


以上所述就是小编给大家介绍的《Objective-C 与代码整洁之道》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

阿里巴巴Java开发手册

阿里巴巴Java开发手册

杨冠宝 / 电子工业出版社 / 2018-1 / 35

《阿里巴巴Java开发手册》的愿景是码出高效,码出质量。它结合作者的开发经验和架构历程,提炼阿里巴巴集团技术团队的集体编程经验和软件设计智慧,浓缩成为立体的编程规范和最佳实践。众所周知,现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程相关的知识点,其他维度的知识点也会影响软件的最终交付质量,比如,数据库的表结构和索引设计缺陷可能带来软件的架构缺陷或性能风险;单元测试的失位导致集......一起来看看 《阿里巴巴Java开发手册》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换