Slide 1

Slide 1 text

Refactoring lesson : from GPA 1.4 to GPA 3.0

Slide 2

Slide 2 text

About me • xdite • 微信号 xxddite • 公眾号 Xdite

Slide 3

Slide 3 text

全栈营教头 • Ruby on Rails • Agile • Growth Hack • 认知⼼心理学 + 技术教学 https://ruby-china.org/topics/31080 無編程經驗的新⼿手,如何在四周開發實戰等級產品

Slide 4

Slide 4 text

为什么会来这次⼤大会 • 刚好我在北京 • 筹备组就问我要不要来 • 顺便投⼀一个题⺫⽬目 • 去年我都没有在燃烧我的激情 ( a.k.a 写 code ) • 想了⼀一下还是投了⼀一个题⺫⽬目

Slide 5

Slide 5 text

Refactoring lesson : from GPA 1.4 to GPA 3.0 酷,程序员听到「翻修」就充满激情

Slide 6

Slide 6 text

1.4 -> 3.0 codeclimate.com

Slide 7

Slide 7 text

3.0

Slide 8

Slide 8 text

1.4

Slide 9

Slide 9 text

背景故事

Slide 10

Slide 10 text

2014 年 7 ⽉月 • 矽⾕谷 O2O 送餐公司 • 种⼦子轮 拿了 250 万美⾦金 • A 轮拿了 1100 万美⾦金 • 来台湾征才 • 8 ⼈人公司加⼊入了 SpoonRocket • 公司⼤大举招⼈人

Slide 11

Slide 11 text

• Angular - 客⼾戶 Web 端 • Android • iOS • Rails - Admin • jQuery - Dispathcher (⼈人⼯工配餐台)

Slide 12

Slide 12 text

• 2 ⼈人技术团队,⼀一个⽉月之内扩编到 20 ⼈人 • ⼤大家就⼀一直踩到隔壁的脚

Slide 13

Slide 13 text

• 虽然技术团队 20 ⼈人 • 每天可以拉上超过 20 ⽀支 pull-request • 但是每天只能 deploy 2 ⽀支 pull-request • 凌晨 3:00 (台湾时间)开店,SA 只能睡公司....

Slide 14

Slide 14 text

• 经常性 Rollback • 20 ⼈人还是只有 2 ⼈人战⼒力 • 每天开店⽼老是炸 • ⽆无法做促销,呼叫⼀一只菜单 API ~= 700ms • 开启 Admin Controller 超过 60 秒

Slide 15

Slide 15 text

• 刚加⼊入没多久就后悔 • Grape API • 每⼀一只 Grape API 约 1000-2000 ⾏行 • 毫⽆无逻辑的 EndPoint • 重复逻辑到处复制贴上 • Admin 后台的单⼀一 index action 也⾼高达数千⾏行

Slide 16

Slide 16 text

• CTO 与第⼀一个 RD。凭借蛮⼒力硬是上线 • 永远保持能卖东⻄西就好 • 我⻅见识到的是 GPA 1.0 的代码也能募到这么多钱.... • 所以代码是否干净与能不能赚钱没有关系

Slide 17

Slide 17 text

⼈人⼈人都想 Refactor

Slide 18

Slide 18 text

但⼈人⼈人都不想 Refactor

Slide 19

Slide 19 text

• ⾝身为 RD Lead • ⼤大便只能由我先吃 • 毕竟这是我的⼯工作..

Slide 20

Slide 20 text

結果 • API 代码 95 % 以上覆盖 • 上了 CI • 全代码覆盖率 60% • GPA 3.0

Slide 21

Slide 21 text

挑战 1 :从哪⾥里开始?

Slide 22

Slide 22 text

• 快 100 只 API • 50 只以上 Controller • 快 70 个 model • 10000 多⾏行代码

Slide 23

Slide 23 text

答案:先补⽂文档

Slide 24

Slide 24 text

WIKI 上必须要有主要 API 的⽂文档 • GET / POST • 输⼊入参数 / 输出结果 • 回传值 • (真实资料)

Slide 25

Slide 25 text

• 取得全局感 • ⼤大致上有多少 API 我们得⾯面对 • 估计⼯工作量 • 估计「重构」所损失的战⽃斗⼒力 ( 必须公司可承受)

Slide 26

Slide 26 text

⽼老板说他只能承受 2 周没有⽣生产⼒力...

Slide 27

Slide 27 text

⽼老板说他只能承受 2 周没有⽣生产⼒力... 当然事实上是 1 天 都不能没有

Slide 28

Slide 28 text

挑战 2 :先写哪边的 Test ?

Slide 29

Slide 29 text

答案:先写 API Test

Slide 30

Slide 30 text

• 把每⼀一⽀支 APP 都开⼀一⽀支 Redmine Ticket

Slide 31

Slide 31 text

实作步骤 • 半强迫每个 RD,⼀一个礼拜必须写 10 ⽀支测试 • 没时间也⾄至少做到 • API 200 • 验证格式正确 • ⼿手头掰不出测试资料就使⽤用「真实资料」

Slide 32

Slide 32 text

• 牵涉到 3rd Party 的 API 不写 Test • 呼叫演算法的 API 不写 Test 
 
 
 
 
 
 • 因为代码太渣了,⽆无法强⾏行 mock…

Slide 33

Slide 33 text

• ⼀一个礼拜补完所有 API Test …(⾄至少有 200 与格式) • ⾄至少 RD 要是拉 pull request,没有绿灯就可以叫他滚 回去了 • 快速滤掉第⼀一层低级错误 • rollback 率⼤大幅下降

Slide 34

Slide 34 text

挑战 3 :先重构哪部分的代码 ?

Slide 35

Slide 35 text

从 API 分离下⼿手 • Before : ⼀一⽀支 API 档案 > 1000 ⾏行代码 • After :⼀一⽀支 APP 的「档案」必须⼩小于 160 ⾏行

Slide 36

Slide 36 text

从 API 分离下⼿手 • 原先 API ⼤大约有 10 个 Endpoint • 降到 3 个 End Point

Slide 37

Slide 37 text

开始有办法清楚的看懂代码

Slide 38

Slide 38 text

闪坑 • 不会再被 if / else 坑到 • 不会再也不知道⾃自⼰己再改那个 end point 的代码 • 有机会把「相近」的 Resource 放在同⼀一个档案 • 因为有 200 Test 可以放⼼心的搬家

Slide 39

Slide 39 text

抽出业务逻辑 • 把业务逻辑搬到 ServiceObject • API 只负责呼叫 Service Object • API 的 Test 只负责验证 200

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

• 添加 error! • 将业务逻辑细细拆分 • 终于可以仔细拆分不同 状况,不需到处 mock • 甚⾄至可以开始 stub ServiceObject

Slide 43

Slide 43 text

开始处理⽆无法 Mock 的 API • 要求「演算法」也抽出成 ServiceObject • 不测试演算法 • 但 API 必须补「格式」Test

Slide 44

Slide 44 text

⼩小结

Slide 45

Slide 45 text

• Step 1: 先估计「⼯工作量」 • Step 2: 装上红绿灯,⾄至少保证永远有基础绿灯。 • Step 3: 切细变成可以「重构」的区块 • Step 4: 盒⼦子打包法,补⿊黑盒⼦子测试

Slide 46

Slide 46 text

挑战 4 :补「重构」哪部分的代码 ?

Slide 47

Slide 47 text

CodeClimate Refactor 法 • 找出 F 等级代码。 • 专攻重复区域,打包成 method,补 unit test。 • ⼜又补了⾄至少 > 50 个 Test • 重复的代码 = 重要的业务逻辑(到处都⽤用到)

Slide 48

Slide 48 text

• ServiceObject (商业流程) • Calculator ( 计费可以随时抽换不同公式 ) • Validator ( ⼀一个 Order ⾄至少要经过 10 道验证,把验证做到可叠 加 ) • Serializer ( 原先是 Hash,改⽤用 Serializer 好管理 ) • Cells ( 让 View 可重复使⽤用 ) • StrategyClass ( 可策略抽换应付「营销活动」与「价格模型」 ) • Worker ( 把效能瓶颈移到背景去执⾏行 ) • Concern ( 复合使⽤用把常⻅见⼯工具类 method )

Slide 49

Slide 49 text

ServiceObject • 查价 / 库存 • 下单 • 退单 • 跟踪订单 • 计算运送所需时间 • 不同版本 API,使⽤用不同 ServiceObject

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Calculator • 不同地区不同税率 • 冷的不收税、热的收税 • 司机⼩小费 • 礼物卡的税务逻辑

Slide 52

Slide 52 text

Strategy • 地区营销活动 • API 上可插拔 • 活动结束直接关掉 Strategy

Slide 53

Slide 53 text

Worker • 付款呼叫 Stripe • 伪卡 • 霸⺩王餐⾏行为

Slide 54

Slide 54 text

SOA • 演算法 • Dispatcher (⾼高 JavaScript 耦合)

Slide 55

Slide 55 text

⼩小结

Slide 56

Slide 56 text

• 每个业务逻辑的接⼝口必须干净,容易插拔 • 每整理⼀一个业务模块,就补 Test • 修改 / 新增功能,必须补 Test

Slide 57

Slide 57 text

花了 1 个⽉月

Slide 58

Slide 58 text

• 2 pull request => 5 => 10 • 终于可以上 CI server • 不是绿灯,pull request 不会有⼈人理

Slide 59

Slide 59 text

讲完 API ,来讲 Controller

Slide 60

Slide 60 text

• ⼀一个 Controller ⼏几千⾏行代码
 
 
 • ⽼老板把逻辑都写在 JavaScript ⾥里⾯面 • 因为 Dispatcher 台是 JavaScript 写的 • 只好其他业务逻辑也⽤用同样的⽅方式

Slide 61

Slide 61 text

第⼀一步 • 把 Dispatcher 「以外」的部分搬去 Rails CRUD • 不太需要写 Test • 烂了请美国同事 hotfix,也不影响消费者下单 • 拆了 ~15 ⽀支 Controller

Slide 62

Slide 62 text

第⼆二步 • 把 Dispatcher 当作是 Client • 不再是 Rails 与 JavaScript 混合产⽣生 View • 显⽰示逻辑都使⽤用 JavaScript • Rails 作为 API 提供资料 • 补 API Test

Slide 63

Slide 63 text

• Frontend 组搞了很久 • 巨坑.... • 那时候剛有 React, … • 学到以后复杂逻辑绝对別⽤用 jQuery 先搞。

Slide 64

Slide 64 text

经过这⼀一轮 • 1 个⽉月就从 1.4 => 2.95 • 代码覆盖率到 40% + • 花了另外的 5 个⽉月 2.95 => 3.06 • 花了另外的 5 个⽉月代码覆盖率到 60% • ⾜足够好

Slide 65

Slide 65 text

挑战 5 :Release 太快带来的问题

Slide 66

Slide 66 text

• 每天 Release 15 ⽀支 pull request • db migration 或是改资料,还是个坑,需要⼈人为介⼊入 • 运营团队抱怨虽然没有 500,但是业务逻辑⼀一直在变 • 应该要出 Release ⽇日报

Slide 67

Slide 67 text

zendesk/samson

Slide 68

Slide 68 text

⼀一键 deploy

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

Deploy 架构 • samson 修改版 for heroku deployment • heroku pipeline

Slide 71

Slide 71 text

• ⾃自动汇集 pull request 成 deploy ⽇日报 • 必须叙述作⽤用 • 若有 migration 必须在 #Risk 环节注记 • Deploy ⾃自动执⾏行

Slide 72

Slide 72 text

成果 • GPA 3.06 • 60 % test coverage • 15 pull request/day • API has ~95% test coverage. • major API response time < 80 ms • We shipped 6-8 major features every week.

Slide 73

Slide 73 text

Take away • 先搞清楚有多少测试要写。 (否则会遭到公司否决) • 先写 Integration Test • Integration test -> Service Object test -> Unit test • 尽量提供 developer 「动机」去写 • 测试资料 • 可复制的测试代码

Slide 74

Slide 74 text

Take away • 做 Refactor 前⼀一定要先上 Test • 前期⺫⽬目标是「有动机去做」 • 中期⺫⽬目标是「接⼝口干净」 • 远期⺫⽬目标是「⾃自动部署」

Slide 75

Slide 75 text

不过

Slide 76

Slide 76 text

• 虽然「补测试」这么不爽 • 之后创业我还是不会先写测试 • 因为我理解了 GPA 1.0 还是可以拿到 1100 万美⾦金 • 商业的重点是「测试」你的 「idea」,⽽而不是「代码」 • 赚了钱你要雇⼏几个⼤大⽜牛来帮你重构都⾏行

Slide 77

Slide 77 text

Q & A