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