背景
最近在尝试将项目内的动态化方案独立成一个Module,其实这个项目已经在GitHub完成了开源,即 RapidView ,那为什么还要去做这件事呢?这还要从当初的一个失误开始说起,当初作者开源时并没有立即把这个能力独立成一个库(or Module),而是从项目代码里面选择核心代码修改了一份作为开源项目。这个选择可以说是埋下了不少坑,笔者在最近几周内感受颇深,于是做一记录。
梳理
业务代码耦合问题
GitHub上的代码是滞后的,因为主工程(应用宝主工程代码)里的代码和开源出来的很多地方不一样,所以很多修改都不能及时同步出去。鉴于此,本次改造的基础代码就是工程里面的代码(而不是GitHub上的旧代码)。
由于长期缺乏规范和约束,导致RapidView和主工程代码有很多耦合,如
使用主工程的日志开关和日志类,还有其他各种主工程的工具类
基础控件和依赖于主工程扩展的控件没有区分
逻辑里面耦合了主工程的统计上报逻辑、网络请求逻辑等不通用的逻辑
.....
诸如此类的问题,需要参考开源项目代码确定解决办法,基本原则是不通用的删掉,依赖的独立到Module里面。
这里不得不吐槽一下,之前主工程里面的代码一半是 Photon
(原始的命名方式) 前缀命名,一半是 Rapid
(因为以公司名义开源, Photon
不能使用) 命名,既然独立出来那就统一使用 Rapid
,这个问题才算是统一了。
感悟开源第一难:如何在主工程的快速迭代中保证 Module 部分足够独立,不受业务入侵(放在主工程里的时候,总有人会去做不合理修改,防不胜防),又能保持同步更新的节奏。
细节决定成败
总有人问我 “你觉得光子怎么样?”,我都会说“核心框架和设计思路很值得学习,就是很多细节做的不好。”
注:光子,就是我们的动态化方案, Photon的原意。因为开源关系,统一改成了 RapidView。所以这几个词说的都是同一个意思。
怎么说呢。下面是自己修改过程中遇到的一些典型Case:
单例写成这样,虽然大概率是手误,但是作为开源项目大概率是会让人觉得缺乏质量保证的(记得之前某团队开源前端项目就因为目录结构被知乎喷了)。
...
public RapidAssetsLoader(){}
public static synchronized RapidAssetsLoader getInstance(){
if(sInstance == null){
sInstance = new RapidAssetsLoader();
}
return sInstance;
}
...
基本的风格问题,这个
update
方法是要提供给开发者经常使用的,但是传入参数的接口,有的是view
在前,有的是data
在前,还和数据类型(LuaTable
)有关系,使用方踩坑概率目测80%+。
@Override
public void updateData(String view, Map<String, Var> data) {
mAdapter.updateData(view, data);
}
@Override
public void updateData(String view, LuaTable data, Boolean clear) {
mAdapter.updateData(view, data, clear);
}
@Override
public void updateData(List<Map<String, Var>> dataList, List<String> viewList) {
mAdapter.updateData(dataList, viewList, false);
}
@Override
public void updateData(List<Map<String, Var>> dataList, List<String> viewList, Boolean clear) {
mAdapter.updateData(dataList, viewList, clear);
}
// 注意,参数突然变成先传viewList,上一个是dataList
@Override
public void updateData(LuaTable viewList, LuaTable dataList, Boolean clear) {
mAdapter.updateData(viewList, dataList, clear);
}
@Override
public void updateData(LuaTable viewList, LuaTable dataList) {
mAdapter.updateData(viewList, dataList);
}
以上是个人选取的两个比较比较容易理解且典型的不足之处(由于已经开源,所以代码都是脱敏的)。
感悟开源第二难:如何把控好代码质量。
文档支持
之前在讨论为什么RapidView没有足够被开源界使用的时候,我就觉得缺少一个合格的文档。RapidView的文档对于一个完全不了解的人来说,可能不是那么容易上手,文档也缺乏层次:哪些是必须要知道的,哪些是作为进阶的,哪些是可以作为附录全部枚举出来的,哪些是隐藏规则需要补充说明的。
笔者也曾经思考过如何写好一份文档,发现还是很 难 的。在改造过程中,笔者也尝试补上了一个文档,当然由于时间紧张也不是十分完美。主要做了两件事:
自定义注解,从代码生成文档,降低了写文档和维护更新的成本:
@RapidTag(type = RapidTag.Type.action, description = "改变Binder中某一数据的值")
public class DataAction extends ActionObject {
static final String DATAACTION = "dataaction";
@RapidAttribution(description = "Binder里面的Key")
private static final String KEY = "key";
@RapidAttribution(description = "修改后的值")
private static final String VALUE = "value";
......
生成的文档:
2.使用GitBook等工具,重新组织排版,对读者更友好:
这里吐槽一下,原来代码的常量都是直接使用的,现在要一个个去提取出来,十几个还可以用快捷键,50个的直接暴走了
感悟开源第三难:如何提供文档、开发工具等周边支持。
网络部分
以光子的一个网络请求为例:
public class RapidRuntimeEngine {
private IListener mListener = null;
public interface IListener{
void onfinish(boolean succeed, String md5, String url, int limitLevel);
}
public synchronized int sendRequest(String rapidID, IListener listener){
return -1;
}
protected void onRequestFailed(final int seq, final int errorCode) {
XLog.d(RapidConfig.RAPID_ERROR_TAG, "实时视图数据协议请求失败,errorcode:" + Integer.toString(errorCode));
if( mListener == null ){
return;
}
mListener.onfinish(false, null, null, -1);
}
protected synchronized void onRequestSuccessed(int seq, Object resp) {
XLog.d(RapidConfig.RAPID_NORMAL_TAG, "实时视图数据协议请求成功");
if( mListener == null ){
return;
}
//mListener.onfinish(true, resp.zipMd5, resp.zipUrl, resp.limitLevel);
}
}
为什么返回的数据面要带 zipMd5
、zipUrl
这些信息,limitLevel
是什么意思,都是很重要的(如果真的要用起来),但是这里直接一个注释pass掉了。这其实代表了一类典型问题, 当把一个能力独立出来的时候,如何把那些业务相关的部分做足够的封装,让使用者既能够知道怎么接入,又不迷失在细节里 ,抑或是根本没看到细节。
感悟开源第四难:如何屏蔽内部细节,让接入方/使用者完成闭环。
感悟
总的来说,瑕不掩瑜,RapidView还是有很多值得学习的地方,也有很多值得反思的案例,在此稍作梳理,以告来者,以醒后人。