Omni 內部
Omni 集團是一家屬於員工的公司,在這裡,人們可以帶他們的狗來上班。
換句話說:當你考慮如何管理大型專案時,首先得考慮文化 。“我們如何組織專案?用什麼樣的原始碼控制管理?” 文化並不糾結於這些細節,一個優秀的文化會形成一個快樂的團隊,他們將弄清楚如何一起工作。而 Omni 在文化建設上的成果可謂豐碩。
Omni 的文化可以另撰一篇長文,但在這裡不打算涉及太多。相反,這裡是一次圍繞著技術的遊歷,著重於討論我們如何管理自己的 App。
組織扁平化
所有的工程師向 Tim Wood 進行彙報,他是 Omni 的 CTO 和創始人。每個產品都有一個專案經理。
各個 App 的專案組是流動的。專案組本身不會經常或隨意地變動,不過它們終究還是會變化。
內部交流
因為大家都在辦公室工作,所以我們會面對面的溝通。有時會召開會議,其中一些是提前安排好的,而另一些則是臨時決定的。每個人都會參加一週一次的全體員工大會,時間大約是 20 分鐘。主要部門主管和專案經理說明接下來的工作方向。在之後還有每週的開發會議,這也持續約 20 分鐘。討論一些大家普遍關心的開發事項 (用於規避逐個找人溝通,雖然有時還是不得不這樣做)。
面對面的交流得益於 Omni 的核心工作時間:人們希望每天早上11點到下午4點的時間都在辦公室內度過,所以你知道你可以在那個時間裡找到想找的人。(不過,你也可以來的或早或晚,隨你心意,只要你在工作上投入了一整週的時間。)
其它途徑的話,包括 E-mail,包括一些郵件列表,和我們內部的聊天室加上私信。(我們最近將 Message 和 Jabber 替換為了一套可以儲存歷史記錄的系統,它還可以播放 GIF。)
Bug
每一個開發組織都有至少一個人負責解決 Bug 追蹤系統中反饋的問題。Omni 使用了一個內部的 Mac 應用 OmniBugZapper (OBZ) ,它具有那些你期望擁有的功能。它不像一些公開應用那樣矚目,但它卻是一個很有用的工具。
(在OmniBugZapper中的) 一個典型的工作流是:你在當前階段發現了一個 Bug,將它的優先順序調為高,然後開啟它,使其他人可以看見你正在解決這個 Bug。
一旦問題解決,你在這個 Bug 上新增一條筆記說明你做了什麼來搞定它,或許也會說明如何去測試,然後你寫上了版本管理系統中的版本號。
你將 Bug 的狀態切換為“驗證”,然後一位測試接收並確定 Bug 是否真的被修復了。如果沒有被解決,它可能會重新被開啟。如果修復衍生出了一個新的 Bug,一個新的 Bug 會被新增至 OmniBugZapper。
一旦處於“驗證”狀態的 Bug 通過了測試,它會被標記為“已解決”。
(前方高能:開發者與測試者之間的關係並非敵對,雖然我已經在幾家公司聽說過類似的事情。有些時候當你覺得測試的工作就是折磨你,但這才該是常態。這意味著他們做了了不起的工作,我們都有著同樣的目標,那就是做一個棒棒的應用出來。)
有一些 Bug 的修復需要工程師來驗證,但這比較少見。大部分 Bug 是通過測試來驗證的。(驗證的工程師與修復的工程師不可以是同一個人。)
有些 Bug 是永遠不會被修復或者驗證的。它們包括討論型 Bug 和參考型 Bug:我們通過在的前者基礎上進行討論來決定某個功能的改變或者增加,而後者可以用來對某個特性的行為或者出現做一些註解。
階段 (里程碑) 管理
OmniBugZapper 有階段管理 (milestones) 的概念,自然,它有一個階段監視器視窗,我們可以看到當前階段的進度和狀態。
每一個階段都劃分為分析、計劃和驗證。Bug 會經過分析變為計劃,或者留至以後解決。
決定一個 Bug 或者功能的開發階段與釋出版本是合作式的,每一個想要參與的人都可以參加。通常,專案經理會做大部分的決定。在比較大的問題上,通常會進行更多的討論,我們一般會達成共識,不過我們不會通過投票決定設計問題,我們的 CEO,Ken Case,有最終決定權。Ken 負責規劃未來。
譯者注:Milestones 可被翻譯為里程碑圖,是專案管理中的一種工具。為明確其功能,此處翻譯為階段管理,而每一個 Milestone 則被翻譯為階段。
原始碼控制管理
我們使用 SVN。所有的應用與網站都在一個巨型的程式碼倉庫中。我絲毫不驚訝於每個人的開發副本只是其中的一部分,而非全部。
你可能會覺得 SVN 是一個還未封印的史前工程師產物,並好奇人們關於切換工具的考慮。不過 SVN 乾的還不錯,如何簡化對一個大型倉庫的管理,總有些值得討論的事情。
我們有很多個指令碼幫助我們做這些工作。比如,當我想得到 OmniFocus 的最後一次更改,我輸入./Update OmniFocus
,然後它會更新我的開發副本 (通常我每天第一件事就是做這個)。我的開發副本中沒有 OmniGraffle,只因為我不需要關注它。但我也可以使用./Update OmniGraffle
。
SVN 建立分支可能不像 Git 與 Mercurial 那樣方便,但也沒有那麼困難。每當我們的應用接近釋出時,我們會建立一個分支,用於隔離其它的變動。人們可以出於各種目的隨時建立私人分支與目錄。
提交資訊通過 E-mail 傳送給工程師和其它關注專案的人。
崩潰
由於應用被置於大量自身也有 Bug 的系統框架上面,而應用也執行在實際的使用者的電腦上而非理想執行環境中,所以無法保證一個應用從來不會崩潰。
不過確保我們自己的程式碼不會崩潰是我們的職責,如果我們發現系統框架中有一個會導致崩潰的 Bug,我們需要找到方法繞過它,以讓程式碼正常工作。
我們使用了一些圖形化的統計來展示一個應用在崩潰之前的平均執行時間。我們還有一個內部應用叫做 OmniCrashSorter,我們可以看到一些被標記出來的崩潰日誌,包括異常的追蹤呼叫棧,以及一些使用者崩潰場景的記錄。
關於崩潰有這麼一個掃興的問題:崩潰永遠不會在開發時出現 (這似乎是一個軟體開發中的鐵律)。 這使得使用者的崩潰報告以及重現步驟非常重要。所以我們收集這些報告,並讓它們易於被檢視。
有時,我們會刻意使崩潰發生在異常中。因為我們的應用使用自動儲存,崩潰會比可能出現的髒資料覆寫更為安全。
程式碼
我們在內部的 Wiki 上有一個簡單的程式碼風格規範,而我因為早已熟練的應用,反而忘了它說的是什麼。
除了一個事情之外,方法應當像這樣開頭:
1 2 |
- (void)someMethod; { |
或許很多人並不知道,在 Objective-C 中允許這樣使用分號。沒錯,是允許的。
這種方式比較好的地方在於,它可以使我們很容易的將類的宣告拷貝到標頭檔案或者類的擴充套件當中。而且,你可以選擇一整行,Command+E,然後查詢包含它的標頭檔案 (或者從標頭檔案查詢回你實現它的 .m 檔案)。
我不喜歡這個方式。對我這樣一個極簡主義強迫症來說,分號太多餘了,而所有多餘的事情都應該被去掉。(想象我的 X-ACTO 刻刀慢慢的繞著一個分號刻上一圈,伴隨著一陣東北風,把它刮到空氣中,離開我的桌子,然後散落進垃圾桶裡,真是說不出的暢快。)
不過這只是我沒事兒時的抱怨。我想說的重點在於,我們需要一個程式碼規範,而且每個人都使用它。然後我們在閱讀彼此的程式碼時,就不會被引誘著去爭論關於分號的問題。我們不應該只為了比拼彼此的口味,把時間浪費在重新格式化已經寫好的程式碼上。
共享框架
Omni 所有的應用在某種程度上是同一個大型應用,因為它們依賴於很多個共享的框架。這些框架一部分是開源的,你可以閱讀相關的資訊,並在GitHub上獲取原始碼。還有一些額外的內部框架,一部分用在每一個應用中,還有一些則只用在部分應用裡。
共享的框架使開發一些不同的應用變得更容易,而且開發組的替換也變的方便,畢竟大部分框架都是相同的。
當然,有一點不好的地方,是某個框架發生了變化可能一次性影響多個應用。不過解決這個問題的唯一方法就是解決它,Bug 就是 Bug。
(當專案快要釋出時,我們會建立一個新分支,用來防止在接下來的幾週中框架開發中產生的變化。)
ARC
新的程式碼往往是基於 ARC 的。雖然有大量的舊程式碼還未被轉換,但這也沒什麼問題。畢竟隨著執行情況的變化,被調校的程式碼只應該是那些需要被校正的。不過有些時候它們依舊應該被轉換。(我已經做過一部分關於轉換的工作,以後還會做更多。我覺得使用 ARC 寫出不會發生崩潰的程式碼會更容易一些。)
Swift
雖然一批工程師已經開始編寫 Swift 的程式碼,Swift 目前尚未出現在我們的任何應用和框架中。
不過這也說不準,或許在明天,或許在一兩年之後,又或許就在你閱讀本篇文章的當下,改變就會發生。
測試
OmniFocus 中有覆蓋模型類的單元測試;其他應用也有差不太多的測試覆蓋率。我們與其他 OS X 和 iOS 開發者面對面臨的問題是相同的,每個應用的 UI 元素都很多,而做自動化的 UI 測試卻很困難。我們的 Mac 應用的解決方案是基於 AppleScript 的測試。(這是確保應用支援 AppleScript 的主要原因之一,而為了確保該支援的功能狀態正常,編寫測試是一種很好的辦法)
對於 Cocoa 的開發者來說,測試並不像在 Ruby,JavaScrpit 以及 Python 的開發中那麼重要,這主要是因為編譯器和靜態分析可以捕獲到很多指令碼語言捕獲不到的問題。
不過它們依舊很重要。
斷言
你可以在我們的程式碼中看到我們正在使用的一些斷言:OBASSERTNOTREACHED, OBPRECONDITION, OBASSERT,等等。
我們使用這些斷言來表示一些推測和意圖。它們是為了我們自己的以及其它工程師開發的後續版本中而編寫的,我們大量的使用它們。
斷言太多的不好的地方在於你獲取到失敗的判定時,你不得不找到原因。為什麼程式碼沒有按照期望執行?是因為斷言使用錯誤麼,是不是斷言程式碼需要被擴充或者修改?
我花了一段時間檢視了許多的控制檯輸出記錄來確定這是不是個好方法,結論是,它是。
構建
Xcode 結構組織
每一個 App 都有一個 workspace 檔案,裡面包括了 OS X 和 iOS 的專案,並嵌入了許多專案中使用的框架。
配置檔案
我們大量的使用 .xconfig 檔案。你可以在我們的程式碼中看到一大堆。
在 Omni 中,這是我之前沒有使用過的東西,甚至好幾個月都不曾看過,只知道它們可以正常被使用就行了。
Debug Builds
除錯構建
OmniFocus 使用獨立的資料庫和一些偏好設定進行除錯版本的構建,所以開發者不需要擔心真實資料被除錯資料汙染。
(我們其它的應用是基於文件的應用,所以並不完全適合上述方法,不過 OmniFocus 之外的應用也會使用獨立的 App ID 來構建除錯版本。)(譯者注:防止和線上版本衝突或者 iCloud Drive 汙染)
靜態分析
靜態分析被設定為深度配置,包括除錯版本的構建。本應如此。
自動構建
我們有一個用於構建的伺服器,當然,我們會在構建失敗的時候得到提醒。OmniAutoBuild 是另一個內部使用的 Mac App,我們可以在軟體中檢視是什麼導致了構建的失敗。
構建完整的、可釋出的程式是由指令碼完成的。我們會設定標記將構建好的版本放在演示伺服器上,所以外部的試用版測試者可以下載最新的測試版本。
iOS 的測試版則使用 TestFlight。
沒有魔法
真希望我可以說自己有一些祕密咒語,這樣我就可以把它們告訴你。
不過,實際上,在 Omni 管理大型專案與你所想像的方式沒什麼差別。詳細的溝通與定義 – 交流到人,聊天,使用 OmniBugZapper,使用斷言,做程式碼審查,遵守編碼規範 – 這些都很重要。
接下來的事情是自動化:讓電腦做它們最擅長的事情。
不過,回到最初,有一些事情是在選擇 Bug 追蹤系統、 程式碼控制管理系統或者其它什麼事情之前的,那就是公司文化。與優秀的人一起建立一個基於信任、尊重的環境,他們會做出更好的決定,並從壞決定中吸取教訓。
好訊息是這些事情都在有條不紊地進行中。
還有午餐,工作與午餐。我們都在一起吃飯,這會產生一些區別。
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 Correia,Tim Ekl,Tim Wood 和 Ken Case。如果有什麼奇怪的錯誤,一定是我犯了錯,不是他們。