工程Dropbox传输:让简单变得更简单

2020-06-27 00:25:00

在像Dropbox这样的老牌公司中,应用工程面临的挑战之一是打破增量改进的循环,重新审视问题。我们做用户研究的同事通过定期提醒我们客户的观点来提供帮助,但当我们的朋友和家人抱怨产品体验并不像应有的那样简单时,他们也可以这样做。

事实上,其中一项投诉导致我们的所有用户现在都可以使用一款名为Dropbox Transfer的新产品。Transfer允许Dropbox用户快速发送大文件,并确认接收,即使收件人不是Dropbox用户。

你已经可以通过Dropbox共享链接来做大部分了,但是在传输之前你不能做的事情对我们的许多用户来说是很重要的。例如,对于共享链接,内容需要在您的Dropbox文件夹内,这会影响您的存储配额。如果您只有一个大型视频文件就会超出配额,那么发送该文件将是一个挑战。共享链接的好处之一是它始终连接到文件的当前版本。但是,如果您想要发送文件的只读快照而不是实时更新链接,此功能可能会成为一个麻烦。

我们越深入研究,就越意识到文件共享和文件发送有着截然不同的用例和需求。对于文件传输,当收件人下载了他们的文件时收到通知真的很有帮助。这导致我们向发件人提供了一个关于下载和查看的统计信息面板,提示他们在未检索到文件的情况下与收件人进行跟进。与链接持久性是预期默认设置的共享用例不同,对于发送案例,许多人更喜欢短暂过期链接和密码保护的选项,从而提高机密内容的安全性,并允许“即发即忘”工作流。

由于这些差异,我们选择构建一个全新的产品来解决这些发送需求,而不是使我们现有的共享功能过于复杂。倾听我们周围人的声音(无论是不是Dropbox用户)帮助我们摆脱了基于Dropbox堆栈之上容易和增量构建的先入为主的观念。

这个博客讲述了Transfer是如何从首席工程师的角度构建的,从原型和测试到生产。

作为软件工程师,我们习惯于优化。一款软件上到处都是我们的指纹。性能、错误状态和设备平台策略(我们支持哪些设备)等问题主要由工程师决定。但是,我们针对哪些结果进行优化呢?我们关注的是性能还是灵活性?当激进的最后期限到来时,应该削减或修改哪些功能?这些成本与价值的判断通常由工程师在一个典型的月内进行数百次。

与机器学习一样,产品开发遵循演绎或归纳推理路径。在机器学习中,有两种主要的训练方法:有监督(演绎)和无监督(归纳)。有监督的培训从已知的输入和输出形状开始:例如,尝试曲线拟合一条线。无监督学习试图从数据中得出推论:例如,使用数据点聚类来尝试理解要问什么问题。

在演绎产品开发中,您构建了一个假设,“用户希望x完成y任务”,然后通过原型和用户研究进行验证。归纳法观察到“用户表现出x行为”,然后问“我们应该构建什么y?”我们用第一种方法构建了Transfer,并正在用第二种方法改进它。在这篇文章中,我将重点讨论第一个问题。

有很多方法可以提出这些最初的想法种子:开放式调查、快速原型制作和迭代、焦点小组以及现有工具或产品的仿真和组合。科技界较少提及的是最简单的方法:观察和检查你周围的环境。幸运的是,在Dropbox,需要解决的问题并不难找到。研究无处不在,因为受众基本上是任何拥有数字内容的人。如果我们倾听,我们就可以让他们指引我们,感官地检查我们的道路。

转会就是这样开始的。我的合作伙伴向我抱怨说,尽管我进行了宣传,但他们从未使用过Dropbox。对于简单的发送来说,它实在太难使用了。“为什么需要这么多点击?为什么它需要在Dropbox文件夹中才能发送?为什么我必须将我上传的所有文件都移动到一个文件夹中?“。当我听说我们可能在探索这个问题时,我欣然接受了这个机会。至少,我也许能说服我苛刻的心上人使用Dropbox!

随着产品的发展,我获得了更多的信心:我不确定我的会计是否收到了我用电子邮件发送的文件,一位摄像朋友想要一种快速的方式将他的原稿发送给编辑进行处理。最初的说服我的伴侣的个人追求很快就变成了一项非常全球性的努力。事实证明,她并不是唯一一个想要在Dropbox中实现新的单向发送范例的人。并不是所有的工具都像Transfer一样通用,但总的来说,仔细倾听人们的需求和反馈可以快速给出方向性指导。对我个人来说,这增强了我的信心,即转会可以产生很大的影响。

这是我五年来一直在这里工作的原因之一:Dropbox的用户无处不在。我的父亲在他的人类生物学研究实验室里存储着含有RNA的显微镜图像和文件;我的邻居将合同存储在Dropbox中,这样他们就可以在旅途中阅读它们;一些DJ在俱乐部里,用我们的音频预览来选择下一首要排队的曲目。成为人们日常生活的一部分是一种难以置信的特权。

戴利:如果你不确定某件事是否有意义,就问问一两个可能在目标受众中的朋友,作为一种感觉检查。

在最初的几个火花之后,根据我的经验以及团队成员的经验和研究,我们准备测试这个想法。我们试图清晰而有力地定义这个想法,要么是正确的,要么是完全错误的。我们不希望这里有不确定的结果,因为这会浪费我们几个月或几年的时间。我们开始证明或反驳这一点,“Dropbox用户需要一种更快的发送文件的方式,内置了设置和忘记的安全功能,比如文件过期。”

我们从一封电子邮件和一个登录页面测试示例开始:人们会对这个描述感兴趣吗?原来是这样的。然后,出于对实际用户行为的好奇,我们升级到了一个具有所有MVP功能的原型。与此同时,我们进行了一系列调查,其中包括一项基于支付意愿的调查,以确保市场存在。后来,随着我们发布新特性,我们开始监控产品市场适合度(稍后将对此进行更多介绍)。

作为一名工程师,重要的是始终理解这一假设,并感觉到如果某个功能没有达到核心假设,就有能力回击并建议缩小范围。这有助于产品和设计磨练他们的使命,用户拥有更干净的体验,工程师降低了只有0.1%的用户会使用的功能的支持成本。工程“做得到”态度的一个常见陷阱是允许特性蔓延,最终导致难以管理的代码库和杂乱无章的产品。就像产品和设计一样,代码应该寻求传达信息,其中最强大和最核心的部分对应于体验中利用率最高和关键的组件。

当工程师开始针对客户和他们的用例进行优化时,底层技术和方法就会与他们的需求绑定在一起。

每一个好的工程决策都是由大量的输入组成的。这些都是类似的事情:

根据这些传统标准,工程师可能经常陷入过度优化和无意中针对客户不关心的问题的陷阱。

确保始终将客户体验添加到这些工程输入中,这将有助于协调努力,以便在客户影响力最高的区域部署最高质量的代码。如果您将用户体验视为一种算法,那么这实际上只是根据Amdahl定律得出的经典性能智慧的重复:专注于优化客户正在(或想要花费)最宝贵时间的地方。

记住:陈词滥调的技术解决方案可以成为正确的工程解决方案。优化不重要部件的质量只会导致不重要的优化。

请注意:我并不提倡编写很多混乱的、容易着火的代码,只是为了始终保持对大局的了解。

在探索新产品领域时,用户的需求(和/或收入)在哪里并不清楚。将产品学习与可持续发展脱钩通常是有帮助的。在构建全新的曲面时,这些曲面的优化解决方案通常不会相同。

学习:优化灵活性。不惜一切代价向一小部分用户展示有价值的东西。这种类型的代码甚至可能不是代码,而是在像Figma这样的工具中构建的可点击的原型。

可持续性:优化以实现长期增长。这类代码可能包括轮廓清晰、优化程度较低的“褶皱区域”,这些区域可以随着产品的扩展而改进,并且需要更高效。它还应该包括与批处理或分页等扩展兼容的期望API。

烟雾弥漫。我们取了一个现有的产品,分叉了部分前端,并应用了一堆新的CSS,使一个基于图库的现有产品变成了一个基于文件列表的“新”产品。只需要少量的后端更改。

考虑到它最终会被删除,我们用注释块包围了所有的原型代码,如下所示:

结果呢?在使用了一个月后,人们看到它消失了,感到很难过,我们用肖恩·埃利斯(Sean Ellis)的评分对这种情绪进行了量化。看着它过去,我们不得不把这个带到第二部分,这已经够悲哀的了。

到了拆除临时产品的时候--一个建立在更多黑客基础上的黑客原型--我们的团队需要决定如何构建真正的产品。

幸运的是,传输建立在文件的概念之上,而文件是Dropbox做得很好的东西,无论它们是用来做什么的。我们高效、高质量的存储系统、防滥用和预览管道,经过多年优化,直接适用于我们的产品。不幸的是,向上移动堆栈,共享和同步模型无法重用。虽然共享和同步引擎背后的底层技术多年来一直在重建和优化(最新的创新飞跃是我们的Nucleus重写),但产品行为和共享模式在过去10年中基本保持不变。

大多数共享堆栈假设上传到Dropbox的文件总是可以在Dropbox内部访问并占用配额。此外,还假设链接将引用实时更新内容,而不是链接创建时文件的快照。

对于文件同步基础设施,有一个异步上载的假设:即内容最终将被添加到服务器的想法。对于一个本应在用户等待时立即上传和共享内容的产品,用于文件同步的排队和最终一致性概念将对我们的用户体验造成灾难性影响。

虽然同步和共享似乎与发送同源,但它们的底层技术有多年的产品行为假设。这将比我们必须放松的7个月的开发时间花费更长的时间,因此我们选择重新构建这些堆栈区域的大部分,而不是调整现有代码(同时按原样利用我们现有的存储基础架构)。对我们原型的反应让我们坚信,为了提供特定的产品体验,我们必须选择这条更艰难的技术路线,而不是改变产品体验,以匹配最短和最直接兼容的技术方法。

值得注意的是,每个“自己做”的决定都是在与平台团队的对话中做出的。我们只是需要一些太过遥远、验证不够充分的东西,不足以出现在他们的近期路线图上。现在,转移已被证明是成功的,我已经看到我们的产品团队拥有的基础设施代码量正在减少,因为我们的产品合作伙伴为他们的系统增加了灵活性,以适应我们的用例。我们没有严格依赖我们的平台合作伙伴,而是能够通过构建我们自己的解决方案来降低临时的团队间复杂性,并加快我们自己的路线图。事实证明,我们选择积极减少跨团队依赖的习惯对于实现我们的目标也是至关重要的。

类似的产品比你想象的更接近。在我们的例子中,我们发现发送相册与我们要做的事情有很多相似之处。这缩短了几个月的开发时间,因为我们能够利用一些古老的、但可用的、经过战斗测试的代码来支持我们最初的共享模型。

在大公司中,流程通常被开发成在其最大产品的规模下工作。当处理用户群最初比核心产品小很多数量级的新项目时,总是在开始与另一个团队开会时告诉他们您在不久的将来预期的用户群规模。团队可能会认为您是在“主要产品规模”进行操作,并围绕此提供基本指导。你的工作就是把这件事说清楚。这可以使您的团队永远不会发布产品,因为他们在解决100个用户的问题之前,就忙于解决200M的用户问题。

我们早期做的一件事是构建一组国际化的字符串组件。起初,这花了我们额外的时间,但是,由于我们知道大约一半的Dropbox用户会说英语以外的语言,我们知道这对用户的影响是非常值得我们花时间的。我们最引以为豪的工程时刻之一是,当我们即将向最初的alpha人群发布时,我们告诉PM(他们以为我们没有翻译产品),我们应该将全球人群包括在预发行组中。她欣喜若狂,工程师们自组织起来,决定必须这么做。

有时候事情就是不能及时完成。如果他们对产品的核心身份没有贡献,考虑让他们离开。对我们来说,这是最初不支持电子邮件传输的决定。通过复制链接发送对测试版来说已经足够好了。

在检查规格以进行广泛更改或阅读新代码本身时,问两个问题很有用:

沿着这条路这是什么地方?我应该看到多少块踏脚石呢?

在将核心身份构建到产品方面,我们会不断地从围绕什么是重要的,什么不是的决策中后退一步。我们还会不断评估,如果有必要的话,改变主意有多容易(用杰夫·贝佐斯的行话来说,第一类决定与第二类决定)。

一些最困难的调用是围绕我们的核心关键路径代码进行的,这些代码每月将处理数十万GB的数据。这些都是为我们(最初的)文件上传者和我们的底层数据存储模型准备的,继承的代码既不是最干净的,也不是经过单元测试的最好的。由于时间和资源的限制,我们不得不满足于简单的“战斗测试”和“呈现”,而不是其他因素。

我们为Transfer的网页版本选择的文件上传程序与网站其他一些区域(主要是文件浏览和文件请求)使用的文件上传程序相同。此上传程序基于名为PLUpload的第三方库的2012版本,这是一个功能非常齐全的库,提供了我们的产品将不会使用的功能和多边形填充。由于这个上传程序起作用了,所以很难证明在最初的产品构建过程中重写是合理的。然而,由于该库(至少是2012版)在很大程度上是事件驱动的,并且改变了DOM,所以当包装在Reaction组件中时,它立即开始导致可靠性问题。奇怪的事情开始发生了:在我们的bug攻击期间,上传时项目会随机地被卡住。由于DOM节点突然消失,长时间运行的上载将出错,导致节点删除的级联,因为Reaction试图按下损坏的DOM树上的“硬重置”按钮。我们选择保留它,但尽可能地抽象化。

我们对Nucleus迁移采取了类似的方法:我们从构建一个接口开始,该接口公开了我们想要使用的PLUpload的每个特性。这个接口由我们自己的数据类型组成,而不是PLUpload的。这起到了两个作用:

测试变得更好了,因为我们有了边界。我们既可以注入依赖项注入模拟库来测试产品代码,也可以将内部代码连接到测试工具,并对每个方法的I/O有明确的预期。

这个边界的另一个好处是,当我们有时间将库换成更简单的东西时,它最终会起到填充物的作用。这也迫使我们提前提出重写的要求,极大地提高了重写的效率。

我们选择的底层共享代码不是基于维护良好的Dropbox文件和文件夹共享链接,而是基于最初为共享相册而创建的更古老的链接类型。这些专辑链接让我们可以快速建立起功能转移链接。好处是我们采用了一个已知的系统:其他团队知道这些链接是什么。客户体验团队能够重复使用围绕这些链接的攻略和指南,安全和滥用团队已经有了这些链接上的威胁模型和监控,并且拥有共享基础设施的团队已经有了上下文。由于不必建立新的基础设施,我们能够减少变量,使我们能够更多地专注于产品开发,而不是基础性的变化。

为了允许我们稍后迁移到新的系统,就像Web上传程序一样,我们将这组古老的帮助器包装在我们自己的门面中。

随着我们的扩展和发布,很明显,我们做出了正确的体系结构调用:这些部分得到了支持和执行。好的工程既可以是约束,也可以是行动。当我们后来重新讨论这些问题时,我们有空间采取比几个月前更深思熟虑和更全面的方法。

注意:在2020年初,我们完全不再将数据存储在相册系统中,从而提高了可靠性、可维护性和性能。

每个十字路口都可能很关键。拥有包容的文化,其中每种声音都是基于论点的优点来考虑的,这是正确进行关键技术呼叫的重要组成部分。当上面提到的核心问题出现时,回答不当可能会导致漫长的弯路,未来的科技债务,甚至产品崩溃。有时候,维护一个劣质产品的成本比它创造的价值还要高。

在重用相册代码的具体情况下,我的一个队友强烈反对从这个系统中承担技术债务的想法。随后几周的讨论产生了一些提案、文档和会议,这些提案、文档和会议揭示并评估了不同备选方案所需的时间承诺。虽然我们选择把相册代码带到GA,但通过这些会议提出的观点激发了短期方法,填补了最初提案中未说出或缺乏的深思熟虑,并将团队聚集在一起,为我们共享模型的支持代码制定了统一的短期和长期愿景。这些会议帮助制定了彻底迁移出系统的最终路线图。

机智。

..