InfoQ 编辑部出品——2021年度技术盘点与展望 了解详情
写点什么

项目延期半年,我被软件外包坑惨了!

  • 2021 年 6 月 28 日
  • 本文字数:6503 字

    阅读完需:约 21 分钟

项目延期半年,我被软件外包坑惨了!

本文最初发布于 Rajiv Prabhakar 的个人博客,经原作者授权由 InfoQ 中文站翻译并分享。


多年前,年轻且天真的我决定与他人一起创业,但同时还要兼顾我们的全职工作。我负责技术开发,另一个创始人负责业务。我们的MVP计划是发布 iOS 和 Android App。


我在后端上有开发经验,但从未开发过 App。为此,我没有选择从头开始学习,而是决定雇佣外部软件开发人员来构建 App,而我则负责所有服务器端开发、P/SaaS 集成和基础设施。

合作始末


这不是我第一次创业。想起来,因为有过这种经历,所以我过度自信了。


以前的创业中,我曾经雇佣过一位年轻的兼职自由职业者,他来自另一个国家。他是熟人推荐的。他不仅很好完成了工作,而且每小时收费不到 10 美元。


当时,我并没有意识到这一点,但考虑到他的才华和责任心,他的报价非常便宜。现在,他在旧金山挣六位数的工资。


有点遗憾的是,他现在无法参与进来。我的联合创始人也想请一个更知名的组织来管理我们的前端开发。因此,我们决定寻找开发工作室,而非自由职业者。


当然,先要做一番调查。我们看了独立的第三方评审报告,询问了评审依据,并与开发工作室之前的客户进行了交谈,最终从多个工作室中选择了让我们最有信心的一家。该工作室收费标准约为每小时 25 美元——明显高于同类的自由职业者。不过,我们认为,雇佣的是一个有经验的专业组织,而非随便一个人,这对我们来说更合适。


我的联合创始人是一名律师,与他们签订合同时,务求详尽。众所周知,软件项目非常容易超支,所以我们协商签订了一份固定价格的合同,并对所有出现的 bug 都“保修”。花了很长一段时间后,我们才敲定合同细节,并在合同里详细描述他们应该构建的每个功能。然后,我们支付了第一笔款项并启动项目。


这里,我们犯下了致命错误!


根据合同协议,这个项目分为三个部分。在完成任何工作之前,我们就要预付 40%的费用。然后每一部分开发完成时分别再付 30%,但是在我们收到刚完成部分的交付成果之前。因为我们是个初创公司,并提前支付了 5 位数的费用,而且在完成下一笔付款之前没有获得任何可交付的内容,所以我们在整个项目期间都被锁定了。


虽然知道会发生这种事,但我们觉得没问题。因为他们有来自独立第三方机构的良好评价,优秀的客户口碑,而我们没有发现什么危险信号。此外,我们知道,在一个由别人创建的项目中增加一名新的开发人员并不不容易——所以我们计划在整个项目中与他们并肩作战。


事后看来,那是我们做出的最糟糕的技术决定,给了我们的初创公司一个沉重的打击。

技术挑战


按照我们的想法,这款 App 需要具备的一个关键功能是实时聊天。在合同谈判时,他们提出一些 SaaS 方面的建议来简化实时聊天功能的构建——其中之一是 Twilio Chat。在研究了他们提出的各种不同建议后,我们觉得 Twilio 似乎是最好的选择,于是,我俩就同意将其应用于我们的聊天功能。


遗憾的是,在开始构建时,他们遇到难题。他们不知道如何在 React Native 中使用 Twilio Chat,尽管是他们最先推荐使用 Twilio Chat 和React Native


更糟糕的是,他们并没有坦白地告诉我们,他们陷入了困境,而只是简单地告诉我们“Twilio Chat 不适用于 React Native”。现在,他们想让我们切换到一个完全不同的聊天服务提供商(由一个我们从未听说过的公司提供),然后重新开始,而我们需要为此支付额外的费用(即使这本是一个固定价格的项目)。


最糟糕的是,他们从开始说的话就不是真的。Twilio Chat 用在 React Native 中完全没有问题——他们只是不知道怎么做。最终,作为一名没有任何 React Native 开发经验的开发者,我花了很多时间去研究解决方案,并教他们应该怎么做。即使在我向他们做了演示之后,他们仍然需要我给他们提供文档链接,并向他们解释如何使用 Twilio API。


如果我没有和他们在一起,或者没有替他们想出办法完成这项工作,那么我们可能就会采纳他们的建议。我们可能会完全抛弃 Twilio,转向一个完全不同的、低标准的服务。这个决定可能会让项目推迟好几个月,并多花一大笔钱。

在安全上马马虎虎


我希望关于 Twilio 的问题就此结束,但这还没完。所有 Twilio 聊天信息都属于一个通道,而通道可以标记为“私有”或“公共”。顾名思义,私有通道属于通道中的特定用户,而公共通道可以“被非会员看到和加入。此外,公共通道及其成员和消息对于给定服务中的每个客户端端点都是可见的。


显而易见,所有的非公开消息都应该使用私有通道来实现。但令人惊讶的是,他们都是用的公共通道——这是我在浏览 Twilio 控制台时看到的。如果我们已经上线了他们的实现,只要是有一点点开发经验的人,就能够窃听每一个 App 用户的私人谈话。如果我自己没有发现这个问题,开发公司肯定不会安排任何渗透测试人员来发现这些安全问题。


这样的错误令人无法容忍。更令人震惊的是,他们非但没有为自己的严重疏忽而道歉,还不愿意更改。显然,使用公共通道实现聊天功能更简单,因此,他们更愿意保持这种方式。只有在我们多次抱怨后,他们才最终同意改变实现方式。

Bug 无处不在


我们之所以愿意雇佣开发工作室,而不是个人自由职业者,是因为他们承诺给我们的其他支持。特别是 QA 团队,他们会在向我们展示应用前进行详尽的测试。


任何软件项目都会遇到 Bug,这是不可避免的,所以我们理解他们不能做出任何承诺。但我们相信了他们的话,他们说我们应该只会发现一些极端情况下的 Bug。


后来我们发现,这完全是一派胡言。我们从他们那里收到的所有交付满是 Bug。甚至最基本的功能都不能工作——我甚至怀疑,即使他们测试过,他们也不是用真正的手机测试的。在整整一周的时间里,我和我的联合创始人每天都要花上几个小时,煞费苦心地测试,并记录所有出现的 Bug。

程序只求可运行


举例来说,我们发现的一个 Bug 是,如果用户的联系人超过 50 个,就只有前 50 个会在 App 中显示,其他的都无法访问。事实是,我们的一个 SaaS 集成被分页了,开发人员只实现了获取第一页结果的代码。


因为这个 Bug 只有在一个用户有 51 个联系人时才会被触发,而且我们尚处于私人测试阶段,所以我们过了一段时间才发现这个 Bug。之后,我们向他们做了反馈,问题很快就得到了修复。我们测试了他们的修复结果,似乎一切正常。


但在审查他们的代码变更时,我发现,他们的修复方式是多么的旁门左道。他们没有用一个 while 循环来获取所有的结果页,而只是简单地添加了一个 if 条件来获取第二页的内容。一旦用户的联系人数量超过 100,我们就会再次遇到完全相同的错误。


我可以原谅第一个 Bug,把它看成是无意的。但第二个 Bug 就是故意失职了。他们一定知道,我们要过很长时间才能触发第二页查询结果,要过更长的时间才能触发第三页。他们清楚地知道自己在做什么,知道“修复”的局限性,但他们还是那样做了。如果没有人仔细检查他们的代码,这个 Bug 就会进入生产环境。

没有版本历史


作为一名开发人员,我亲身体会到版本控制历史是多么有用。它可以帮助未来的开发人员了解为什么要做出某些设计决策,特定的功能是如何构建的,以及如何构建其他类似的特性。


出于这个原因,在合同谈判中,我特别坚持最后的交付物应该是一个 Git 存储库。他们欣然同意,并说他们内部也普遍使用 Git。


遗憾的是,在交付源代码的时候,他们只给我们发送了一个压缩文件,其中包含所有源代码和生成的文件。


我提醒他们,根据合同,他们应该给我们一个 Git 存储库。事实上,在他们发送的压缩文件中,我甚至看到了一个“.git”目录——表明他们在开发时确实在用 Git。


第二天,他们很快就给我们发送了一个 Git 存储库,其中只有一次提交,而里面的文件与前一天发送给我们的 zip 文件完全相同。


我抑制着自己的挫败感告诉他们,我们想要整个版本历史,而不只是一次提交,而且还是提交的同一个 zip 文件。他们回答说,他们的 Git 存储库中有一些“敏感信息”,不方便向外人提供。因此,他们不能分享给我们。“合同只规定交付 Git 存储库。但并没有说存储库中应该包含所有的开发提交和历史“。

随意改变规则


在谈判过程中,我们多次提到服务器端 API 还没有完全实现,我们希望后端开发和前端开发同时进行。在项目开始时,我会把所有 API 端点提供给他们,其中一些会完全实现。这样,他们就可以使用这几个端点立即开始开发比较简单的特性。当他们完成这些功能时,用于下一批特性的 API 也就完成了。


我们的目标是避免延期,同时开展这两项工作,可以更快地推出我们的 App。这是我们预先明确并反复申明的内容。我们总是被告知,没有问题。


遗憾的是,付完钱之后,我们开始听到一些完全不同的声音。他们直截了当地拒绝开始任何工作,直到整个项目中每一个特性用到的后端都 100%完成开发并最终确定。


所幸,我们在合同谈判和设计工作上花费了大量时间,我几乎已经完成了后端开发。所以这并没有成为一个问题。但令人震惊和痛心的是,他们没有履行销售人员早些时候做出的承诺。

唯我独尊


当我们把他们当作潜在客户来交谈的时候,他们为我们铺开了红地毯。但一谈到实质问题,他们就坚持要按他们的方式来做。


例如,在研究了各种选项之后,我决定用Swagger来记录所有 API 端点、它们的输入、模式、描述和行为。这样,文档就嵌入到了代码中,能够自动生成,并保持更新。Swagger GUI还提供了一种非常友好的方式让我们可以浏览所有 API 文档,甚至可以直接从 GUI 进行 API 调用来做测试。


遗憾的是,这不是他们的做事方式。因此,他们拒绝使用 Swagger 作为文档源。取而代之,他们坚持让我们用电子邮件给他们发送一个 Word 文档,包含所有在 Swagger 中能找到的内容,但要按照他们指定的格式填写。


我们花了好几天讨论这个问题,最后他们让步了。但在整个开发过程中,他们的态度一直没有改变。我们雇佣他们,是为了让他们使用我们的后端 API 来创建移动应用。但他们却对 API 的实现方式提出要求。每当在 API 设计上出现意见分歧时,我们就不得不花好几天讨论,还要忍受他们的抱怨。


这种争论可能是源于他们对 API 最佳实践的热情,但我怀疑,他们主要是想让自己的工作尽可能简单。而且,他们经常弄不清楚如何利用现有的 API 实现所需的功能。

缺少直接沟通


项目开始后还有一个很大的意外,就是缺乏沟通。在我以前所有的工程项目中,在跨团队合作时,为了更好地了解和解决出现的问题,我们都会直接与工程师交谈。令我惊讶的是,这是他们明确禁止的事情。


按照他们的规定,我们只能与一个非技术的项目经理单点联系。尽管我们提了要求,但他们拒绝让我们与实际从事项目开发工作的开发人员联系。此外,他们的项目经理也拒绝通过实时聊天工具交流。他们坚持一切都通过电子邮件进行。


随着时间的推移,这带来了很大的沟通问题。每当开发人员遇到问题,或者有什么想不明白,他们就会把问题发给项目经理。然后,她会把所有的问题汇总起来,在一天工作结束时给我发一封大邮件。即使我很快回复了邮件,他们还是要到下一个工作日才能看到。


因此,即使是一个简单的问答也需要 24 个小时的时间,比较复杂的讨论则需要好几天,而不是聊 30 分钟,然后问题就解决了。值得庆幸的是,在项目后期,他们终于意识到这个过程是多么低效,并允许我们与他们的开发人员直接联系。遗憾的是,到那时候,一切都太晚了。


在项目刚开始时,我们就知道这会成为一个大问题。但他们向我们保证,这不成问题。可以肯定的是,我们的担忧变成了现实。事实证明,当你每天只能通过一封电子邮件进行沟通时,很难做到敏捷。

严重延期


很遗憾,上述所有问题体现到了项目时间表上。原本应该是一个为期 2 个月的项目,最后却用了 7 个月。对我们来说,这是一个重大挫折,因为我们错过了许多潜在的用户,他们决定不再等我们的 App 发布。


现在回想起来,这些延误一点也不奇怪,因为他们缺少技术专家,坚持采用瀑布式方法,并拒绝通过聊天或电话直接沟通。但我怀疑,这还不是问题的全部。


我怀疑,在不同时段,他们有其他觉得更有利可图的项目,并因此降低了我们项目的开发优先级。这也是其开发团队在项目中途出现重大人事变动的原因。

推卸责任


在他们所有的失败中,要说有什么东西不变的话,那就是他们完全拒绝为任何事情负责。在执行任何任务之前,他们都会对自己的能力表现出百分之百的信心,并承诺结果不会有任何差错。而当他们没能兑现自己的承诺时,总是把责任推给其他人。


你们搞不清楚如何使用 twilio SDK?

在 React Native 中无法使用 Twilio 聊天软件

(事实是可以)


你们的聊天实现会暴露所有的私人对话?

替代方案太复杂了

(这是我们雇佣你们的原因)


为什么某个屏幕要花 30 秒来加载?

我们必须进行 5 次 API 调用,这使它变慢了。

(这 5 个 API 调用加起来不到 1 秒就完成了)


项目比承诺的时间长 3 倍?

服务器端 API 太差,Bug 太多。

(我确信,这与你们设定的项目优先级、能力和沟通方法不无关系)


令人沮丧的是,这种方法很有效!如果我自己不是一个开发人员,我就会相信他们告诉我的一切。他们阻止我们与开发人员直接沟通是有原因的——他们的项目经理是讨好人、传递信心以及转嫁责任的专家。愿上帝帮助那些雇佣了他们但却没有技术能力与他们辩论的可怜人。

经验教训


面对上面出现的所有问题,我很想说:"离岸开发者很糟糕。"但是,这样的结论既狭隘也过于局限。我与来自其他国家的优秀工程师合作过,认为优秀的开发人员只存在于美国,是非常愚蠢的。


我也很想说,永远不要把开发工作外包。如果你的公司像谷歌一样成熟,或者是由风险投资公司资助的初创公司,那么一切都要自己构建,并且使用工资六位数的开发人员。但是,对于一个创始团队规模不大的自给自足的初创公司来说,使用一些便宜的雇佣兵来帮助你完成 MVP 是有意义的。这是一个可以成功应用于其他场合的方法。


我们不禁会想,既然看到了上面出现的所有问题,那么应该可以通过谈判达成具体的合同条款来预防。这种做法注定要失败。有太多的未知因素和太多的主观性,不可能把所有东西都囊括在一个法律文件中。更不用说通过诉讼依法执行合同,这本身就是一个巨大的工程。


归根结底,当你雇用自由职业者或开发工作室时,重要事的只有一件,那就是:如果他们做得不好,你有能力离开他们。我们遇到的所有问题,都是因为我们缺少制衡手段。因为我们预付了很多钱,所以我们没有能力离开并雇佣其他人,即使事情变得非常糟糕。

一种更好的方法


合同一结束,我们就与他们断了联系,并大大地松了一口气。我真得感到卸下了一个大包袱。从此之后,我们从根本上改变了与外部开发人员的合作方式:


  • 针对我们想要构建的功能,拟定一个顺序列表。

  • 找几名开发人员,最好是独立的自由职业者,但如果同意以下流程,开发工作室也没问题。

  • 对于每名开发人员,挑选列表中最重要的功能,与他们讨论功能需求、预算和成本。

  • 让他们实现那个特性并测试。

  • 让一名内部人员审核他们的 PR,测试升级后的 App,并标出有问题的地方。

  • 符合要求后,合并并部署该特性,这样,所有创始人/用户就可以继续审核该 App,并提供反馈或者根据需要调整。

  • 如果我们对他们所做的工作感到满意,就挑选下一个我们希望他们实现的功能,然后再次重复这个过程。

  • 如果我们对他们的工作不满意,就解雇他们,并寻找替代者。


我们采用了上面的渐进式方法,摆脱了巨大的预付合同,开发过程就变得非常顺利,非常令人愉快。一切都好极了。开发人员与我们相处得更加愉快,也表现出了更大的灵活性,与我们的沟通也更坦诚,并在更短的时间内交付了更好的工作成果。


最重要的是,我们不会长期绑定到一个开发工作室,这使我们摆脱了风险,让我们感到无比安心。如果对事情的发展方式不满意,我们就可以在一周后离开。这在一些情况下拯救了我们,并减轻了我们的负担。


反过来,我相信开发人员也会喜欢这种灵活性。我们持续合作的内容是双方每周协商一致的事情,他们不会觉得是迫于先前的合同在做事。


如果你避免了我们的错误并雇佣了合适的开发团队,那么“大瀑布项目”是否有可能获得成功?当然有可能,但是,你真有信心自己不会遇到同样的问题?这一系列的问题让我对敏捷有了新的认识,也理解了敏捷出现的原因。


  • 客户合作胜于合同谈判

  • 个体和互动胜于流程

  • 可运行的软件胜于详细的文档

  • 响应变化胜于遵循计划


事实证明,许多开发工作室都拒绝采用这种工作方式,而是坚持使用瀑布法,并签订大额的预付合同。但在今后所有的工作中,我会坚持我现在学到的东西。


原文链接:


https://software.rajivprab.com/2021/04/26/experiences-working-with-an-outsourced-dev-shop/

2021 年 6 月 28 日 14:133222
用户头像

发布了 397 篇内容, 共 178.7 次阅读, 收获喜欢 958 次。

关注

评论 3 条评论

发布
用户头像
2个月开发完成一个软件,开什么玩笑
2021 年 07 月 05 日 07:52
回复
是挺匆忙的,但是数据分页问题应该出在后端提供的接口上,怪不得APP开发的人啊
2021 年 07 月 05 日 13:36
回复
用户头像
1
2021 年 06 月 30 日 11:56
回复
没有更多了
发现更多内容

【增强】(注解)SSM之配置多数据源

Java 程序员 后端

【死磕JVM】什么是JVM调优?

Java 程序员 后端

【源码分析设计模式 10】SpringMVC中的适配器模式

Java 程序员 后端

音频 AI 算法在 RTC 中的实践

网易云信

人工智能 算法 音视频

【springcloud合集】02:微服务架构理论基础

Java 程序员 后端

【数据结构 Java 版】玩转顺序表

Java 程序员 后端

【备战秋招冲击大厂】Java面试题系列—Java集合

Java 程序员 后端

【消息队列最佳实践】消息恰好被消费一次(1)

Java 程序员 后端

Java程序媛的秋招历程(附字节,阿里,百度,网易,美团等面经)

Java spring 程序员 面试 大厂

【程序人生】为什么Java开发人员在简历上不敢轻易写精通Java(1)

Java 程序员 后端

【程序人生】为什么Java开发人员在简历上不敢轻易写精通Java

Java 程序员 后端

做云原生时代标准化工具,实现高效云上研发工作流

CODING DevOps

云原生 研发管理工具 CODING

【玩转Linux】史上最详细的Linux命令大全和线上问题排查手册

Java 程序员 后端

【Spring Cloud 12】分布式架构下的高可用设计与可伸缩设计

Java 程序员 后端

技术干货 | Flutter在线编程实践总结

有道技术团队

flutter 大前端 #技术干货#

【大厂技术内幕】字节跳动原来是这么做数据迁移的!

Java 程序员 后端

【数据库实验】《小型MIS的开发》

Java 程序员 后端

【并发编程】Thread类的详细介绍

Java 程序员 后端

【并发编程系列3】volatile内存屏障及实现原理分析(JMM和MESI)

Java 程序员 后端

【备战秋招冲击大厂】Java面试题系列—数据库

Java 程序员 后端

【数据结构与算法 11】常见的7种排序算法

Java 程序员 后端

外包学生管理系统详细架构设计

stars

架构训练营

【Spring Boot 8】Okhttp实现GitHub第三方登录

Java 程序员 后端

【Spring 工厂】工厂设计模式、第一个Spring程序细节分析、整合日志框架

Java 程序员 后端

【Spring 工厂】注入详解 — Set注入(JDK内置类型

Java 程序员 后端

【springcloud】eureka服务治理入门

Java 程序员 后端

【源码分析设计模式 9】SpringIOC中的模板方法模式

Java 程序员 后端

【SpringBoot搭建个人博客】- 后台登录(四)

Java 程序员 后端

用APaaS平台落地高校闲置资产调剂业务

明道云

基于etcd实现大规模服务治理应用实战

百度Geek说

百度 架构 后端 etcd 服务治理

【消息队列最佳实践】消息恰好被消费一次

Java 程序员 后端

项目延期半年,我被软件外包坑惨了!-InfoQ