因为自工作以来开发的都是ToB的项目,每家公司的ToB项目开发模式也都不同,讲讲我个人对开发ToB项目的感受和理解。
0 ~ 1
ToB项目很多时候是从某个客户开始,这个客户可以是企业、政府等,无论从项目侧、产品侧、研发侧都是从0开始,一定会经过各种踩坑摸索,这个时候我们是绝对服务于第一个客户,他们有啥要求基本都会满足,并且这个项目肯定是亏本的(作为行业标杆项目)。
对于开发来说,其实并没有什么架构设计、代码洁癖、性能优化之类的考虑,直接就是干出一个app完成客户的需求就可以。因为从0到1的项目,无论是toC还是toB,能成功的概率并不高,很多时候可能拿下几家客户基本就到头了,如果一开始你就要设计架构,不但浪费研发成本也会降低客户交付时间,这并不是一件好事。
总体来说,这个时期我们不需要考虑其他的,只需要做到超预期的满足客户需求。
1 ~ 100
到了这个时期就表明我们已经成功拿下好几家客户,在行业内已经建立了标杆,并且PM他们也谈下了越来越多的客户,这个时候我们就需要升级开发模式/架构了。
此次升级开发模式/架构的目标:
- 降低人力成本。
- 支持客户定制化需求的快速开发。
0-1过程的项目开发模式必定是一个客户一个项目工程,再来一个客户的时候会拷贝一份工程在此基础上进行修改定制化客户的需求(当然你可以通过拉分支,但其实等同于完全不同的工程,因为你不会去合并代码的)。
那架构升级后的开发模式应该是怎样的?
就我个人的理解,应该是所有客户会公用一套代码,这套代码是我们的标准项目(指的是客户如果没有定制化需求,那么直接拿出这个标准项目就可以交付了), 我们假定这个标准项目在我们的工程里是 master 分支。
场景一:A 和 B 客户都会直接在master分支上打包交付。
场景二:这时候 C 客户有大量的定制化需求(钱给的多),于是我们要重新拉出一个分支,专门给 C 客户做定制化需求,假定这个分支叫 master_c。
场景三:这个时候来了 D 客户,D 客户只有很少的定制需求,比如说改文案、改颜色、微调布局等。这个时候是在master分支上针对D用户新建一个 flavor,这个 flavor 可以有自己的 string.xml, color.xml 等资源文件可以替换相同key的资源,当然布局资源文件也可以替换就能实现微调布局了。还有一些很小的逻辑差异需要在java,kotlin代码文件里去做的,可以通过 BuildConfig.FLAVOR 判断是哪个客户,当然这里的处理方式不止这一种,比如可以把开关写在 string.xml 里并判断开关是否打开,思路大体都是类似的。
场景四:这个时候来了 E 客户,E 客户和 D 客户的需求也差不多,所以还是在 master 分支上建一个 flavor 处理,不过它有一个比较特殊的需求,某个功能能开关,某个文案在特定的时候能修改。这个时候需要看公司是否有打算做一个配置中心了,这个配置中心其实也就是用key-value的形式配置某些开关或文案,实现动态化远程配置。
场景五:又来了个 F 客户,F 定制需求也不少,我们考虑拉一个分支 master_f 专门开发,但是发现其中一个功能已经在 C 客户上开发过了。这个时候最好是拉项目、产品一起去决策一个事情,这个功能需不需要放到我们的标准项目(即master分支)里,如果需要则把那块功能从 master_c 分支 cherry-pick 到 master 分支上,并做好开关。然后 master_f 重新合一下 master 分支即可。
场景六:在迭代过程中,在标准项目(master分支)上发现了一些bug,直接可以在 master 分支上修改,这样其他的标准项目的客户就能受益,一次修改个个都修复了,但是别忘了我们还有 master_c **master_f分支呢,这个时候就需要人工介入,如果此bug确实重要那就要通知各分支的负责人让他们cherry-pick下,如果不重要那么可以等到master_c/master_f分支有一次大需求需要全量回归的时候再把 master 分支合并一次。在master上开发的项目是幸福的,因为不需要关心太多的事。
场景七:如果在master_c/master_f上发现了bug,那对应的开发就不那么舒服了,这个是 1-100 的开发模式中个人认为最麻烦的。假设在 master_c 分支上修复了一个bug,这个时候需要考虑这次修复需不需要合到 master 分支上,可能要根据bug的严重性定性,大家伙一起讨论过后,第一种情况就是合并,这种情况还好,cherry-pick过去最多解决下冲突。但是第二种情况是不合并,不合并的理由可以多种多样,比如这个页面和主项目的页面布局、代码逻辑已经相去甚远,没有必要合,还比如说这个bug属于客户吹毛求疵的问题,其他客户没反馈并不重要等等。然后第二种不合并的情况越来越多了,和主分支的代码差异越来越大,于是当需要从 master 分支合并功能到 master_c 时或者从 master_c 合并功能到 master 分支就会发现有大量的冲突,每次解决冲突都要花费大量的时间。
以上几种场景相信已经覆盖了开发过程中绝大多数的场景,这个就是 1 - 100 的开发模式,能一定程度降低人力成本以及快速满足客户的定制化需求。但是当客户越来越多时你就会发现定制分支越来越多,项目维护起来非常困难,开发人员有一半的时间都在合代码解决冲突。
100 ~ ∞
100 - ∞ 这个模式是我个人认为的 ToB 最终形态了。这个时候说明我们的客户已经有几十家甚至上百家了。我们在这个领域基本做到了TOP3级别,这个时候客户源源不断,首先他们肯定是已经认同了我们的标准产品以及其他合作客户的定制化产品,对于公司来说已经占据了主导地位。
这个时候我们的目标:
- 进一步降低人力成本
- 模块商业化,按模块定价。
- 减少定制需求,提高定制需求报价。
当然这些目标有我自己意淫的成分,但实际确实也类似,因为我们已经占据了主导地位,以前是和一个个客户谈,现在就是明码标价,这个模块多少钱,那个模块多少钱,购买多少个模块就有优惠什么的。已经做成这样的规模,客户的定制化需求自然就需要比以前付出更多的报酬。
从技术的角度来说要实现以上的目标第一步就是组件化,第二步是构建打包系统。第二步这里就不详述,这也是一个复杂的工程。 第一步的组件化就是之前总结的 组件化系列 ,这里再简单总结下和之前的模式的区别。
模块插拔:
- 1 - 100 模式是不支持某个模块可插拔的,各个模块互相调用,耦合严重。所以在售卖过程中只能卖完整的标准产品。
- 100 - ∞ 模式支持模块插拔,几十个模块可以在打包时勾选,打出的app拥有不同的功能。
定制需求:
- 1 - 100 模式需要维护大量的定制化分支,这种分支还是以项目为维度的,各种合并显得非常麻烦。
- 100 - ∞ 模式如果有定制化需求也是以模块为维度,并且有对应的版本管理,可以给某个定制客户版本后面加个后缀(如 1.0.2-c 表示 C 客户的版本),这样管理定制需求的复杂度会大大降低,我们也不需要给某个客户拉大分支。 当然定制需求是一定会有人力成本以及管理成本的,这个是无法避免的。
扩展需求:
- 1 - 100 模式基本无法满足客户的扩展需求,如果客户想要自己开发只能通过h5的方式做些活动页。如果要开发原生功能,那只能购买我们整个项目的源码,之后就可以和我们断绝关系了(这种情况可能是需求太多报价太高导致的,有些客户也会去找报价低的竞争对手购买产品)。
- 100 - ∞ 模式可以开放扩展,让有开发能力的客户直接介入开发。即便客户还是想买源码,我们也可以以模块的维度卖客户需要的模块给客户,这样对客户来说价格也便宜,对我们来说还是掌握大部分的功能的主动权。
总结
本文也是组件化系列的最后一篇,总结了本人几年做 ToB 安卓端项目的经验和想法,希望对读完此系列的同学有所帮助。