Omni 是如何做 APP 开发 & 管理的

659 查看

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 上有一个简单的代码风格规范,而我因为早已熟练的应用,反而忘了它说的是什么。

除了一个事情之外,方法应当像这样开头:

或许很多人并不知道,在 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 WorthingtonCurt CliftonJim CorreiaTim EklTim Wood, and Ken Case. Anything weird or wrong is my fault, not theirs.

另外,非常感谢 Omni 中阅读过这篇文章草稿并提出反馈的人们,Rachael WorthingtonCurt CliftonJim CorreiaTim EklTim Wood 和 Ken Case。如果有什么奇怪的错误,一定是我犯了错,不是他们。