Pro Yearly is on sale from $80 to $50! »

Refactoring lesson : from GPA 1.4 to GPA 3.0

19e786a2a74377ff6e052d87fd8d1fa8?s=47 Yi-Ting Cheng
September 24, 2016

Refactoring lesson : from GPA 1.4 to GPA 3.0

RubyConf China 2016 - Xdite

19e786a2a74377ff6e052d87fd8d1fa8?s=128

Yi-Ting Cheng

September 24, 2016
Tweet

Transcript

  1. Refactoring lesson : from GPA 1.4 to GPA 3.0

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

  3. 全栈营教头 • Ruby on Rails • Agile • Growth Hack

    • 认知⼼心理学 + 技术教学 https://ruby-china.org/topics/31080 無編程經驗的新⼿手,如何在四周開發實戰等級產品
  4. 为什么会来这次⼤大会 • 刚好我在北京 • 筹备组就问我要不要来 • 顺便投⼀一个题⺫⽬目 • 去年我都没有在燃烧我的激情 (

    a.k.a 写 code ) • 想了⼀一下还是投了⼀一个题⺫⽬目
  5. Refactoring lesson : from GPA 1.4 to GPA 3.0 酷,程序员听到「翻修」就充满激情

  6. 1.4 -> 3.0 codeclimate.com

  7. 3.0

  8. 1.4

  9. 背景故事

  10. 2014 年 7 ⽉月 • 矽⾕谷 O2O 送餐公司 • 种⼦子轮

    拿了 250 万美⾦金 • A 轮拿了 1100 万美⾦金 • 来台湾征才 • 8 ⼈人公司加⼊入了 SpoonRocket • 公司⼤大举招⼈人
  11. • Angular - 客⼾戶 Web 端 • Android • iOS

    • Rails - Admin • jQuery - Dispathcher (⼈人⼯工配餐台)
  12. • 2 ⼈人技术团队,⼀一个⽉月之内扩编到 20 ⼈人 • ⼤大家就⼀一直踩到隔壁的脚

  13. • 虽然技术团队 20 ⼈人 • 每天可以拉上超过 20 ⽀支 pull-request •

    但是每天只能 deploy 2 ⽀支 pull-request • 凌晨 3:00 (台湾时间)开店,SA 只能睡公司....
  14. • 经常性 Rollback • 20 ⼈人还是只有 2 ⼈人战⼒力 • 每天开店⽼老是炸

    • ⽆无法做促销,呼叫⼀一只菜单 API ~= 700ms • 开启 Admin Controller 超过 60 秒
  15. • 刚加⼊入没多久就后悔 • Grape API • 每⼀一只 Grape API 约

    1000-2000 ⾏行 • 毫⽆无逻辑的 EndPoint • 重复逻辑到处复制贴上 • Admin 后台的单⼀一 index action 也⾼高达数千⾏行
  16. • CTO 与第⼀一个 RD。凭借蛮⼒力硬是上线 • 永远保持能卖东⻄西就好 • 我⻅见识到的是 GPA 1.0

    的代码也能募到这么多钱.... • 所以代码是否干净与能不能赚钱没有关系
  17. ⼈人⼈人都想 Refactor

  18. 但⼈人⼈人都不想 Refactor

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

  20. 結果 • API 代码 95 % 以上覆盖 • 上了 CI

    • 全代码覆盖率 60% • GPA 3.0
  21. 挑战 1 :从哪⾥里开始?

  22. • 快 100 只 API • 50 只以上 Controller •

    快 70 个 model • 10000 多⾏行代码
  23. 答案:先补⽂文档

  24. WIKI 上必须要有主要 API 的⽂文档 • GET / POST • 输⼊入参数

    / 输出结果 • 回传值 • (真实资料)
  25. • 取得全局感 • ⼤大致上有多少 API 我们得⾯面对 • 估计⼯工作量 • 估计「重构」所损失的战⽃斗⼒力

    ( 必须公司可承受)
  26. ⽼老板说他只能承受 2 周没有⽣生产⼒力...

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

  28. 挑战 2 :先写哪边的 Test ?

  29. 答案:先写 API Test

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

  31. 实作步骤 • 半强迫每个 RD,⼀一个礼拜必须写 10 ⽀支测试 • 没时间也⾄至少做到 • API

    200 • 验证格式正确 • ⼿手头掰不出测试资料就使⽤用「真实资料」
  32. • 牵涉到 3rd Party 的 API 不写 Test • 呼叫演算法的

    API 不写 Test 
 
 
 
 
 
 • 因为代码太渣了,⽆无法强⾏行 mock…
  33. • ⼀一个礼拜补完所有 API Test …(⾄至少有 200 与格式) • ⾄至少 RD

    要是拉 pull request,没有绿灯就可以叫他滚 回去了 • 快速滤掉第⼀一层低级错误 • rollback 率⼤大幅下降
  34. 挑战 3 :先重构哪部分的代码 ?

  35. 从 API 分离下⼿手 • Before : ⼀一⽀支 API 档案 >

    1000 ⾏行代码 • After :⼀一⽀支 APP 的「档案」必须⼩小于 160 ⾏行
  36. 从 API 分离下⼿手 • 原先 API ⼤大约有 10 个 Endpoint

    • 降到 3 个 End Point
  37. 开始有办法清楚的看懂代码

  38. 闪坑 • 不会再被 if / else 坑到 • 不会再也不知道⾃自⼰己再改那个 end

    point 的代码 • 有机会把「相近」的 Resource 放在同⼀一个档案 • 因为有 200 Test 可以放⼼心的搬家
  39. 抽出业务逻辑 • 把业务逻辑搬到 ServiceObject • API 只负责呼叫 Service Object •

    API 的 Test 只负责验证 200
  40. None
  41. None
  42. • 添加 error! • 将业务逻辑细细拆分 • 终于可以仔细拆分不同 状况,不需到处 mock •

    甚⾄至可以开始 stub ServiceObject
  43. 开始处理⽆无法 Mock 的 API • 要求「演算法」也抽出成 ServiceObject • 不测试演算法 •

    但 API 必须补「格式」Test
  44. ⼩小结

  45. • Step 1: 先估计「⼯工作量」 • Step 2: 装上红绿灯,⾄至少保证永远有基础绿灯。 • Step

    3: 切细变成可以「重构」的区块 • Step 4: 盒⼦子打包法,补⿊黑盒⼦子测试
  46. 挑战 4 :补「重构」哪部分的代码 ?

  47. CodeClimate Refactor 法 • 找出 F 等级代码。 • 专攻重复区域,打包成 method,补

    unit test。 • ⼜又补了⾄至少 > 50 个 Test • 重复的代码 = 重要的业务逻辑(到处都⽤用到)
  48. • ServiceObject (商业流程) • Calculator ( 计费可以随时抽换不同公式 ) • Validator

    ( ⼀一个 Order ⾄至少要经过 10 道验证,把验证做到可叠 加 ) • Serializer ( 原先是 Hash,改⽤用 Serializer 好管理 ) • Cells ( 让 View 可重复使⽤用 ) • StrategyClass ( 可策略抽换应付「营销活动」与「价格模型」 ) • Worker ( 把效能瓶颈移到背景去执⾏行 ) • Concern ( 复合使⽤用把常⻅见⼯工具类 method )
  49. ServiceObject • 查价 / 库存 • 下单 • 退单 •

    跟踪订单 • 计算运送所需时间 • 不同版本 API,使⽤用不同 ServiceObject
  50. None
  51. Calculator • 不同地区不同税率 • 冷的不收税、热的收税 • 司机⼩小费 • 礼物卡的税务逻辑

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

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

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

  55. ⼩小结

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

  57. 花了 1 个⽉月

  58. • 2 pull request => 5 => 10 • 终于可以上

    CI server • 不是绿灯,pull request 不会有⼈人理
  59. 讲完 API ,来讲 Controller

  60. • ⼀一个 Controller ⼏几千⾏行代码
 
 
 • ⽼老板把逻辑都写在 JavaScript ⾥里⾯面

    • 因为 Dispatcher 台是 JavaScript 写的 • 只好其他业务逻辑也⽤用同样的⽅方式
  61. 第⼀一步 • 把 Dispatcher 「以外」的部分搬去 Rails CRUD • 不太需要写 Test

    • 烂了请美国同事 hotfix,也不影响消费者下单 • 拆了 ~15 ⽀支 Controller
  62. 第⼆二步 • 把 Dispatcher 当作是 Client • 不再是 Rails 与

    JavaScript 混合产⽣生 View • 显⽰示逻辑都使⽤用 JavaScript • Rails 作为 API 提供资料 • 补 API Test
  63. • Frontend 组搞了很久 • 巨坑.... • 那时候剛有 React, … •

    学到以后复杂逻辑绝对別⽤用 jQuery 先搞。
  64. 经过这⼀一轮 • 1 个⽉月就从 1.4 => 2.95 • 代码覆盖率到 40%

    + • 花了另外的 5 个⽉月 2.95 => 3.06 • 花了另外的 5 个⽉月代码覆盖率到 60% • ⾜足够好
  65. 挑战 5 :Release 太快带来的问题

  66. • 每天 Release 15 ⽀支 pull request • db migration

    或是改资料,还是个坑,需要⼈人为介⼊入 • 运营团队抱怨虽然没有 500,但是业务逻辑⼀一直在变 • 应该要出 Release ⽇日报
  67. zendesk/samson

  68. ⼀一键 deploy

  69. None
  70. Deploy 架构 • samson 修改版 for heroku deployment • heroku

    pipeline
  71. • ⾃自动汇集 pull request 成 deploy ⽇日报 • 必须叙述作⽤用 •

    若有 migration 必须在 #Risk 环节注记 • Deploy ⾃自动执⾏行
  72. 成果 • 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.
  73. Take away • 先搞清楚有多少测试要写。 (否则会遭到公司否决) • 先写 Integration Test •

    Integration test -> Service Object test -> Unit test • 尽量提供 developer 「动机」去写 • 测试资料 • 可复制的测试代码
  74. Take away • 做 Refactor 前⼀一定要先上 Test • 前期⺫⽬目标是「有动机去做」 •

    中期⺫⽬目标是「接⼝口干净」 • 远期⺫⽬目标是「⾃自动部署」
  75. 不过

  76. • 虽然「补测试」这么不爽 • 之后创业我还是不会先写测试 • 因为我理解了 GPA 1.0 还是可以拿到 1100

    万美⾦金 • 商业的重点是「测试」你的 「idea」,⽽而不是「代码」 • 赚了钱你要雇⼏几个⼤大⽜牛来帮你重构都⾏行
  77. Q & A