谈谈组件化的历程
组件化是一个不可避免的阶段,个人认为也是一个越早越好的阶段。特别在一个公司内的产品越来越多的时候,显得尤为重要。
什么是组件化
组件化的字面意思,是代码上可复用的结果,而实际上组件化远不止这个层面。
在没有组件化的时候,每个项目都是各做各的事情,包括一个组内也是存在重复造轮子的行为,等到需要抽离独立模块的时候,又发现耦合过大,花费大量人力来解耦或者重构,甚至不了了之。
新项目的快速成型
立项
---> 确定需求
---> 搭建主工程框架
---> 引入需要的组件
---> 补充没有的组件
---> 成品
在一定的积累下,一个公司内部的新产品开发,都可以快速的搭建起一个拥有公司基础业务组件的框架,专心做差异的业务开发,而无须重新花费力气重新开发这些基础功能,例如一些性能监控,数据上报,测试框架等。
研发思维于流程的质变
组件化并不局限于基础底层的东西,业务逻辑,界面封装都可以成为一个组件,例如通用下发配置,相机相册界面等,都可以成为大组件化的一个积累过程。
在这样的环境下,研发的开发流程会变成下面这样
获得需求 ---> 前期分析和设计
---> Coding ---> 组件化(如果可以)
---> 完成开发
区别在于,在组件化的大环境下,大家在开发前期,会思考这个需求是否可以成为一个通用方案,而进行一些前期的分析和设计,可以的话,就会从中找到可以组件化的部分,成为一个独立模块。即使不能组件化,也会对于后续这个模块的低耦合方面有所贡献,引导大家面向接口编程,保持下层稳定性。
潜在的激励
每个研发都希望自己的代码得到认可,组件化可以潜在的推动这个事情。从组内组件化,到跨项目通用,到公司内公用,最后发展成对外开源,是一个不断鼓励代码优化和维护的过程。
探索之路
早期的开发可能都会经历过这么几个阶段,现在或许直接就到了最后的形态,再往后就在于是否坚持了。
以下列出了几种我经历过的工程形态,有些或许现在还是这样,我们可以顺着理一下,这里都是基于iOS的开发来讨论。
一个工程走天下
在早期,或者现在还有些项目,一个app只有一个主程序,所有代码按照文件夹来划分,实际上都在一个target上,很多开发会觉得很方便啊,这样有什么不好呢?
- 容易造成import泛滥
- import没有显示来源,不利于代码理解和解耦
- 没有一个很好的寻找特定代码的方向,容易重复造轮子
- 文件夹划分太弱,不利于大家规划同类的东西在一起,甚至一些通用的基础方法直接写在业务代码里
- 比如系统的Foundation,如果我找一个字符串操作相关,我一下子就知道该去这里找了
- 后期要解耦或者抽离的时候,工作量很大,如果还有交叉循环依赖,就更痛苦了
- 代码没有得到隔离
- 太容易触碰到的东西,很容易诱发不能用就改实现的行为
- 或者完全基于实现来使用接口,没有达到一个面向接口编程的目的
- owner也没有动力完善接口,基于实现告诉使用者怎么用
- 跨项目复杂度高
- 代码复用全靠copy
- 修改bug容易另外一个项目忘记修改
- 如果实现得太业务化,另外一边也用不起来
当然也不能一棍子打死所有案例,有些app就是简单,没必要那么复杂化,但是怎么也会有一些自定义的基础类封装吧,统一管理出来是个好习惯,说不定那天会感激自己的多此一举的
至于公司级别的app,基本上就不会简单到那个地步了,至少我个人是这么觉得的。
多工程模式
多工程模式下,一般有点规模的一个功能,都会独立成一个工程,放在主工程下,成为一个子工程,主工程依赖这个target即可。
多工程和多文件夹的最大区别在于
- 工程可以有不一样的配置,文件夹只是很弱的视觉划分
- 工程有明确的一个独立的姿态
- 工程一般会有一个Dependency的文件夹,把依赖的文件,库,子工程放这里,一目了然
- 哪天需要复用,我很快了解这些依赖有什么,解决这些依赖就可以了,文件夹的话,你需要看完每个文件的
.h
和.m
,才能汇总起来一共依赖了些什么 - 方便整个功能移植到其他app,或者在demo上测试开发,不影响主项目的流程
这里的好处是没有异议的,但是怎么很好地解决依赖问题是一个探索的过程
0x1 拖拽文件
从早期我们遇到一个子工程,需要一来到另外一个子工程的文件的时候,第一想到的办法就是把那些文件的.h
拖到这个工程来,然后发现这个.h
又依赖了另外的.h
,直到把所有头文件都拖进来了,总算完事了。
但是突然有一个文件上的改动,这个行为可能要重新做一次,这就很恐怖了。
0x2 子工程化依赖
既然我们已经用工程化的行为来模块化了每个业务或者说每个功能集,为什么不还局限于对某个文件的依赖呢?
你依赖一个业务里面的文件,很大概率你就会用到这个业务里的其他文件,既然这样,我们就把文件级别的依赖升华到工程级别的依赖。
也就意味着我们不再一个文件一个文件的拖,而是整个子工程拖到自己的工程下面,那么这个工程就可以访问整个子工程的公开文件了,何乐而不为呢?
0x3 统一工程化依赖
但是我们发现,依然没有完全解决这个问题,想象一下,如果某个工程被其他很多工程都依赖了,而这个工程位置变了,一个基础模块工程,一下子需要放到很多很多工程下,这个操作也是很难受的。
我们想了一下,创建了一个Bridge工程
,所有工程都依赖Bridge工程
,同时所有工程都是Bridge工程
下的子工程。
乍看之下好像有点死循环,其实不然。
-
Bridge工程
的编译并没有依赖它的子工程的运行,而是依赖子工程下的脚本,但是子工程依赖Bridge工程
的编译 -
Bridge工程
的编译会引发每个子工程脚本,生成自己的头文件路径,从而给到使用者 - 主程序同样不依赖
Bridge工程
,还是按顺序依赖其他真是子工程的编译
这种情况下,为了方便大家,还创建了一个工程模板,统一标准创建,且附带好所有脚本,唯一需要做的,就是把自己拖进Bridge工程
的依赖当中。
0x4 脚本时代
说到底,Bridge工程
开始还是需要人为拖动,且不利于新人理解
我们回归到最原始最本质的问题上:可以使用目标.h
文件
清晰了这么一点就好办了
- 每个工程只需要搭配一个配置文件,声称我这里哪些文件是Public的,这样就可以了
- 然后我们写一个脚本,遍历工程文件夹下所有配置文件,在对应的目录生成这些Public Header的soft-link,这样每个子工程都有自己的一个Public Headers的目录
- 使用者引用对应的路径即可
(可能很多人觉得这只是一个framework的事情,但是那时候还没有framework,而且目前一些老项目说不定还在支持iOS7)
脚本能带来的好处是很大的,除了方便了很多以外,甚至可以做到任何交叉循环依赖,在每个子工程上可以说用的风生水起了。
但是埋下的问题也是很可怕的
- 首先是这套方案是内部使用的,并不能推广出去,连跨组推广都有一定的复杂度,更别说公司外了
- 然后这套方便的方案,在设计模式层面上,可以说破坏的很厉害了,会削弱研发的设计思维
- 使用过程没有规范化,新人有一定的学习和理解成本
Workspace模式
0x5 Pod的到来
Pod的规范化可以说带来了一个最终的项目工程形态(当然也有些公司有自己更好的一套方案),主要得益于:
- 业内的一个通用标准和规范,降低相互之间的一个学习成本
- 更深一层的代码隔离,更好的代码引入方式,更丰富的配置
- 对私对公的repo机制,插件可自定制化,更少的人为配置依赖操作
- 不同分支仓库的组件差异化
对于Pod的模式和使用上,这里就没必要多说了,可以到Cocoapods的官网了解详细的内容,也可以Google一些插件定制化的东西来满足特定的需求。
这里先回答一个疑问,Pod毕竟也不是很晚期的东西了,为什么早期或者中期不直接开始引入这个模式呢?
一个客观原因是,在当时Pod刚起步,或者说还没有到现在这么普及,我们还在观望和不熟悉的阶段,的确是有点踌躇,通俗点说,对于陌生的东西,就是有点怂。
另外一个是历史原因,我们虽然看到前面几个阶段有很多不规范不成熟的地方,但是它们在当时的确是一个相对较佳的方案,我们来分析一下为什么:
- 这些旧方式都没有任何难度,想到一个优化点就可以马上动手,对原来的代码框架修改很少,可以说非常轻量化
- 0x4模式虽然很大程度上破坏了设计模式层面,但是不得不说在中国互联网公司快速开发迭代的节奏下,还是获得很多人的青睐,毕竟项目的发版压力还是有的
- 历史代码的解耦难度大,一下子需要抽离出来,不是一件容易的事情,另外大家对历史 相对稳定 的代码,也是睁一只眼闭一只眼的态度
阻力是什么
从前面的分析看来,不难发现,组件化的阻力从来不是技术上的,而是在于流程上的。
从技术层面上分析,组件化无非就是
-
选定组件化的方式
- Pod的方式对我来说已经非常合适了,不行就加点定制化的东西进去
- 如果牛逼一点的公司可以有自己更优的方案,但是毕竟维护一整套东西也是个成本
-
代码解耦抽离
- 从快速抽离的角度来看,解耦的做法,没有什么是一个中间层解决不了的,如果有,就两个
- 复杂的模块顶多多费点时间,缺乏人力的情况下,甚至可以大合集先包含进去,尽早先收拢再优化
-
后期维护
- 只是一个继续开发的过程而已
阻力从来都出现在流程上
组件化的推进,毕竟不是一个组的事情,也不能局限在一个部门上。在所有方案都确定好的时候,你发现需要跨团队跨部门跨子公司来沟通的时候,才是真正的阻力。因为每个部门每个组的情况都不一样,他们有着各自的项目压力和技术方向,更有着不一样的想法。
如何推进
推进的过程,我个人觉得是个艺术活,论外交手腕的重要性(会心一笑)
因为技术方案都确定好了之后,剩下的技术手段都是苦力活,真正花时间的可能都是交际手段
- 推销方案,并得到认同,保持步调一致
-
确定前期目标,收拢的目标项目有哪些,整理出来的组件模块有哪些,从而确定开刀的
主客项目
是谁-
主客
是谁这个很关键,相当于确定公共模块的标准是谁,用谁的代码,以后谁来维护
-
-
资源分配,也可以说是工作模式,最大的阻力环节
- 各个组来各自安排,这样会由于各自的项目压力和人力,把周期拖得很长
- 统一出人来帮忙各个组抽离组件,这样涉及到权限的问题,流程上就有点周折
- 另外还需要有人负责打通平台对接,使得涉及的平台支持新的方案
-
效果检验,这个是最重要的环节,不让其他人看到效果,这个事情只会是一次徒劳的苦力活
- 最容易看到的效果,就是拿新项目来试水,对比出成品的时间
- 长期来看的话,就是内部开源项目的数量,组件化的数量,甚至对外开源的项目情况
-
持续维护,需求是源源不断的,肯定需要有人持续跟进这个开发流程上的问题
- 需要持续优化过程中遇到的问题,需要高保证不出错
- 提升使用组件化的带来的副作用而影响的效率
这个过程如果是从上往下推动的话,显然比从下往上要容易得多。
最后
组件化显然是一个不可避免的趋势,如前面提到的,我个人认为是越早开始越好,对于开发人员的思维定性来说,一旦长期没有这个概念,乱糟糟的代码是不可避免的,也是会互相传染而泛滥。
虽然很多产品现在都是不在乎实现,但求无错不崩溃的态度,显示也不是一个可持续发展的做法,一定规模的公司更是要避免这种短期成品的行为。
也有一部分公司觉得需要的人力很大,在业务重压之下没有任何资源来做这件事情。我个人认为也是一个比较极端武断的想法,组件化并不要求一次性到达一个完美的阶段,它和业务一样是一个重复迭代优化的过程,在即使最糟糕的情况下,我也可以把所有明显的独立模块大集合先抽离出来,不要求代码有任何改动,只是单纯的抽离,达到一个可用的阶段即可,这样就可以先让全公司的项目,先用上同一份代码,无论是资源的释放,还是逻辑上的统一,都是一个非常好的开端,总之代码的收拢越早越好。
一些公司也有自己的SDK组,这些其实是组件化的前身,如果能进一步去演进和统一操作,相信会得到更多的好处。
以上仅代表本人个人想法,欢迎吐槽和交流