index模块重构
index模块包含
V业务
3个站点的首页和频道页,以下简称a站
、b站
、c站
。
背景
经过两年的业务迭代,index模块又到了难以维护的状态,通用组件复杂度高、代码质量低,给线上业务带来很大风险,急需梳理改造。
历史
模块重构需要从回顾历史开始,通过历史演变过程分析业务需求、总结好与不好的地方、发现研发的根本需求。
目录规范是代码的整体组织结构,能直观的反映出业务演变过程以及开发过程的思考。
0. 最开始,a站
首页
基于fis1开发
index
├── page // 页面模板
│ └── index
│ └── index.tpl
├── static // 静态资源,非组件资源目录
│ ├── index
│ │ └── index
│ │ └── index.css
│ └── ui
│ └── ...
└── widget // 组件
└── index
└── ...
1. 增加b站
首页
index_b
├── page
│ └── index_b
│ └── index.tpl
├── static
│ ├── index_b
│ │ └── index
│ │ └── index.css
│ └── ui
│ └── ...
└── widget
└── index_b
└── ...
2. 合并到一个模块
b站
首页和a站
首页有很多需求需要同步
index
├── page
│ └── index
│ ├── a_index.tpl
│ └── b_index.tpl
├── static
│ ├── index
│ │ ├── a_index
│ │ │ └── a_index.css
│ │ └── b_index
│ │ └── b_index.css
│ └── ui
│ └── ...
└── widget
└── index
└── ...
3. 再拆开
a站
首页的单独需求变得频繁,开发上线出现多次影响b站
首页的线上问题
index
├── page
│ └── index
│ └── index.tpl
├── static
│ ├── index
│ │ └── index
│ │ └── index.css
│ └── ui
│ └── ...
└── widget
└── index
└── ...
index_b
├── page
│ └── index_b
│ └── index.tpl
├── static
│ ├── index_b
│ │ └── index
│ │ └── index.css
│ └── ui
│ └── ...
└── widget
└── index_b
└── ...
4. fis-plus重构
- 模块化,以业务或功能为单元组织HTML、CSS、JS代码。
a站
首页和b站
首页共同维护- 抽出公共组件、设计代码适配逻辑
- 设计目录结构、部署上作隔离,避免代码冲突
index
├── page // 页面模板
| ├── data // 数据模板,用于数据拼装
| │ ├── a_home.tpl
| │ └── b_home.tpl
| ├── a_home.tpl
| └── b_home.tpl
├── static // 静态资源,非组件资源目录
│ ├── page
│ │ ├── a_home
│ │ │ └── home.less
│ │ └── b_home
│ │ └── home.less
│ └── ui
│ └── ...
└── widget // 组件
├── global // a站、b站公共组件
│ └── ...
├── a // a站业务组件
│ └── ...
└── b // b站业务组件
└── ...
5. 增加多个频道页
频道页之间有相似的逻辑,a站
和b站
也有相似的逻辑,怎么抽layout模板?
index
├── page
| ├── data
| │ ├── a_home.tpl
| │ ├── a_comic.tpl
| │ ├── a_movie.tpl
| │ ├── a_show.tpl
| │ ├── a_tv.tpl
| │ ├── b_home.tpl
| │ ├── b_comic.tpl
| │ ├── b_movie.tpl
| │ ├── b_show.tpl
| │ ├── b_tv.tpl
| │ └── home.tpl
│ ├── a_layout.tpl
│ ├── b_layout.tpl
│ ├── home.tpl
│ ├── comic.tpl
│ ├── movie.tpl
│ ├── show.tpl
│ └── tv.tpl
├── static
│ ├── page
│ │ ├── a_home.less
│ │ └── b_home.less
│ └── ui
│ └── ...
└── widget
├── global
│ └── ...
├── a
│ └── ...
└── b
└── ...
6. 引入FIS静态资源合并系统
基于组件访问的在线统计生成静态资源map表,达到最优的合并打包策略,不支持一个模块产出两套代码,部署上又合并到了一个目录。
7. 后来
因为各种业务需求,又增加了一些新的模板
index
├── page
| ├── data
| │ ├── a_home.tpl
| │ ├── a_comic.tpl
| │ ├── a_movie.tpl
| │ ├── a_show.tpl
| │ ├── a_tv.tpl
| │ ├── a_customize_home.tpl
| │ ├── b_home.tpl
| │ ├── b_comic.tpl
| │ ├── b_movie.tpl
| │ ├── b_show.tpl
| │ ├── b_show_mango.tpl
| │ ├── b_tv.tpl
| │ └── home.tpl
│ ├── a_layout.tpl
│ ├── b_layout.tpl
│ ├── c_layout.tpl
│ ├── n_layout.tpl
│ ├── layout_newhome.tpl
│ ├── home_new.tpl
│ ├── home.tpl
│ ├── a_customize_home.tpl
│ ├── home.tpl
│ ├── comic.tpl
│ ├── movie.tpl
│ ├── show.tpl
│ ├── show_mango.tpl
│ └── tv.tpl
├── static
│ ├── page
│ │ ├── a_home.less
│ │ └── b_home.less
│ └── ui
│ └── ...
└── widget
├── global
│ └── ...
├── hao123
│ └── ...
└── video
└── ...
模板越来越多,通用组件越来越复杂,线上部署未作隔离,代码未形成良好的规范,业务迭代风险极大。
重新思考,我们的根本需求是什么?
总体设计
根本需求
- 分治:模块化开发,降低复杂度、提升维护效率
- 资源复用:在模块化的前提下减少重复工作
业务拆分
按业务划分子模块
站点公共模块 | a站 首页、频道页 | b站 首页、频道页 | c站 首页、频道页 |
---|---|---|---|
common | aindex | bindex | cindex |
资源复用
伪复用
- 多类页面用一个模板,内部各种if else,增加了模板的复杂度
- 多类区块用一个组件,组件内部各种if else,组件引用传各种配置参数,增加了组件的复杂度
降低复杂度、提升维护效率是基本要求,在这个前提下再考虑代码复用
怎么定义复用范围
有哪些资源?
资源分类 | 说明 |
---|---|
JS模块 | 独立的算法和数据单元 |
CSS模块 | 独立的功能性样式单元 |
UI组件 | 独立的可视/交互功能单元 |
页面 | UI组件的容器 |
应用 | 整体项目或站点 |
从业务需求的角度对资源进行分类
- 从资源分类看是否合理,复用要有一定的语义,代码逻辑上有相似的部分,不一定要复用
- 针对index模块,需要的是抽离出aindex、bindex、cindex通用的组件
站点公共模块 | a站 首页、频道页 | b站 首页、频道页 | c站 首页、频道页 | 三站公共组件 |
---|---|---|---|---|
common | aindex | bindex | cindex | indexcommon |
其他类似页面和首页、频道页之间是否需要复用?
如果从业务需求上看相互独立,产品迭代上没有同步需求,在资源划分上应该互不关联。
UI上有相似或相同的区块,应该怎么做?
- 组件复用 !== 引用相同位置的组件
- 复制出已有的组件,作差异化修改也是一种复用方式
再次强调,按业务对资源作合理的分类才是重点
业务模块的划分
业务分类 | 子系统 |
---|---|
站点通用模块 | common |
首页、频道页 | aindex、bindex、cindex、indexcommon |
筛选页 | category |
详情页 | detail |
数据结构
数据是一切的基础,HTML结构 = 数据 + 模板逻辑,交互功能 = 数据 + JS代码逻辑,数据结构会影响模板逻辑和JS逻辑的复杂度。
V业务
的问题
服务端给到的数据结构过于原始,与页面区块缺少清晰的映射关系。
服务端给到的区块数据
$index_show => [
"num" => 10,
"items" => [
[
"title" => "",
"url" => ""
// ...
]
// ...
]
]
一个完整区块,实际需要的数据结构是
$block => [
"id" => "xxx",
"title" => "标题",
"more" => "http://xxx.com/aaa/"
"items" => [
[
"title" => "",
"url" => ""
// ...
]
// ...
]
]
或
$blocks = [
"id" => "xxx",
"title" => "标题",
"more" => "http://xxx.com/aaa/",
"blocks" => [
[
"id" => "xxx",
"title" => "标题",
"more" => "http://xxx.com/bbb/",
"items" => [
[
"title" => "",
"url" => ""
// ...
)
// ...
]
],
[
"id" => "xxx",
"title" => "标题",
"more" => "http://xxx.com/ccc/",
"items" => [
[
"title" => "",
"url" => ""
// ...
]
// ...
]
]
// ...
)
)
受限于内部内容管理系统
- 每个区块只有内容列表,没有区块自身的描述:区块名字、id、更多链接等。
- 区块之间相互独立,没有区块组的概念,比如页面上常见的Tab区块。
怎么改进
模板逻辑如果直接面向原始数据,需要作各种额外的数据处理工作,增加了模板的复杂度,中间需要加一层专门用来处理数据。
现阶段的做法是,把这些数据处理层分离到了一个单独的模板文件中,模板中通过include引入。
<%include file="aindex/page/data/home.tpl"%>
数据结构约定
基本原则
- 数据结构的设计一定要有语义,不要根据UI来确定结构。
- 在原始数据结构的础上作扩展。
- 确定各类基础区块的数据结构,定制化区块数据根据具体业务设计。
服务端原始区块
$mis_block_name = [
"num" => 10,
"items" => [
[
"id" => "", // id
"title" => "", // 标题
"url" => "", // 页面链接
"sub_title" => "" // 副标题
"img" => "" // 横版图
// ...
]
// 更多视频,格式同上
]
]
组装后的普通区块
// 区块名采用驼峰命名法
$block = [
"id" => "", // 必填,区块id
"title" => "", // 可选,区块标题
"stitle" => "", // 可选,副标题
"more" => "", // 可选,更多链接
"icon" => "", // 可选,标题图标
"links" => $mis_block_a.items, // 可选,链接列表
"items" => $mis_block_b.items, // 必填,内容列表
"iframe" => [ // 可选,插入外部页面
"url" => "", // 必填,页面url
"height" => "" // 必填,页面高度
],
"config" => [ // 必填,区块配置
"limit" => 10 // 可选,条数限制
]
)
Tab区块
// 区块名采用驼峰命名法
$block = [
"id" => "", // 必填,区块id
"title" => "", // 必填,区块标题
"stitle" => "", // 可选,副标题
"more" => "", // 可选,更多链接
"icon" => "", // 可选,标题图标,通常放于标题左侧
"blocks" => [ // 区块列表
[
"id" => "", // 必填,区块id
"title" => "", // 可选,区块标题
"more" => "", // 可选,更多链接
"items" => $mis_block_b.items, // 必填,内容列表
"iframe" => [ // 可选,插入外部页面
"url" => "", // 必填,页面url
"height" => "" // 必填,页面高度
],
"config" => [ // 必填,区块配置
"limit" => 10 // 可选,条数限制
]
)
// 更多区块,格式同上
]
]
禁止写入区块数据的字段
- UI层面的描述,如区块是否通栏,这种配置请直接在widget引用时传入
- 引用哪个widget,如tab区块,PM可能提出对某个tab内容差异化展现的需求,针对这种需求,首先要思考的是UI差异化背后的语义是什么,tab区块表示某一类数据的集合,分组展示,如果其中某组数据必须要差异化展示,必定有很强的语义支撑,请根据数据特征来满足差异化。widget的引用属于页面组装的逻辑,不要在数据层面描述。
组件开发
- 非通用组件:具体需求、具体对待
- 通用组件:分析共性和差异的过程,从内容和功能着手分析、而非UI层面
例子:内容列表组件A
频道页 | 字段:横版图 | 字段:坚版图 | 字段:标题 | 字段:一句话简介 | 字段:更新至 |
---|---|---|---|---|---|
电影 | - | √ | √ | √ | - |
电视剧 | √ | √ | √ | √ | √ |
综艺 | √ | - | √ | √ | √ |
动漫 | √ | - | √ | √ | √ |
区块 | 区块标题 | 区块内容 | tab | links | more |
---|---|---|---|---|---|
可选,无值则不显示hd部分 | a模板 or b模板 or iframe | 可选 | 可选 | 可选 |
内部逻辑的适配
- 能根据内容特征适配的,就不要提供配置项,降低组件使用的复杂度
- 数据结构中只标识数据特征
- 少数迁就多数,大部分组件的引用不需要配置,内置的默认配置就适用,少数差异化的需求通过配置项适配
创建:皮成,2016-07-01