我最常做的開發任務是設計一個可重用的API組件。組件通常為iOS(盡管有時它們是OS X) 設計的,且總是GUI控件或某種視圖。
多年來,我為客戶開發了很多API組件,其中包括像Apple這樣的客戶,而且我已經很了解這個過程。我也定期發布開源組件,並且我把曾經對我有幫助的資料和API設計指南放在一起與大家分享。
這是一個重要的主題,無論你是一個開源貢獻者,或作為團隊的一員參與開發大型的應用,或者只是設計自己的軟件。正如開發一個應用的過程,API接口是使用你代碼的開發者對你代碼的第一印象,將嚴重影響著開發者決定是使用或扔掉它。
APIs是開發者的用戶體驗。我一直驚訝,具體到這個流行平台上沒有很多的資料是寫我們這方面工作的。
當我們閱讀一些設計指南時,必要的時候,我將要用我最近發布的開源GUI組件MGTileMenu作為一個例子。你可以在這裡先閱讀所有關於MGTileMenu的信息,如果你喜歡。
應用程序接口(API)設計和用戶界面、用戶體驗設計很相像。你的目標用戶有不同的需求和特點,但歸根結底他們的目標還是把需求完成而已。就像一個設計友好、易用的應用程序的用戶界面一樣,你需要讓你的API有以下的特點:
如同人們設計的其它的軟件一樣,我們首先需要考慮的是使用案列。我們的設計需要使最經常被用到的的功能簡單易用,不需要過度的配置。在默認配置下軟件就應該是可用的,並且具有一定的可配置性。軟件的設計應該具有可探索性,而且應該允許用戶從已知的的范例中推廣到其他應用場景。這和我們創建一個用戶界面的規則非常的相像。
用於和開發者交互的元素使用四個主要的顯示意味著:
我們需要把每一個都設計成:明智和慎重的,用於人類使用。這裡有2個問題當你設計API的時候需要考慮:
我們的核心原則是讓已有的類和模型保持一致性,以用來保證我們可以把一個開發者不熟悉的控制讓他很輕松的在他可以理解的平台上使用。使用標准APIs,模型,和模式無論是不是可能(並且這個應該是總用的)。對於終端用戶,熟悉和直覺性是和代碼層級一樣重要的。
讓我們看看我們之前提到的這四個元素:
Here’s the interface file for MGTileMenu.
在我們討論具體的接口之前,這有一些涵蓋范圍比較廣泛的規則:
我所看到最常見的錯誤是API的設計利用了外來的約定。APIs 屬於固定平台和固定的開發者生態系統。你根本無法使用任何習語和你用過的其他平台的架構,這樣做會污染您當前的代碼庫,並對其他開發人員的效率造成損害。
在coding之前要了解你目標平台的約定,比如,在iOS 或者 OS X,不使用異常對待control的流程 。以適當的方式命名你的方法(通常指有足夠詳細,但也應該有足夠的簡潔)。
了解協議,和委托,類別分別是什麼。在你的代碼中使用他們。學習相關的構造函數和析構函數的命名方案。請遵守內存管理規則。詞匯和語法是不可分割的,你要麼發展為一個固定的的平台,或者你跨平台。
任何component的設計應該沒有連接到你當前創建的項目,如果他是一個GUI control或者一個視圖,它應該默認顯示一些東西。使用現有的框架作為一個指南,與委托協議,精心設計的/命名的API方法和通知在適當的地方保持松耦合。
一個很明顯的,但非常有效的方式,是每次為你的component創建一個項目,並逐漸的隔離開發component。強迫自己使用自己的API。遠離無關的類。
接下來,讓我們來適當談談類的接口。初始化方法的接口中最重要的部分之一,因為他們是人們如何開始使用您的組件。你的類將有一定的初始配置所需的設置。所以,一個明顯的規律:
如果有什麼需要設置的,不要等待 -需要它了就去做,如果你沒有得到的東西的立即返回nil。
1
-
(id)initWithDelegate:(id)theDelegate;
//
required parameter; cannot be nil.
這個前一個結果的必然結果: 記住不要僅僅傳入參數,應該可以通過屬性或者賦值來訪問他們,如果他們可以通過任何方式來一場“按摩”(修改,重寫等)
1
@property
(nonatomic, weak, readonly) id delegate;
//
must be specified via initializer method.
前兩個例子闡述了這個觀點。
實際上,你不總為component提供單獨的文檔。如果你不提供文檔,你的.h文件(包括demo app)就是你的文檔。他們應該適當的描述,我的意思是:
特別是,你應該簡要注釋在屬性或訪問器旁邊;頭文件掃描比在初始化實例的時候更容易。
1
@property
(nonatomic) CGGradientRef tileGradient;
//
gradient to apply to tile backgrounds (default: a lovely blue)
2
@property
(nonatomic) NSInteger selectionBorderWidth;
//
default: 5 pixels
3
@property
(nonatomic) CGGradientRef selectionGradient;
//
default: a subtle white (top) to grey (bottom) gradient
你的類應該設計成只需要最少的代碼來集成(包括後續將用到的委托/數據源協議)。但不包括委托方法,你應該著手於用3行代碼就可以達到測試的目的。
這3行代碼如下:
1
//
Instantiate.
2
tileController
= [[MGTileMenuController alloc] initWithDelegate:self];
3
4
//
Configure.
5
tileController.dismissAfterTileActivated
= NO;
//
to make it easier to play with in the demo app.
6
7
//
Display.
8
[tileController
displayMenuCenteredOnPoint:loc inView:self.view];
另一個推論:您的demo的大小是衡量你component質量的標准,其值越小越好。Demo/Code 應該盡可能的小巧而又精簡(用於演示,旨在描述所有組件的定制或功能)。
核心思想是當你的代碼從你的空的Xcode項目模板到你的demo中應該保持最小化的修改。這並不是一個好的借口當你需要復制粘貼demo來讓你的component運行。
我對於apps的准則就是:不要讓用戶去做選擇。選擇滿足多數人的人性化的默認設置,略去參數設置窗口。畢竟,好的軟件都是有傾向性的。
由於運用場景不是那麼的清晰明確,所以不同的組件面對的情況也有些不同。你當然可以做一個只滿足某種特定情況的組件,但是,通常我們都希望有些靈活性。你絕不會准確的知道另一個開發者將會怎樣使用你的組件,所以你必須做到有一定的通用性。
認真的選擇你的定制點是很重要的。考慮依賴關系更加的重要——不是對編譯/鏈接的理解,而是定制類型之間的邏輯關系。我的方法就是盡量從“方面”的層次上考慮而不是實例變量的層次上。你希望你的組件的那些方面允許被定制化?那麼你就知道哪些特定的屬性需要暴露。
通過不暴露足夠的的配置點,就可以很容易的弱化某個特定的定制類型。例如:3.如果沒有空間,就不要暴露大小。
具體的情況取決於具體的組件,但是需要從外觀或者功能角度來考慮屬性之間的關系。學會理解開發者。不要禁止組件的個性化,讓它靈活些。
1
@property
(nonatomic)
BOOL
dismissAfterTileActivated;
//
automatically dismiss menu after a tile is activated (YES; default)
2
@property
(nonatomic)
BOOL
rightHanded;
//
leave gap for right-handed finger (YES; default) or left-handed (NO)
3
4
@property
(nonatomic) NSInteger tileSide;
//
width and height of each tile, in pixels (default 72 pixels)
5
@property
(nonatomic) NSInteger tileGap;
//
horizontal and vertical gaps between tiles, in pixels (default: 20 pixels)
6
@property
(nonatomic) CGFloat cornerRadius;
//
corner radius for bezel and all tiles, in pixe
讓常識來指導你。確定那些能夠滿足70%左右你所能想到的使用場景的選項,然後提供這些選項。剩下的就讓你的授權方法和代碼架構來滿足吧。有一個特定的模式持續出現在我所喜歡的一些來自標准庫、開源的第三方以及我自己的一些代碼組件。它是一個組件中屬性(或者訪問器,定制點)個數與方法(也就是所有其它的,從初始化到狀態更新)個數的比率。
多屬性少方法(再申明一次,方法不是指在Interface Builder中的那些)。MGTileMenu有一個初始化函數和四個實際上供公共使用的願意非常(每一個都很方便調用另一個方法)。對定制點而言,它的比率有4倍之多。我認為這是一個非常好的比率,使組件不但在功能上變得簡潔,而且在定制時更加靈活。
1
-
(id)initWithDelegate:(id)theDelegate;
//
required parameter; cannot be nil.
2
-
(CGPoint)displayMenuPage:(NSInteger)pageNum centeredOnPoint:(CGPoint)centerPt inView:(UIView *)parentView;
//
zero-based pageNum
3
-
(
void
)dismissMenu;
4
-
(
void
)switchToPage:(NSInteger)pageNum;
//
zero-based pageNum
一個同時簡化組件API和實現的好方法就是在你的實現中使用己有的控件。具有統一的外在並不意味著你不可以使用已經存在的組件(確實,這是軟件工程當中的一個基本原則)。
考慮是什麼讓UITableViewCell和UIButton擁有簡單的API接口,發現這是因為它們使用諸如UIImageView和UILabel這樣的子控件。你也可以,並且該這樣去做,並且如果可行的話使用相應的子控件來使你的類接口保持簡單不變。
舉個例子,在MGTileMenu中,它的外表是常規的UIButtons(不只是子類)。跟在一個的view中自定義去畫它的樣式、處理輸入事件和支持訪問而言,極大地簡化了它的實現。
你會很自然地在實現的過程中加入合適的方法並下意識地將其設置為私有的。相反,應該考慮是否可以公開這些方法,使這些組件能被集成到別人的應用程序。
對你而言那些如何簡單方便地加入一個方法或函數的方式,對開發者而言同樣如此。
舉例來說,在MGTileMenu中,我創建了這些合適的函數:
1
CGRect
MGMinimallyOverlapRects(CGRect inner, CGRect outer, CGFloat padding);
2
3
CGGradientRef
MGCreateGradientWithColors(UIColor *topColorRGB, UIColor *bottomColorRGB);
//
assumes colors in RGB colorspace
第一個函數可以幫助我移動tile menu使它在父View完全可見(如果它們對菜單提供輔助的用戶接口,便可以方便地讓其它的開發者使用),第二個返回一個漸變Core Graphics給UIColors,它被我用來設置tiles的默認的背景(同時 當實現MGTileMenu的代理協議 時,其它的開發者們發現可以很方便的給tiles配置漸變色)。你遲早都會在你的component中加入一些魅力。人人都希望有大量的Steve Jobs式的直觀、宜人、富於掌控力的魅力,但是我所說的卻是代碼中的一些東西,諸如擁有特殊含義的數字或者值。例如,-1在某項設置或者某個特殊場景中,就有著某個特定的意義。
很好,那樣做真的挺好的。不爽的是你的代碼中充滿一些莫名其妙的原始值,更不爽的是還將它們暴露在API中。如果你正在暴露一些魔數,那麼為了便於使用,最好還是用#define或者常量或者其他的東西包裝一下它們,這樣會讓魔數更加的直觀和易於理解。
1
//
Used for the page-switching tile in methods expecting a tile-number.
2
#define
MG_PAGE_SWITCHING_TILE_INDEX -1
代理協議是奇妙的。它們是實現MVC模式的簡單的、熟悉的和靈活的方式,它們更會使你養成松耦合的好習慣並且教你明智的API設計。
這個是 MGTileMenu’s delegate protocol.
有太多經典的代理和數據源協議供我們用到幾乎所有的組件中。如果你正在顯示數據,這個准確的數據源協議可能更接近於:
同樣的,在幾乎任何的情況中,這個准確代理協議可能采用下面的形式:
這也被稱為Should, Will, Did協議模式,這也巧妙的連結了之後的Will-Did通知模式。
讓我們來討論一下你可能會覺得有爭議的話題:我發現將代理協議合並到數據源協議中被完美的接受了(也就是說將他們整合到一個協議中)。我在MGTileMenu和幾個其他的組件中這樣做了。
我完全接受分離他們的原則,並且我能想到很多你想保持他們分離的例子。通常,蘋果也是保持他們分離的。那好吧。
可是,就我的經驗看,在大多數例子中合並他們是很好的。大多數人將數據源方法和代理方法放到同一個地方。我從來沒有因為合並這些協議而抱怨過,我幾乎不能記得一個存在分開的協議在不同的地方使用的情況。
如果你重視清晰,或者有將代理從數據源中分離出來的需求,那麼很明顯你應該那樣做。我只是不認為如果你合並他們會覺得不好。
當你選擇設置哪些代理方法為required(必須實現)時一定要小心。太多的required方法將會表明:
一個精心設計的組件應該有很少很少required代理方法-僅僅是那些不得不的方法。小心選擇。同樣的,記住再後來添加optional(可選擇實現)方法是很簡單的,但是添加required方法就非常的困難(很多時候人們會抱怨)。
在MGTileMenu中有5個required方法,其中有4個是數據源方法:
1
-
(NSInteger)numberOfTilesInMenu:(MGTileMenuController *)tileMenu;
//
in total (will be shown in groups of up to 5 per page)
2
-
(UIImage *)imageForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu;
//
zero-based tileNumber
3
-
(NSString *)labelForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu;
//
zero-based tileNumber
4
-
(NSString *)descriptionForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu;
//
zero-based tileNumber
前兩個屬於一個真正的數據源協議。第三第四個也是,但是這也表明了我的觀點:我認為軟件應該是容易理解的,並且我正在強制你為每一個tile提供一個標簽和表述來共讀者們閱讀。我很享受這些。
當然也有一個代理方法
1
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu didActivateTile:(NSInteger)tileNumber;
//
zero-based tileNumber
這個方法是required,因為他告訴你如何來發現一個tile是激活狀態的。如果你不打算實現這個方法,MGTileMenu將不會做任何有用的事情,甚至你可能就根本不能用它。所以他是必須實現的。
立刻跟上這最後的規則:使設計是容易理解的。不要在最後才注意到它,恰恰相反,應該是在起初就設計成容易理解的。如果你遵循了“在你的控件中運用控件”原則,那麼你可能在不知不覺中就遵循了這個原則。
上面展示的代理(倒不如說是數據源)方法是另一個開發者為了使他們給語音輔助提供一些東西的地方。 如果你能夠在視覺上(就像展示一個文本標簽)自動地改變一些事情的意圖就像一個語音輔助標簽那樣,那該多好啊(此外,在大多數例子當中語音輔助已經為你實現了這個)。
在交流方面保持清醒。做到不容易理解的設計倒是困難的。我也寫了另外一篇關於在IOS應用中支持語音輔助功能的文章,這也是蘋果向在容易理解的程序有聯系的合伙人們推薦的。我也推薦它,但是我寫它就是為了你可以接受它。
這不僅僅適用於協議,但協議是尤其重要的部分。最好是在實際應用中,用合適的語義對象作為數據,雖然在你的實現中這樣做可能會更加的麻煩。
如果你需要一個日期,不要用一些數字-而應該是一個真實的日期對象。對象或者結構體可以表示每一樣事物,並且你應該有意地去用它們。如果需要的話可以創建一個類(你可能不會需要)。
當然一個標准的目錄除外-除了基元沒有任何理由可以把他們變成任何事物,自從NSNumber的加入,對於抵消打包/非打包帶來的麻煩沒有任何事物是語義地足夠重要的。
規則 16:如果語義不合適的話就提高API
我時刻都在注意這一點。我曾在早些時候提到過,你怎麼能就像一些事物已經存在(通常,就像是已經存在了的在你的實現中無意間用到的東西)了那樣考慮幾乎任何新的定制的控件呢?
那非常好,並且你是很聰明的,但是不能讓語義勝過相似點。為了使語義合適,在一個已經存在了的API上疊加一個新的API絕對是好的(或者漂亮的)。例如:
諸如此類的等等。不要時常地強制你自己(或者其他的開發者)在抽象的實現API和真實的組件語義之間做精神上的轉變 - 反而應該讓這個API反映出組件真實的目的。
MGTileMenu 的代理協議通過不把菜單當做UIBUttons(實現了的)的集合而寧可說是菜單的統一做到了這一點,利用每個菜單中相關的有限的tile來展示內容。
當我不得不回過頭來添加一個新的代理方法和通知到我認為已經完成了的API中,我才意識到這一點。對交互控件來說,高亮是有趣的。通過“高亮”,意味著其在應用程序中潛在的重要性。
任何控件都會通知App(在某種意義上來說,可能只是通過調用一個動作或方法),當它被完全觸發時;但是當它們被高亮(選中,按住)或取消高亮被觸發時,只有比較少的情況才會通知。這說明它實際上非常重要。應用程序可能需要:
1
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu didSelectTile:(NSInteger)tileNumber;
//
zero-based tileNumber
2
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu didDeselectTile:(NSInteger)tileNumber;
//
zero-based tileNumber
我們大部分人都把可選的委托方法當做二選一的情形:如果你不實現他們,就使用默認的行為;如果你要實現他們,那你就要為將發生的事情全部負責。那不是理想的事情。
在任何的提供一個可選的委托方法的實現中,你應該仍然返回去默認的行為,即使這個方法已經被實現了,但不要返回一些明顯的東西。這聽起來很明顯,但是令人驚訝到底有多少組件無憂無慮地然後委托對象返回任何類型沒有經過精細檢查的愚蠢東西,僅僅是因為這委托莫名奇妙地答應通過實現這個方法來管理自己的行為。
我要具體地討論下可視化的定制,如背景顏色和圖片。非常非常仔細地考慮下,你是否不應該去干涉那種情況,同時依靠於你的默認界面。他們真的不想展示些東西嗎?甚至讓感覺好一點?它會讓控制看起來很糟糕嗎?如果如此,插手介入吧,同時僅在如果委托方法從來不在開始的地方就實現的時候准備默認的。
相關地,用一個有文檔的、標准的及不是很突兀的方式去慎重地從每個可選的委托方法中通過返回一些如空的東西來執行默認的行為。
例如,MGTileMenu有一個比較復雜的層次方式讓你可以自定義標題的背景。你可以實現三個委托方法中的任何(或者全部,或者不)方法去為每個標題提供一個背景圖片、梯度或者顏色。你也能夠在任何時候為任何標題選擇默認的行為,通過返回空或者NULL等適當的類型。
你將不得不嘗試相當困難地去使標題的背景透明(通過返回清澈的顏色或者使用空的UIImage對象)。
這是一個簡單的規則,大家也可以同樣簡單地產生一個錯誤。在你的委托(delegate)方法中,總要傳遞一個sender參數。
不管是單例,還是你無法想象地將被不止一次同時使用的東西(,都應如此)。沒有例外:
應當這樣:
1
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu didActivateTile:(NSInteger)tileNumber;
2
//
zero-based tileNumber
不應當這樣:
1
-
(
void
)tileMenuDidActivateTile:(NSInteger)tileNumber;
2
//
zero-based tileNumber
3
//
Um, 哪個菜單?
一個真正的數據源協議總有將最感興趣東西放在前邊的方法。比如,你請求的指定質量或屬性等。像這樣:
1
-
(UIImage *)imageForTile:(NSInteger)tileNumber inMenu:(MGTileMenuController *)tileMenu;
2
//
zero-based tileNumber
不要像這樣:
1
-
(UIImage *)tileMenu:(MGTileMenuController *)tileMenu imageForTile:(NSInteger)tileNumber;
返回類型自然而然應作為方法名的第一部分,這並不會另人奇怪(請注意上邊兩個函數的不同處)。數據源協議中經常有許多命名相似的方法,因此我們應該最先考慮保持函數的唯一性和感興趣部分。這樣的話,(這些方法)更容易讀,更容易做到自動補全。
有人指出:Apple公司的UITableViewDataSource協議並沒有按照那些做,他們是把sender放在第一位的,例如:
1
-
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
對此,我想說的就是:我關注這些不同之處。我堅持我的觀點。一個真正的委托(Deletage)協議不是用來查詢,而是用來通知的。在這種情況下,你應該將sender放到第一位(參考 “說出誰正在說話” 規則)。
1
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu willSwitchToPage:(NSInteger)pageNumber;
2
//
zero-based pageNumber
這是遵循兩個人交流的習慣的。你不應該跳出來就說“她將會遲到”,因為另一個會問“誰?”
取而代之,你應該說出誰正在說話。這是一個習慣,可以很方便地將查詢(數據源)與通知(委托)方法區分開來。
上面已經說了這麼多,記住約定和一致性一定是在某些時刻要屈服於優秀的觀點的 - 在這個例子當中,或者你的例子中。如果約定被打破,那就好不擔心的去跳過它。如果你的觀點真的更好,那就去做吧!
舉個例子,在菜單控件中已經存在一個約定了,靠這個約定你能夠通過代理來使菜單選項可用或者不可用,利用calledvalidateMenuItem:方法。為了一致性的緣故,我曾考慮過給我代理協議中的一部分方法用相同的名字。但是我最終決定沒有那樣做,因為:
相反的,我繼續為了更簡單並且更加的容易理解而打破了約定:
1
-
(
BOOL
)isTileEnabled:(NSInteger)tileNumber
inMenu:(MGTileMenuController *)tileMenu;
//
zero-based tileNumber
我們能夠為了特殊的語法而戰,但是當你遇到那個方法時,你應該立刻就知道它是做什麼的?如何來使用它。我認為,那樣會更好。
通知是委托協議的另一部分。我認為,如果你使用委托協議(如果合適,你應當用它),你最好加上通知,不然它不算完整。
在 MGTileMenu 中,你可以在 MGTileMenuController 的接口文件 中找到的通知。
在代理方法(注意;不是數據源方法)和通知之間有一個剪不斷理還亂的聯系。在你的代碼中同樣的地方你會同時用到它們,並且起到同樣的作用。
如果一個代理方法告訴代理發生了一些事情,你通常應該為了起到相同的作用發送一個通知。就像代理方法一樣加上通知,去除掉模稜兩可的方法,並且落實你的通知列表。
代理方法的參數應該和通知的userInfo的內容相匹配,這是很明顯的除非你作為對象來傳遞sender,而不是捆綁在字典信息中。
代理方法:
1
-
(
void
)tileMenuWillDisplay:(MGTileMenuController
*)tileMenu;
2
-
(
void
)tileMenuDidDisplay:(MGTileMenuController
*)tileMenu;
與之對應的通知:
1
extern
NSString
*MGTileMenuWillDisplayNotification;
//
menu will be shown
2
extern
NSString
*MGTileMenuDidDisplayNotification;
//
menu has been shown
盡量為通知提供有用的信息。記住:通知接收方(很)可能與你組件裡面的代理方法或者數據源鏈完全無關。
你要想想到底哪些信息會有用,並提供相應的信息。至少,你必需確保代理方法的所有參數都包含在 userInfo 對象裡。
代理方法:
1
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu willSwitchToPage:(NSInteger)pageNumber;
//
zero-based pageNumber
2
-
(
void
)tileMenu:(MGTileMenuController
*)tileMenu didSwitchToPage:(NSInteger)pageNumber;
//
zero-based pageNumber
相應的通知:
1
//
The following notifications have a user info key "MGPageNumber" with an NSNumber (integer, zero-based) value.
2
#define
MGPageNumberKey @"MGPageNumber"
3
extern
NSString
*MGTileMenuWillSwitchToPageNotification;
//
menu will switch to the given page
4
extern
NSString
*MGTileMenuDidSwitchToPageNotification;
//
menu did switch to the given page
最終有些事我們已經知道了. 軟件工程和敬業精神的第101條: 確保它真地有效.
是否用正式TDD測試取決於你,但是測試是不可缺少的。每個可選的委托方法。每一個發布的通知。定制的每一個點,在每一個可能的組合。組件提供了一千個微妙的問題的機會。
可能有一些缺陷。找到他們,先解決這些問題。如果你的時間推後,切功能,而不是調試。你要受的苦沒有出貨的錯誤。
我制定上述的規則是通過我自己這幾年在創建components和APIs時所犯的錯誤中總結的。我也努力的嘗試的遵守我制定的規則,但是難免的在某些情況下我沒有。
雖然不可能在每個場景or每個事件中應用到這些規則,但是如果盡可能的遵循這些規則,你將可能創造出一個設計良好的,靈活,可重用的components。讓其他人一起享用的components。
你也許想要一個簡單的提綱關於這些規則,下面的圖片就是。我有全尺寸的圖片版本托管在Flickr上。
如果你喜歡發布自己的components給別人去使用,就像我的MGTileMenu一樣。那麼也許你也想去讀讀我發的open source code(開源代碼),其中幾個點也許會觸及到。這篇文章也討論了一些README文件,協議選擇的相關事宜。