<翻譯>[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/

相關文章
相關標籤/搜索