<翻译>[Code Smells]#import被滥用!如何管理文件的依赖关系?

#import被滥用!如何管理文件的依赖关系?

像全部的基于C的语言同样,Objective-C一般都是成对的:一个头文件,一个实现文件。每个文件均可以使用#import引入其余的头文件。假如你在写#import的时候不是很care,当心本身给本身埋了一个文件依赖的定时炸弹。假如这样一直不care下去会有什么后果呢?该如何才能拆掉这个炸弹呢?框架

##文件依赖关系 首先要干掉.m文件中那些没有必要的#import。为何要这样作呢?由于#import会强制你添加其余的文件到当前的项目工程中。在一个单独的不会跟其余项目有交集的项目中这无所谓,可是,你要是想在其余的项目中重用这些原文件就有问题了,由于你不得不添加#import的那些文件(这些引添加的文件可能和项目工程没有任何关系)。模块化

可是,在.h文件中的那些没有必要的#import引发的问题就更加严重了,彻底就是指数级的问题了!仅仅是由于一个头文件引用了另外一个头文件,而另外一个头文件又会引用其余的头文件,如此下去。试想一下这样的依赖关系图:工具

A.h引用B.h和C.h,且B.h又引用了D.h。如此一来,若你要在项目工程中使用A,就必须同时把B,C,D也添加进来。你要知道,这是已经一个很简单的依赖关系了。可是,假如还有一些没有必要的#import混了进来,那么这个关系图可就要失控了。oop

##问题:不断增加的编译时间 文件依赖关系也会形成编译成本的增长。引入D.h后,Xcode就不得不从新编译D.m,B.m,和A.m。在一个小项目中这貌似没什么大不了的,可是,在大项目中,你就会有深陷泥潭难之前进一步的感受。不得不说,人们老是告诉我:那不重要,赶快结束项目才是王道。可是,说这样的话的人有几个作过测试驱动开发(TDD)呢?测试驱动开发(TDD)能够对代码修改做出快速反馈(In TDD, unit tests give feedback about the code you just changed.)。你减小的反馈越多,你就越处于有利的地位(The more you can tighten that feedback loop, the more you can stay “in the zone”.)。即便最后只减小了几秒钟的编译时间,也会形成不同的结果(Even a few seconds can make a difference.)。测试

##问题:那些隐式的依赖关系 “既然,头文件中使用#import会形成编译时间的增长,将#import写到实现文件不就能够了吗。”假如你这样想,就错了,在实现文件中也要避免乱用#import。在实现文件中,这种依赖关系依然是存在的,虽然不那么明显了。咱们往下看:优化

仍是使用前面的关系图,稍微变一下。在A.m中引用B.h和C.h,B.m又引用D.h。这里引用D不会引发从新编译的问题,是另外一个问题。当你在项目工程中引入A的时候,你必须引入B,C,D,这个你们都知道。可是,真实的状况是这样的:你引入A的时候,看了一下A.m文件,知道要引入B和C。而后,只有你看了B.m才知道还有引入D。这种依赖关系是很隐秘的,由于有时你根本就看不到.m文件的,只有等到编译并提示错误时,才能猜想一下。ui

而且更加糟糕的是,你刚刚尝试着添加了B,编译后发现还有错误,接着尝试着添加了D,如此下去。在这种无聊的猜谜游戏面前是我的都会崩溃的。atom

##代码异味:在.h文件中过多的使用#import 如今,咱们尝试优化一下文件依赖关系,首先从头文件开始,而后再实现文件。头文件中的Code Smell很明显:过多的使用#import。咱们要决定哪些#import是必须的,那些是要避免的。code

假设咱们定义了一个Foo类,继承自类Superclass,并遵循两个协议,以下:对象

@interface Foo : Superclass <Protocol1, Protocol2>
// ...
@end

很明显,咱们必需要#import定义了Superclass,Protocol1和Protocol2的头文件。

可是,那些属性变量、其余地方使用的协议以及方法的参数和返回值等涉及的类或协议等怎么处理呢?让咱们看下面这个例子:

@interface Foo : Superclass <Protocol1, Protocol2>
{
	Bar *bar;
}

@property(nonatomic, retain) id <DelegateProtocol> delegate;

- (void)methodWithArg:(Baz *)baz;
- (Qux *)qux;

@end

在这个例子中,咱们添加了Bar,DelegateProtocol,Baz,Qux这些类或协议。咱们还要再写几个#import呢?答案是:一个也不须要写!咱们只须要在@interface以前提早声明(forward-declare)一下它们便可:

@class Bar;
@class Baz;
@class Qux;
@protocol DelegateProtocol;

可能你习惯将全部的@class提早声明(forward declaration)整合到一个里面,可是我推荐每个都单独声明。这样作能够快速地检查是否有漏写或者重复,同时也能够方便查看有多少个@class声明。

注意:若要#import的类在框架(framework)中,像UIKit,只用#import这个框架(framework)就能够了,没有必要依次#import这个框架中的每一个使用的类。一个框架(framework)就像一个已编译的代码块(a single prebuilt chunk),且都有一个主头文件,所以在同一个层面上,直接#import框架不会影响文件的依赖关系。在使用任何通用的框架( frameworks)或库(libraries)时,这样的#import方式都是合理的;固然,那些只针对特定项目的框架使用起来可能会有不一样。

好,让咱们回到例子中,咱们仅仅须要#import父类以及须要遵循的协议的头文件:

#import "Superclass.h"
#import "Protocol1.h"
#import "Protocol2.h"

可能还有一些像枚举(enum)和类型定义(typedef)这样的非面向对象的声明,须要使用#import来引入。尽可能避免这样的#import,由于通常来讲,除了上面必需要#import的以外,其余的#import都是Code Smell。

这也是为何我在单独的头文件中声明协议,而不是将协议的声明放在其余类的文件中。这样能够保证依赖关系的简洁。

##代码异味:在.m文件中过多的使用#import

咱们不多在实现文件中使用@class等提早声明(forward declaration),由于在实现文件中咱们基本上是给对象发送消息,而不是传递对象。( Though if your class is the middle-man of a delegation, you will find times when a method takes an argument from a return value and passes it back as its own return value. Then see if you can use forward declaration and avoid the #import.)

因此,在.m文件中咱们没法像在头文件同样经过提早声明(forward declaration)来去掉不必的#import。不论是.h文件仍是.m文件,貌似随着时间的推移,#import的数量都会慢慢的增长。咱们能够绝不费力气的把那些不是特别须要的#import加进来,也能够完全的把它们清掉。下面的状况在你身上极可能会发生:

  1. 你会在新建一个类的时候习惯性的加上一批#import,由于那些是你比较经常使用的一些工具类或其余的。可是,你实际上可能根本就不会用到全部的这些工具。
  2. 你在删掉类中的某个属性变量或方法或协议等的时候,忘记了要同时删掉与它对应的头文件的引用。

基本上,这是比较混乱的管理方式。一次不经意的把混乱的@import删掉就能够削减掉一些没必要要的文件依赖关系。在 why #import order matters中我会详细地说一下#import。

可是,尽管有时你清掉了那些没有必要的#import,你仍是会陷入一层一层的#import列表之中。在开发的过程当中的那种情绪,很容易使你将好些东西所有写到一个类中,形成了低內聚,高耦合。结果仍是一个槽糕的依赖关系。

在Martin Fowler的书Refactoring中,他描述了一个称为Large Class的Code Smell,是由于在Large Class中包含了过多的属性变量。我想说:使用过多的#import也是Large Class的一种(甚至使用过多的@class这样的提早声明(forward declaration)也能够是Large Class的一种)。按照针对Large Class的建议:使用提炼类(Extract Class)或提炼子类(Extract Subclass)。采用这样的方法后,你本身都有可能对结果感到惊讶。“高內聚”正在由一个纯粹的概念变成一个你能够真实感觉到的东西。

##总结

让咱们再加把劲!下面是管理文件依赖关系时须要注意的一些地方:

###头文件中

  1. 使用#import引用父类和要遵循的协议。
  2. 使用提早声明(forward-declare)处理其余的。
  3. 尽可能清掉其余的#import。
  4. 在单独的头文件中声明协议,减小没必要要的依赖关系。
  5. 不要过多的使用提早声明(forward declaration),不然就Large Class了。

###实现文件中

  1. 将那些根本就没用到的#import干掉。
  2. 假如仅仅是传递对象,没有向对象发送消息,就把#import换成@class。
  3. 对于能够模块化的东西,尽可能作成一个单独的库。
  4. 不要过多的使用#import,不然就Large Class了。

好,咱们来检查一下本身的代码吧!

原文连接:http://qualitycoding.org/file-dependencies/

相关文章
相关标签/搜索