iOS 开发栈

搞定移动端系统设计面试

01 Mar 2022

自从系统设计相关面试题被引入面试流程以来,它已经成为 FAANG 等各大互联网公司最喜爱的一类问题。过去这类问题只出现在后端的面试中,近几年开始被引入客户端的面试流程里。

系统设计类面试题与其他类型的问题相比,能够更全面、更深入的考察我们的设计和架构能力。

关注公众号“iOS开发栈”,了解更多iOS开发知识

系统设计面试题主要集中在两个主题中(设计和架构)。作为一个候选人,你更可能被安排移动端应用程序或某个特性的设计任务。比如说:

  • 设计一个图片分享应用(例如:Instagram)
  • 设计一个即时通讯应用(例如:WhatsApp、Messenger、微信)
  • 设计一个信息流应用(例如:Twitter、微博)

这类问题的目的是让你描述你是怎么把一系列抽象的需要转变成精确具体的解决方案的过程。它也让你的面试官通过你在不同方案之间的取舍来对你的技术深度有一个整体印象。

在当前富有竞争力的人才市场中,不管是大型技术公司还是小的初创公司都希望有一种新的流程来让他们能够筛选出天才程序员。正因如此,越来越多的公司开始采用这种系统设计的面试题来取代其他形式。

如果你之前没有经历过或者对系统设计没有很丰富的经验,那么你可能对系统设计类的面试有点害怕。移动端系统设计面试是怎么设置的有一层神秘的面纱。如果你之前搜索过系统设计相关的问题或者面试题,你可能已经意识到现在网上的很多相关资料(面试题、书籍、视频)绝大多数都是针对后端开发工程师的,针对移动端的非常少。我们也遇到了这个问题,这就是为什么我决定分别从候选人和面试官的角度来分享这篇文章的原因。

你很快就会发现这并没有什么可怕的。通过一些指导和练习,你完全可以放心大胆地准备并通过这类面试。最终你可能会爱上它。

系统设计面试流程

大多数的系统面试会持续大概45分钟(最多不超过1个小时),主要有这几个步骤:

  1. 6 - 8 分钟:面试官介绍和简短的开场白
  2. 4 - 5 分钟:问题陈述
  3. 25 - 30 分钟:设计并讨论你的解决方案
  4. 5 分钟:候选人向面试官提问

我看到大多数候选人——包括我自己会犯的一个非常典型的错误是认为自己有45分钟来解决问题,因为整个面试时间有 45 分钟。而实际上,正如你从上面看到的,你只有大概 30 分钟的时间来设计你的方案。因此,抓住时间是重要的。好好分配这 30 分钟的时间,尽力多地覆盖你掌握的知识。甚至在这仅有的 30 分钟时间中,也会有中断——面试官提问。因此,最好能够有一个清晰的计划并且进行充足的练习,以此确保你能搞定它。

💡Tip: 当你练习的时候,进行倒计时并迫使自己在 30 分钟内完成。之后,回顾你涉及到的主题,如果还有剩余时间就继续添加一些可以说的内容。每次联系完都要问问自己是不是把绝大多数相关主题都涉及到了?还有没有能够添加进来的内容?如果是的话,你要怎么把这些问题放进来呢?进行这些联系能够让你创造一个固定的路径和慢慢培养感觉。

面试官视角

在开始进入具体的面试之前,我们需要先了解桌对面的人 —— 面试官的视角。他们在寻找什么呢?

开放形式的面试目的是找到你的知识边界。让你选择你想要谈论的题目并且看看施行方案的时候,你是怎么考虑它们的。他们设计这种方式以便让面试官衡量接下来的事情:

  • 你模糊问题的能力。通过询问正确答案来处理,来将它分割成一系列具体的需求
  • 你的思考过程:在保持整个系统连接和完成所有需求的同时,你是怎么把一个大问题分割成更小的部分的
  • 你是怎么做决定的,评估不同的方案并做出取舍
  • 你的知识,你更熟悉 iOS 或 Andorid 的哪一部分。你能给服务端提出建议吗?
  • 最后但并不是最不重要的一条,你的交流和合作能力。你是怎么同步你的方案并赢得信任的

这里并没有正确或错误的答案 —— 只有不同的角度。并且你的面试官知道这个。因此不要关注在寻找最完美的方案上。而是集中精力设计一个在你的日常工作中每天都要使用的方案,运用你的知识来强化你的优势。有一次别人给我了一个建议 —— 对我一直有帮助,专注在你了解的东西。如果面试官真的想要让你成功,他们会对你了解的东西更有兴趣而不是你不了解的。另外,如果你能够更快的完成方案设计并且证明行之有效,那你会获得额外的分数。大多数情况下,最初的问题不会是整个练习,面试官会继续涵盖一些其他问题。这也就为什么接下来是一个循序渐进的过程,最开始是一个高屋建瓴的“更简单”答案,随着新需求的提出不断发展它,效果相对较好。

在面试期间选择你熟悉的领域是一把双刃剑。这给了你掌握对话和在你更熟悉的部分探索的自由,但是同时,面试官会假设那些你没有涉及到的内容就是你不知道的。记住,绝大多数面试官(尤其是大公司的)倾向于更保守的写下自己的评价。因此,最棘手的一点就是怎么在简短的涵盖问题所涉及的所有主题的同时又能深入到某些相关问题中之间取得适当的平衡。

掌握这个平衡点是非常困难的,主要是因为你和你的面试官关注的点不一样。幸运的是,大多数面试官会为你提供帮助。当你对他们关注的点有所纰漏的时候,他们会提示你,甚至会让你直接解决他们感兴趣的地方。因此,对你来说最好是认真听取面试官的提示,此外,如果不要害怕对自己有疑惑的地方提出问题。

搞定系统设计面试的技巧

下面是搞定移动端系统设计相关面试的策略。我已经在多年的面试中使用过这个策略,并且看到了许多成功的候选人表现出色。

💡Tip: 不要把这个策略当作万能药。我鼓励你花时间理解它,学习每一步的重要性和目标,并且把这变成你自己的。世界上没有两个完全相同的候选人,你自己最清楚什么东西更适合你。你有技巧,有经验,自信点。

这个策略由下面六步组成:

  1. 理解问题
  2. 定义范围
  3. 明确技术要求
  4. 提出高层设计
  5. 深入某一部分
  6. 圆满完成

让我们更详细地理解这六步吧!

理解问题

第一步应该是理所当然的。在构建方案前,我们需要理解问题。

这可能是最显而易见的一步,并且也是大多数候选人失败的地方,包括我自己 —— 很多次。为什么?因为我太快的跳到结论了。但是对我们来说这并不难。我们多数人会跳入陷阱是有原因的,正是因为众所周知的成见 —— 面试环境 —— 我们太想要证明自己了。

“跳跃式结论偏差,也称为推理-观察混淆,是一个心理学术语,指的是在没有足够信息来确定一个人/事是正确的情况下做出决定,这可能会导致糟糕或草率的决定,往往会造成更大的伤害。”来源于:Wikipedia

因此,现在你知道它了,不要再直接跳到结论了。减缓你想要开始设计方案的想法,并且避免成为那种在还没有理解题意的情况下直接跳到答案的候选人。取而代之的是,把面试官想让你关注的点具体化,这类应用程序最大的跳转是什么,以及之前你是怎么处理类似的问题的。

这也是面试官之所以给你一个模糊的、开放的问题的原因,这一步正是要细化它。记住,她正在评估你分析一个未完成问题的能力,识别出盲区,并提出正确的问题。

因此,在这一步,你想要提出清晰的问题来理解整个场景。思考你获得的信息之后提出相关问题来完成整个题目的全貌。

这里有几个我认为适合在此阶段提出的问题,取决于具体的题目:

  • 我们被要求设计什么东西?
  • 用户是谁,他们会怎么使用这个系统?
  • 初始用户规模是多少?预期增长是多少?
  • 给我们基础设计或者范围了吗,我们应该自己提出一个吗?
  • 我们是在设计 MVP(Most Valuable Product)还是最终产品呢?
  • 我们是从最开始构建吗,或者我们可以利用其他已经存在的部件?我们能利用哪个模式或者架构呢?
  • 实现并维护这个产品的组有多少人?
  • 我们只用实现移动端还是包括整个系统的其他部分(例如 API)?
  • 只用做 iOS 或 Android 端,还是要跨平台?要支持手机、桌面,还是都有?

你不需要问上面的所有问题,要根据具体的题目和已经得到的信息提问。对某个题目来说,只有某些相关问题需要优先考虑。

定义范围

第二步是找到对这个 App 来说你需要的功能或特性,并与面试官取得一致意见。

想想你使用过的类似的著名应用或系统,他们是怎么处理类似问题的。他们提供了哪些功能,哪些是主要功能。你可以建议一些你能想到的潜在功能,并就你要集中讨论的功能点与面试官取得一致。这应当是面试中协作的一部分。

一旦范围清晰了并且面试官也没有异议,你就可以准备进行下一步了,慢慢展开你的方案。

明确技术要求

真正有意思的地方从这里开始!一旦功能要求清晰了,你应该换个思路,开始考虑构建一个能够提供所需用户体验的解决方案所必需的技术方案。

让我们直接进入到设计移动端应用通常需要考虑的方面:

网络

现在大多数应用程序都需要从后端获取或者分享它们的状态。花费一些时间来考虑服务端,大多数情况下,服务端提供 REST API。已经有 API 了吗?如果有的话,它们是什么样的?

有没有功能必须要低延迟来模拟实时更新?如果有的话,你要怎么把信息推送到客户端?你可能需要比 HTTP 请求更复杂的方案。你可能要使用 push notifications、WebSockets、polling 等等。你需要考虑这些选项的优劣和作出的取舍。

安全性

正如我们上面提到的大多数应用程序都需要和其他系统交互。那么保证这些通讯的安全性就是显而易见的了。考虑下面的话题:

  • 授权(Authentication):你的方案是怎么进行用户验证的,怎么确保提供了访问的正确等级

  • 存储敏感数据(Storing sensitive data):你需要保存用户凭证吗?当然!除非你想提供给用户每次都要登录的糟糕体验。哪种凭证呢(access token,refresh token)?我们要处理个人验证信息(PII)吗?你要怎么安全的存储这些内容(Keychain,Smart Lock)?
  • 安全通讯(Secure communications):你要怎么保证和服务端的通讯安全性?例如所有请求都用 HTTPS。

可用性

应用程序要提供离线模式吗?大多数应用都会,只要你不想每次打开的时候都从空页面开始。你会用一个本地存储来缓存数据(例如 Core Data,Realm,SQLite,shared preferences等),考虑哪一个和原因。

对于图片和其他媒体数据呢?如果可能的话,当从网络获取之后,你可以把它们缓存到本地。这是一个好的选择,但是它也带来了一些挑战:一次处理多个请求,取消过期请求,清理策略(例如:LRU),限制同步请求数,等等。

可扩展性

移动端的可扩展性和其他系统有些不同,在服务端面试中,你需要设计系统来支持百万 QPS、分块存储数TB的数据。在移动应用中,扩展性通常联系着代码量和团队人员的增加。因此,考虑你是怎么为新特性进行设计的,以及怎么处理团队人数增加的。

你应该:

  • 把界面分割成更小的部分。以便不同的人可以高效的工作在不同的工作栈中
  • 界面标准化。通过构建可复用的 UI 库,可以在跨 app 的时候减少代码量和确保稳定性
  • 模块化(组建化):把功能分割成独立的模块(一个团队可以负责一块),并且提取可复用组件做成共享的核心模块。

性能

现在的移动端生态系统越来越多的应用程序通过提供平滑、流畅的用户体验来增加辨识度。要做到这样,你需要面对隐藏从服务端获取数据的挑战。

你可能会涉及到的关于性能相关话题:

  • 有 UI 敏感操作吗(例如 无限滚动,大量动画,复杂交互)?你要怎么支持这些?例如,你可能要提前加载数据并建立缓存。
  • 应用要加载大量数据吗 —— 图片、音频、视频?如果是的话,我们应该考虑怎么异步处理它们,因此我们确保界面流畅来持续提供尽可能好的用户体验,例如,通过把获取数据和刷新 UI 区分开。在你的方案中,什么可能会成为瓶颈或挑战?

测试

你可以简短的提到关于怎样保证 app 质量的问题。现在,默认方案是提供可依赖的测试套件,但是具体细节需要根据具体需求(例如 是否只是一个 MVP 产品)。你或许想要考虑并描述你的测试策略。

你可能会简短的讨论下面的话题:

  • 解释一个测试策略:你会怎么采用不同的测试类型(单元测试,集成测试,界面测试来全面覆盖应用流程)
  • 强调你的架构是如何让测试每一个部件变得容易的
  • 使用依赖注入来简化测试

监控

通常你不需要花费太长时间在这一部分,除非面试官问你。解释你的设计是如何确保系统正确性并在发生错误时快速反应是非常重要的。两个最重要的支柱是:

  • 崩溃报告和日志
  • 统计分析

部署

你是怎么预见系统上线的?这取决于面试官的要求。通常需要提高的话题有:

  • 带有自动发布的持续集成(CI/CD)。(例如 Fastlane _lanes _to send builds to production, QA and beta)
  • 利用远程功能标记来逐步推出更改,并将发布和审查分开(例如 确保新特性已经准备好面向市场)

这是本步所有内容!

哦。。。这些内容太多了,时间太紧了!不要紧张,目标是让你提高它们中尽量多的内容(一或者两句话),以便面试官知道你都考虑到了。除非它对题目是重要的或者面试官问到,否则不需要很详细。(记住,仔细听面试官的提示)。

提出高层设计

画出主要流程(如果没有提供的话)

如果你还没有得到流程图,在你深入开发之前的第一步是画出它们。这一步是至关重要的。这会让你和面试官同意主要的界面,UI 组件,用户交互和导航流程,之后这些会提示你的技术决策。你要确保你设计的功能满足给定的要求,并取得面试官的认可。

遵循下面三个简单的步骤,这将会是非常简单的:

  1. 画出主要界面,描述界面中的主要内容
  2. 梳理流程,根据用户的使用路径画出箭头
  3. 添加细节,讨论每一个界面的组成:找出主要的 UI 元素,分割 UI 组件(例如 CollectionView/TableView 中的 cards/cells)。考虑可能重用的元素。

💡Tip:不要花太多时间画一个高保真线框图。考虑最重要的:描述用户体验,集中在不同的界面和它们的组件,以及用户流程。

画出主系统(如果需要)

在这里,你应该询问面试官是想让你涵盖端到端系统还是仅仅是客户端。大多数移动端设计面试只专注于 app,但是这取决于你面试的岗位,用人单位要找的工程师类型和团队大小,他们会想你至少描述你对 app 之外的内容有基础的了解。

假设面试官让你设计的是一个端到端的系统。你通常要设计的元素有:

  1. 移动端
  2. API 服务(客户端会交互的那一层)
  3. 后端
  4. 数据存储(云上存储信息)
  5. 通知服务(如果需要的话)

定义基础的数据实体

在这里,你应该对你的设计和你的应用程序功能有一个好的概念。描述对你的问题最重要的数据实体应该是非常容易的。

💡Tip:不要过于深入细节。你并不是在设计一个数据库引擎。你只想列出实体(例如 users、posts、comments),提一下他们最相关的属性和关系。如果你的面试官询问细节,你可以继续深入。

描述主要的端点(需要的话)

取决于在你之前的询问中,面试官说的 API 是否已经提供,你或许需要设计断点。

💡Tip:随着方法的迭代,不要提出完全的端点规格。列出端点使用的 HTTP 方法(GET、POST、PUT、DELETE)和路径(例如 GET /post/:id/comments),端点需要的输入和输出参数。对于输出,不要写出完整的实体内容。和前面一样,向面试官确认。

提出客户端架构

回到应用中,是时候讨论并决定你的设计使用的架构和设计模式了。

回想你熟悉的标准架构(例如 MVC、MVP、MVVM+C、VIPER、RIBs 等),还有最常用的设计来在你的系统不同层上抽象和包装逻辑(例如 Repositories,Use Cases,Services 等等)。针对你的题目考虑它们的优势和劣势。哪一个更适合这个需求呢?为什么?

💡Tip:选择一个干净的架构会让你接下来的练习更加容易。这些架构使你的设计更容易分成更小的、独立的部分,来提高系统的可扩展性,灵活性和可测试性。记住,你的面试官或许以一个相对简单的问题开始,之后让你把它扩展到更复杂的场景。你的架构越灵活的应对新挑战,你的系统就会容易的处理更复杂的场景。

不幸的是,我无法告诉你最好的架构。这不仅依赖于你面对的问题,还有你的经验。如果你对它的认识不够,选择一个新架构也许是个错误。然而,我建议你选择你熟悉的,当你需要在面试中描述它的细节时,充分理解你架构的每一部分是基础。

于我来说,我倾向于使用更简单的,与趋势无关的干净架构,由以下几部分组成:

  • 展示层(UI):MVVM + Coordinator,来处理 view & controller 或者 activities & fragments,以及界面导航。
  • 业务层:从用户和仓库组织数据
  • 数据层
    • 仓库,从网络和本地存储获取数据
    • 网络数据:典型 API 客户端的各个端点
    • 持久化层:本地存储(如果有缓存的话)
  • 辅助服务:从不同功能中提取出来的函数,例如
    • 网络服务
    • Session 服务
    • 凭证存储

当使用标准组件的时候,我发现保持架构与趋势无关是有帮助的。我更喜欢根据实际问题添加需要的部分,而不是固执己见的选择(例如 VIPER)。这显然是一个个人习惯问题,你和你的面试官可能持不同态度。因此,我的建议是在练习时与她谈判,解释你所采取选择的权衡。

深入某一部分

是时候开始练习某一部分的细节了。当能够在高层次下给出好的解决方案,你的面试官更期望你能够深入浅出的描述设计组件。

这里有几个我采取的方法:

  1. 选择最有趣的界面并画出它的架构:包括不同界面元素的所有层级,VMs,Repos,Endpoints/Sockets,Network Layer,Local Store等等
  2. 描绘依赖,画出调用箭头
  3. 通过工作流,从用户视角出发:用户体验是什么?每一步用户看到的是什么?描述可能的界面状态:Loading,Error,No Data,Data
  4. 解释数据流:数据转化随着这样的路径:NetworkModel -> Business Model -> View State Model。画出来展示数据流(不同于依赖箭头,例如 使用虚线箭头或另一种颜色)
  5. 一旦你完成了上面所有内容,深入其中一个部分。考虑哪些东西可能成为最大的挑战,这个设计是否会有瓶颈,例如:
    • 实时刷新
    • 图片缓存(挑战,NSOperation Queue)
    • Cell复用(准备 VMs)
    • 数据缓存

💡Tip:再说一次,仔细听面试官说的。大多数面试官会让你选择一个或一些,但是也有一些会指定她向让你说的。或许,如果你忘记了。

圆满完成

快速回顾你的设计

回顾最初的范围和功能需求,以及你的设计是怎么满足它们的。

后续问题/延伸目标

在这里,你的面试官也许已经问你后续问题了。仔细听这些问题并且解释你的设计是怎么支持它们的。另外,问问她还有没有其他内容需要你展开讲。

进一步精炼和考虑

简短的涉及一些你能做的改进,如果你还有时间的话:仔细检查你上面的设计和技术考量,想一下哪里还有可以展开详细讲的。思考一下你刚才可能遗漏的技术考虑,现在或许是时候简短的提高它们了。对于你的一些想法的测试策略会是什么样?你会怎么使用 crash 上报和分析来确保系统正确性?你是怎么通过可访问性来保证程序的包容性的?

最后一点

通用建议

我们已经在描述我们方法的不同步骤中涵盖了很多内容了(我希望这些东西现在已经成你的了)。通过这些,你或许已经注意到一个通用的设计套件。让我们快速的过一遍:

  • 不要害怕提问:题目往往是模糊的,你需要收集必要信息来确保你解决的问题就是面试官想让你解决的。只有一个方法能做到:向面试官提问。
  • 验证你的假设:向面试官检查你做出的假设,确保你的方向没有问题。
  • 知道并使用你的工具:真正擅长绘制图表和同步方案。时间很宝贵,这会催促你加快速度。无论是一个白板还是在线工具,练习以一种清晰易懂的方式来构建你的想法。学习让你的图表更容易修改和扩展。
  • 分享你的想法:要想使面试更有效率,你就要持续地与面试官交流。记住,她想要理解你想的东西。
  • 为你的选择买单:对于你做的每一个决定,提及你考虑的不同替代方案,它们的优势和劣势,以及你为什么选择这一个和为此做的取舍。

练习,练习,练习

我已经提到了练习的重要性,练习可以提高这类面试成功的几率。现在,你或许好奇应该怎么做。这里有几个工作很好的建议:

  • 熟悉不同的标准架构:MVC,MVVM,MVP,Redux,VIPER 等
  • 设计常见应用:拿起你的手机,想一下你常用的应用以及它们最著名、最具挑战的功能(例如 Mail client、Instagram、Spotify、Twitter、Facebook、WhatsApp、Etsy),之后想一下你会怎么设计它。拿起一张纸开始设想你会怎么实现这些应用。这种简单的练习对大多数人都非常行之有效。相信我:这确实有用!
  • 阅读已经存在的方案:查阅来自大公司工程师的博客文章、录像等,比较一下他们是怎么解决这些挑战的。
  • 回顾一些有名的开源项目
  • 询问朋友和同事来检查你的设计,去的反馈。
  • 练习模拟面试,和同事或者其他候选人一起。

放松并享受过程

系统设计面试确实很难,有些让人不知所措,但是这也是富有创造性的,并且让你构想你从没有参与过的系统。准备任何面试都是充满压力的,但是如果你学会享受准备本身,这就成了一个能扩展你知识面的机会,这可以成为令人兴奋的体验。

因此,关于即将到来的面试

如果你正在阅读这篇文章,你很可能正在准备一场面试。

移动端系统设计面试就像一个智力游戏。一个你之前从没有做过的游戏,但是你可以带上这些部分来解决它。因此,请确保在您的工具箱中包含一组广泛的零件,并乐于仔细检查它们并选择最适合工作的零件。

这这篇文章中,我已经尽力分享面对这些面试的方法。但是这只是我的方法,这个方法我已经自己使用了几十次,并面试了几百个候选人。当描述它的时候,我的目的是尽力多的涵盖可能的话题,因此你可以形成适合你自己的策略。不要只是拿走我的方法,做你自己的!

在面试之前多练习可以做的更好,但并不是让你死记硬背。没有两个完全相同的系统设计面试,就像没有两个相同的候选人或面试官。记住,面试官引导你并想要让你成功。请注意面试官给的小提示和建议并将这些小数据整合到您的解决方案中。

感谢你读完一篇这么长的文章。我希望你会对移动端系统设计有一个更好的理解:它们的组成部分,面试官会如何评价,赢得面试的可靠方法。

如果你能分享你的经历,我会非常高兴。如果你有任何提示或者不同的策略请通过公众号分享给我。

祝你旗开得胜!

——————————————————————————————

资源

这里有一个简单的资料列表(文章,WWDC 视频,课程 等)来深入和扩展你在不同话题上的知识。

  • App Architecture - iOS Application Design Patterns in Swift by Chris Eidhof, Matt Gallagher, and Florian Kugler. [Book]
  • 33 Engineering Challenges of Building Mobile Apps at Scale by Gergely Orosz [Post Book]
  • System Design Interview: an Insider’s Guide by Alex Xu [Book Course]
  • Push technology. Wikipedia [Article]
  • Advances in Networking, Part I. WWDC 2019 [Video]

关注公众号“iOS开发栈”,了解更多iOS开发知识

这篇文章翻译自The MOBILE INTERVIEW