鍍金池/ 教程/ iOS/ Omni 內(nèi)部
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

Omni 內(nèi)部

Omni 集團(tuán)是一家屬于員工的公司,在這里,人們可以帶他們的狗來上班。

換句話說:當(dāng)你考慮如何管理大型項(xiàng)目時(shí),首先得考慮文化 。“我們?nèi)绾谓M織項(xiàng)目?用什么樣的源代碼控制管理?” 文化并不糾結(jié)于這些細(xì)節(jié),一個(gè)優(yōu)秀的文化會(huì)形成一個(gè)快樂的團(tuán)隊(duì),他們將弄清楚如何一起工作。而 Omni 在文化建設(shè)上的成果可謂豐碩

Omni 的文化可以另撰一篇長文,但在這里不打算涉及太多。相反,這里是一次圍繞著技術(shù)的游歷,著重于討論我們?nèi)绾喂芾碜约旱?App。

組織扁平化

所有的工程師向 Tim Wood 進(jìn)行匯報(bào),他是 Omni 的 CTO 和創(chuàng)始人。每個(gè)產(chǎn)品都有一個(gè)項(xiàng)目經(jīng)理。

各個(gè) App 的項(xiàng)目組是流動(dòng)的。項(xiàng)目組本身不會(huì)經(jīng)?;螂S意地變動(dòng),不過它們終究還是會(huì)變化。

內(nèi)部交流

因?yàn)榇蠹叶荚谵k公室工作,所以我們會(huì)面對(duì)面的溝通。有時(shí)會(huì)召開會(huì)議,其中一些是提前安排好的,而另一些則是臨時(shí)決定的。每個(gè)人都會(huì)參加一周一次的全體員工大會(huì),時(shí)間大約是 20 分鐘。主要部門主管和項(xiàng)目經(jīng)理說明接下來的工作方向。在之后還有每周的開發(fā)會(huì)議,這也持續(xù)約 20 分鐘。討論一些大家普遍關(guān)心的開發(fā)事項(xiàng) (用于規(guī)避逐個(gè)找人溝通,雖然有時(shí)還是不得不這樣做)。

面對(duì)面的交流得益于 Omni 的核心工作時(shí)間:人們希望每天早上11點(diǎn)到下午4點(diǎn)的時(shí)間都在辦公室內(nèi)度過,所以你知道你可以在那個(gè)時(shí)間里找到想找的人。(不過,你也可以來的或早或晚,隨你心意,只要你在工作上投入了一整周的時(shí)間。)

其它途徑的話,包括 E-mail,包括一些郵件列表,和我們內(nèi)部的聊天室加上私信。(我們最近將 Message 和 Jabber 替換為了一套可以保存歷史記錄的系統(tǒng),它還可以播放 GIF。)

Bug

每一個(gè)開發(fā)組織都有至少一個(gè)人負(fù)責(zé)解決 Bug 追蹤系統(tǒng)中反饋的問題。Omni 使用了一個(gè)內(nèi)部的 Mac 應(yīng)用 OmniBugZapper (OBZ) ,它具有那些你期望擁有的功能。它不像一些公開應(yīng)用那樣矚目,但它卻是一個(gè)很有用的工具。

(在OmniBugZapper中的) 一個(gè)典型的工作流是:你在當(dāng)前階段發(fā)現(xiàn)了一個(gè) Bug,將它的優(yōu)先級(jí)調(diào)為高,然后打開它,使其他人可以看見你正在解決這個(gè) Bug。

一旦問題解決,你在這個(gè) Bug 上添加一條筆記說明你做了什么來搞定它,或許也會(huì)說明如何去測(cè)試,然后你寫上了版本管理系統(tǒng)中的版本號(hào)。

你將 Bug 的狀態(tài)切換為“驗(yàn)證”,然后一位測(cè)試接收并確定 Bug 是否真的被修復(fù)了。如果沒有被解決,它可能會(huì)重新被打開。如果修復(fù)衍生出了一個(gè)新的 Bug,一個(gè)新的 Bug 會(huì)被添加至 OmniBugZapper。

一旦處于“驗(yàn)證”狀態(tài)的 Bug 通過了測(cè)試,它會(huì)被標(biāo)記為“已解決”。

(前方高能:開發(fā)者與測(cè)試者之間的關(guān)系并非敵對(duì),雖然我已經(jīng)在幾家公司聽說過類似的事情。有些時(shí)候當(dāng)你覺得測(cè)試的工作就是折磨你,但這才該是常態(tài)。這意味著他們做了了不起的工作,我們都有著同樣的目標(biāo),那就是做一個(gè)棒棒的應(yīng)用出來。)

有一些 Bug 的修復(fù)需要工程師來驗(yàn)證,但這比較少見。大部分 Bug 是通過測(cè)試來驗(yàn)證的。(驗(yàn)證的工程師與修復(fù)的工程師不可以是同一個(gè)人。)

有些 Bug 是永遠(yuǎn)不會(huì)被修復(fù)或者驗(yàn)證的。它們包括討論型 Bug 和參考型 Bug:我們通過在的前者基礎(chǔ)上進(jìn)行討論來決定某個(gè)功能的改變或者增加,而后者可以用來對(duì)某個(gè)特性的行為或者出現(xiàn)做一些注解。

階段 (里程碑) 管理

OmniBugZapper 有階段管理 (milestones) 的概念,自然,它有一個(gè)階段監(jiān)視器窗口,我們可以看到當(dāng)前階段的進(jìn)度和狀態(tài)。

每一個(gè)階段都劃分為分析、計(jì)劃和驗(yàn)證。Bug 會(huì)經(jīng)過分析變?yōu)橛?jì)劃,或者留至以后解決。

決定一個(gè) Bug 或者功能的開發(fā)階段與發(fā)布版本是合作式的,每一個(gè)想要參與的人都可以參加。通常,項(xiàng)目經(jīng)理會(huì)做大部分的決定。在比較大的問題上,通常會(huì)進(jìn)行更多的討論,我們一般會(huì)達(dá)成共識(shí),不過我們不會(huì)通過投票決定設(shè)計(jì)問題,我們的 CEO,Ken Case,有最終決定權(quán)。Ken 負(fù)責(zé)規(guī)劃未來。

譯者注:Milestones 可被翻譯為里程碑圖,是項(xiàng)目管理中的一種工具。為明確其功能,此處翻譯為階段管理,而每一個(gè) Milestone 則被翻譯為階段。

源代碼控制管理

我們使用 SVN。所有的應(yīng)用與網(wǎng)站都在一個(gè)巨型的代碼倉庫中。我絲毫不驚訝于每個(gè)人的開發(fā)副本只是其中的一部分,而非全部。

你可能會(huì)覺得 SVN 是一個(gè)還未封印的史前工程師產(chǎn)物,并好奇人們關(guān)于切換工具的考慮。不過 SVN 干的還不錯(cuò),如何簡(jiǎn)化對(duì)一個(gè)大型倉庫的管理,總有些值得討論的事情。

我們有很多個(gè)腳本幫助我們做這些工作。比如,當(dāng)我想得到 OmniFocus 的最后一次更改,我輸入./Update OmniFocus,然后它會(huì)更新我的開發(fā)副本 (通常我每天第一件事就是做這個(gè))。我的開發(fā)副本中沒有 OmniGraffle,只因?yàn)槲也恍枰P(guān)注它。但我也可以使用 ./Update OmniGraffle

SVN 創(chuàng)建分支可能不像 Git 與 Mercurial 那樣方便,但也沒有那么困難。每當(dāng)我們的應(yīng)用接近發(fā)布時(shí),我們會(huì)建立一個(gè)分支,用于隔離其它的變動(dòng)。人們可以出于各種目的隨時(shí)創(chuàng)建私人分支與目錄。

提交信息通過 E-mail 發(fā)送給工程師和其它關(guān)注項(xiàng)目的人。

崩潰

由于應(yīng)用被置于大量自身也有 Bug 的系統(tǒng)框架上面,而應(yīng)用也運(yùn)行在實(shí)際的用戶的電腦上而非理想運(yùn)行環(huán)境中,所以無法保證一個(gè)應(yīng)用從來不會(huì)崩潰。

不過確保我們自己的代碼不會(huì)崩潰是我們的職責(zé),如果我們發(fā)現(xiàn)系統(tǒng)框架中有一個(gè)會(huì)導(dǎo)致崩潰的 Bug,我們需要找到方法繞過它,以讓代碼正常工作。

我們使用了一些圖形化的統(tǒng)計(jì)來展示一個(gè)應(yīng)用在崩潰之前的平均運(yùn)行時(shí)間。我們還有一個(gè)內(nèi)部應(yīng)用叫做 OmniCrashSorter,我們可以看到一些被標(biāo)記出來的崩潰日志,包括異常的追蹤調(diào)用棧,以及一些用戶崩潰場(chǎng)景的記錄。

關(guān)于崩潰有這么一個(gè)掃興的問題:崩潰永遠(yuǎn)不會(huì)在開發(fā)時(shí)出現(xiàn) (這似乎是一個(gè)軟件開發(fā)中的鐵律)。 這使得用戶的崩潰報(bào)告以及重現(xiàn)步驟非常重要。所以我們收集這些報(bào)告,并讓它們易于被查看。

有時(shí),我們會(huì)刻意使崩潰發(fā)生在異常中。因?yàn)槲覀兊膽?yīng)用使用自動(dòng)保存,崩潰會(huì)比可能出現(xiàn)的臟數(shù)據(jù)覆寫更為安全。

代碼

我們?cè)趦?nèi)部的 Wiki 上有一個(gè)簡(jiǎn)單的代碼風(fēng)格規(guī)范,而我因?yàn)樵缫咽炀毜膽?yīng)用,反而忘了它說的是什么。

除了一個(gè)事情之外,方法應(yīng)當(dāng)像這樣開頭:

- (void)someMethod;
{

或許很多人并不知道,在 Objective-C 中允許這樣使用分號(hào)。沒錯(cuò),是允許的。

這種方式比較好的地方在于,它可以使我們很容易的將類的聲明拷貝到頭文件或者類的擴(kuò)展當(dāng)中。而且,你可以選擇一整行,Command+E,然后查找包含它的頭文件 (或者從頭文件查找回你實(shí)現(xiàn)它的 .m 文件)。

我不喜歡這個(gè)方式。對(duì)我這樣一個(gè)極簡(jiǎn)主義強(qiáng)迫癥來說,分號(hào)太多余了,而所有多余的事情都應(yīng)該被去掉。(想象我的 X-ACTO 刻刀慢慢的繞著一個(gè)分號(hào)刻上一圈,伴隨著一陣東北風(fēng),把它刮到空氣中,離開我的桌子,然后散落進(jìn)垃圾桶里,真是說不出的暢快。)

不過這只是我沒事兒時(shí)的抱怨。我想說的重點(diǎn)在于,我們需要一個(gè)代碼規(guī)范,而且每個(gè)人都使用它。然后我們?cè)陂喿x彼此的代碼時(shí),就不會(huì)被引誘著去爭(zhēng)論關(guān)于分號(hào)的問題。我們不應(yīng)該只為了比拼彼此的口味,把時(shí)間浪費(fèi)在重新格式化已經(jīng)寫好的代碼上。

共享框架

Omni 所有的應(yīng)用在某種程度上是同一個(gè)大型應(yīng)用,因?yàn)樗鼈円蕾囉诤芏鄠€(gè)共享的框架。這些框架一部分是開源的,你可以閱讀相關(guān)的信息,并在GitHub上獲取源碼。還有一些額外的內(nèi)部框架,一部分用在每一個(gè)應(yīng)用中,還有一些則只用在部分應(yīng)用里。

共享的框架使開發(fā)一些不同的應(yīng)用變得更容易,而且開發(fā)組的替換也變的方便,畢竟大部分框架都是相同的。

當(dāng)然,有一點(diǎn)不好的地方,是某個(gè)框架發(fā)生了變化可能一次性影響多個(gè)應(yīng)用。不過解決這個(gè)問題的唯一方法就是解決它,Bug 就是 Bug。

(當(dāng)項(xiàng)目快要發(fā)布時(shí),我們會(huì)建立一個(gè)新分支,用來防止在接下來的幾周中框架開發(fā)中產(chǎn)生的變化。)

ARC

新的代碼往往是基于 ARC 的。雖然有大量的舊代碼還未被轉(zhuǎn)換,但這也沒什么問題。畢竟隨著運(yùn)行情況的變化,被調(diào)校的代碼只應(yīng)該是那些需要被校正的。不過有些時(shí)候它們依舊應(yīng)該被轉(zhuǎn)換。(我已經(jīng)做過一部分關(guān)于轉(zhuǎn)換的工作,以后還會(huì)做更多。我覺得使用 ARC 寫出不會(huì)發(fā)生崩潰的代碼會(huì)更容易一些。)

Swift

雖然一批工程師已經(jīng)開始編寫 Swift 的代碼,Swift 目前尚未出現(xiàn)在我們的任何應(yīng)用和框架中。

不過這也說不準(zhǔn),或許在明天,或許在一兩年之后,又或許就在你閱讀本篇文章的當(dāng)下,改變就會(huì)發(fā)生。

測(cè)試

OmniFocus 中有覆蓋模型類的單元測(cè)試;其他應(yīng)用也有差不太多的測(cè)試覆蓋率。我們與其他 OS X 和 iOS 開發(fā)者面對(duì)面臨的問題是相同的,每個(gè)應(yīng)用的 UI 元素都很多,而做自動(dòng)化的 UI 測(cè)試卻很困難。我們的 Mac 應(yīng)用的解決方案是基于 AppleScript 的測(cè)試。(這是確保應(yīng)用支持 AppleScript 的主要原因之一,而為了確保該支持的功能狀態(tài)正常,編寫測(cè)試是一種很好的辦法)

對(duì)于 Cocoa 的開發(fā)者來說,測(cè)試并不像在 Ruby,JavaScrpit 以及 Python 的開發(fā)中那么重要,這主要是因?yàn)榫幾g器和靜態(tài)分析可以捕獲到很多腳本語言捕獲不到的問題。

不過它們依舊很重要。

斷言

你可以在我們的代碼中看到我們正在使用的一些斷言:OBASSERT_NOT_REACHED, OBPRECONDITION, OBASSERT,等等。

我們使用這些斷言來表示一些推測(cè)和意圖。它們是為了我們自己的以及其它工程師開發(fā)的后續(xù)版本中而編寫的,我們大量的使用它們。

斷言太多的不好的地方在于你獲取到失敗的判定時(shí),你不得不找到原因。為什么代碼沒有按照期望運(yùn)行?是因?yàn)閿嘌允褂缅e(cuò)誤么,是不是斷言代碼需要被拓展或者修改?

我花了一段時(shí)間查看了許多的控制臺(tái)輸出記錄來確定這是不是個(gè)好方法,結(jié)論是,它是。

構(gòu)建

Xcode 結(jié)構(gòu)組織

每一個(gè) App 都有一個(gè) workspace 文件,里面包括了 OS X 和 iOS 的項(xiàng)目,并嵌入了許多項(xiàng)目中使用的框架。

配置文件

我們大量的使用 .xconfig 文件。你可以在我們的代碼中看到一大堆。

在 Omni 中,這是我之前沒有使用過的東西,甚至好幾個(gè)月都不曾看過,只知道它們可以正常被使用就行了。

Debug Builds

調(diào)試構(gòu)建

OmniFocus 使用獨(dú)立的數(shù)據(jù)庫和一些偏好設(shè)置進(jìn)行調(diào)試版本的構(gòu)建,所以開發(fā)者不需要擔(dān)心真實(shí)數(shù)據(jù)被調(diào)試數(shù)據(jù)污染。

(我們其它的應(yīng)用是基于文檔的應(yīng)用,所以并不完全適合上述方法,不過 OmniFocus 之外的應(yīng)用也會(huì)使用獨(dú)立的 App ID 來構(gòu)建調(diào)試版本。)(譯者注:防止和線上版本沖突或者 iCloud Drive 污染)

靜態(tài)分析

靜態(tài)分析被設(shè)置為深度配置,包括調(diào)試版本的構(gòu)建。本應(yīng)如此。

自動(dòng)構(gòu)建

我們有一個(gè)用于構(gòu)建的服務(wù)器,當(dāng)然,我們會(huì)在構(gòu)建失敗的時(shí)候得到提醒。OmniAutoBuild 是另一個(gè)內(nèi)部使用的 Mac App,我們可以在軟件中查看是什么導(dǎo)致了構(gòu)建的失敗。

構(gòu)建完整的、可發(fā)布的程序是由腳本完成的。我們會(huì)設(shè)置標(biāo)記將構(gòu)建好的版本放在演示服務(wù)器上,所以外部的試用版測(cè)試者可以下載最新的測(cè)試版本。

iOS 的測(cè)試版則使用 TestFlight。

沒有魔法

真希望我可以說自己有一些秘密咒語,這樣我就可以把它們告訴你。

不過,實(shí)際上,在 Omni 管理大型項(xiàng)目與你所想像的方式?jīng)]什么差別。詳細(xì)的溝通與定義 - 交流到人,聊天,使用 OmniBugZapper,使用斷言,做代碼審查,遵守編碼規(guī)范 - 這些都很重要。

接下來的事情是自動(dòng)化:讓電腦做它們最擅長的事情。

不過,回到最初,有一些事情是在選擇 Bug 追蹤系統(tǒng)、 代碼控制管理系統(tǒng)或者其它什么事情之前的,那就是公司文化。與優(yōu)秀的人一起建立一個(gè)基于信任、尊重的環(huán)境,他們會(huì)做出更好的決定,并從壞決定中吸取教訓(xùn)。

好消息是這些事情都在有條不紊地進(jìn)行中。

還有午餐,工作午餐。我們都在一起吃飯,這會(huì)產(chǎn)生一些區(qū)別。

P.S. Many thanks to the folks at Omni who read drafts of this article and provided feedback: Rachael Worthington, Curt Clifton, Jim Correia, Tim Ekl, Tim Wood, and Ken Case. Anything weird or wrong is my fault, not theirs.

另外,非常感謝 Omni 中閱讀過這篇文章草稿并提出反饋的人們,Rachael Worthington,Curt Clifton,Jim CorreiaTim Ekl,Tim WoodKen Case。如果有什么奇怪的錯(cuò)誤,一定是我犯了錯(cuò),不是他們。