重构 常见重构手法 (上)

qinfanpeng · December 21, 2016 · Last by emanon replied at January 20, 2017 · 13397 hits
Topic has been selected as the excellent topic by the admin.

经多处比较,发现还算 ThoughtWorks 和 金数据 的小伙伴比较喜欢追求优雅的代码。http://qinfanpeng.github.io/jekyll/update/2016/12/15/common_refactor_skills_part_one.html

[slide]

Agenda

  • Introduce Explaining Variable
  • Replace Temp with Query
  • Inline Temp
  • Extract Method
  • Inline Method
  • Substitue Algorithm
  • Split Temoary Variable
  • Remove Assignments to Parameters

[slide]

Agenda

  • Introduce Explaining Variable
  • Replace Temp with Query
  • Inline Temp
  • Extract Method
  • Inline Method
  • Substitue Algorithm
  • Split Temoary Variable
  • Remove Assignments to Parameters

[slide]

const calculatePrice = ({ itemPrice, quantity }) => {
  // price is base price - quantity discount + shipping
  return itemPrice * quantity -
      Math.max(0, quantity - 500) * itemPrice * 0.05 +
      Math.min(100, itemPrice * quantity * 0.1)
}

重构信号?

  • 注释 {:&.fadeIn}
  • 表达式逻辑杂糅于细节中,复杂难懂
  • 可读性不高
  • 重复

[slide]

Introduce Explaining Variable(引入解释变量)

const calculatePrice = ({ itemPrice, quantity }) => {
  // price is base price - quantity discount + shipping
  return itemPrice * quantity -
      Math.max(0, quantity - 500) * itemPrice * 0.05 +
      Math.min(100, itemPrice * quantity * 0.1)
}
const calculatePrice = ({ itemPrice, quantity }) => {
    const basePrice = itemPrice * quantity
    const quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05
    const shipping = Math.min(100, basePrice * 0.1)

    return basePrice - quantityDiscount + shipping
}

[note] Demo 好处?

  1. 把主逻辑从底层细节中剥离了,更可读
  2. 代码自身就充当了注释的作用,
  3. 消除了重复 [/note]

[slide]

const calculatePrice = ({ itemPrice, quantity }) => {
    const basePrice = itemPrice * quantity
    const quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05
    const shipping = Math.min(100, basePrice * 0.1)

    return basePrice - quantityDiscount + shipping
}

下一步?

  • 是否真的需要暴露底层的细节 {:&.fadeIn}
  • 是否考虑重用
  • 是否会滋生更长的函数
  • 变量是否值回票价

[slide]

Replace Temp with Query(用查询取代临时变量)

const calculatePrice = ({ itemPrice, quantity }) => {
    const basePrice = itemPrice * quantity
    const quantityDiscount = Math.max(0, quantity - 500) * itemPrice * 0.05
    const shipping = Math.min(100, basePrice * 0.1)

    return basePrice - quantityDiscount + shipping
}
const calculatePrice = (order) => {
    return basePrice(order) - quantityDiscount(order) + shipping(order)
}

const basePrice = ({ itemPrice, quantity }) => {
    return itemPrice * quantity
}

const quantityDiscount = ({ itemPrice, quantity }) => {
    return Math.max(0, quantity - 500) * itemPrice * 0.05
}

var shipping = function (order) {
    return Math.min(100, basePrice(order) * 0.1)
}

[note] 好处?

  • 代码即注释,一目了然
  • 更可重用
  • 抽象层级更协调 [/note]

[slide]

Performance concern of More Small Functions

  • 一般都不会有明显的性能差别 {:&.fadeIn}
  • 即使有细小的区别,代码可读性价值更高
  • 若真有大问题,回退并不麻烦
  • 总之,性能顾虑不能成为我们不抽方法的理由

[slide]

const isBigDeal = (order) => {
  const basePrice = order.basePrice
  return basePrice > 1000
}

变量是否值回票价?

  • 此处变量并没有带来更清晰的语义,反而显得冗余了 {:&.fadeIn}

[slide]

Inline Temp Variable

const isBigDeal = (order) => {
  const basePrice = order.basePrice
  return basePrice > 1000
}
const isBigDeal = (order) => {
  return order.basePrice > 1000
}

[slide]

let temp = 2 * height * width
console.log('perimeter: ', temp)

temp = height * width
console.log('area: ', temp)

重构信号?

  • temp 不表意,不可读 {:&.fadeIn}
  • 对同一个变量多次赋值(并且内容并无关联) ,职责不单一

[slide]

Split Temoary Variable(分解临时变量)

let temp = 2 * height * width
console.log('perimeter: ', temp)

temp = height * width
console.log('area: ', temp)
const perimeter = 2 * height * width
console.log('perimeter: ', perimeter)

const area = height * width
console.log('area: ', area)

[slide]

const users = [
  { user: 'barney', age: 36, active: true },
  { user: 'fred',   age: 40, active: false }
]

const activeUsers = []
users.each(user => {
  if (user.active) {
    activeUsers.push(user)
  }
})

遇到循环的时候,多斟酌一下

  • 少告诉计算机怎么做,而应告诉它做什么 {:&.fadeIn}
  • 不利于Parallelize
  • 尽量熟悉集合的 API,如filtermapreducefind

[slide]

Substitue Algorithm(替换算法)

const users = [
  { user: 'barney', age: 36, active: true },
  { user: 'fred',   age: 40, active: false }
]

const activeUsers = []
users.each(user => {
  if (user.active) {
    activeUsers.push(user)
  }
})
  • {:&.fadeIn}
const activeUsers = filter(users, user => user.active)
const activeUsers = filter(users, 'active')

[slide]

More Examples of Substitue Algorithm

const users = [
  { user: 'barney', age: 36, active: true },
  { user: 'fred',   age: 40, active: false }
]

[magic data-transition="cover-circle"]

let activeUser = null

users.each(user => {
  if (user.active) {
    activeUser = user
    break
 }
})

====

let totalAge = 0
users.each(user => {
  if (user.active) {
    totalAge += user.age
  }
})

[/magic]

[slide]

const discount = (inputVal, quantity) => {
  if (inputVal > 50) inputVal -= 2 
  // ...
  return inputVal  
}

重构信号?

  • 对参数赋值,降低代码清晰度 {:&.fadeIn}
  • 对参数赋值,参数为引用时,极易引入副作用
  • javascript const aMethod = (aObj) => { aObj.modifyInSomeWay() // That's ok aObj = anotherObj // Bad }

[slide]

Remove Assignments to Parameters(移除对参数的赋值)

const discount = (inputVal, quantity) => {
  if (inputVal > 50) inputVal -= 2 
  // ...
  return inputVal  
}
const discount = (basePrice, quantity) => {
  const finalPrice = basePrice
  if (basePrice > 50) finalPrice -= 2 
  // ...
  return finalPrice  
}

[slide]

Extract Method(提取方法)

const printOwing = (orders) => {
  let outstanding = 0.0
  // print banner
  console.log('*********************************')
  console.log('********* Customer Owes *********')
  console.log('*********************************')
  for (let i = 0; i < orders.length; i++) {
    outstanding += orders[i].amount
  }
  // print details
  console.log('name: ', name)
  console.log('amount: ', outstanding)
}

重构信号?

  • 职责不单一 {:&.fadeIn}
  • 注释
  • 抽象层次不协调,以致于主逻辑淹没其中

[slide]

Extract Method(提取方法)

const printOwing = (orders) => {
  printBanner()

  // print details
  console.log('name: ', name)
  console.log('amount: ', outstanding)
}
const printOwing = (orders) => {
  printBanner()
  const outstanding = calculateOutstanding(orders)
  printDetails(outstanding)
}

const printBanner = () => {
  console.log('*********************************')
  console.log('********* Customer Owes *********')
  console.log('*********************************')
}

var calculateOutstanding = function (orders) {
  return sumBy(orders, 'amount')
}

const printDetails = function (outstanding) {
  console.log('name: ', name)
  console.log('amount: ', outstanding)
}

[slide]

Inline Method(内联方法)

const getRating = () => {
    return moreThanFiveNegativeFeedbacks() ? 1 : 2
}

const moreThanFiveNegativeFeedbacks = () => {
    return this.negativeFeedbacks.length > 5
}

  • {:&.fadeIn}
const getRating = () => {
    return this.negativeFeedbacks.length > 5 ? 1 : 2
}

[slide]

Summary

refactory_flow

[slide]

参考资料

  1. 《重构》
  2. 《编写可读的艺术》
  3. 《代码整洁之道》

[slide]

Thank You & QA

感觉比起重构手法,价值观来得重要一些:什么样的代码是好的,什么样的代码是不好的。或者说有这些价值观是对代码进行重构的动力。请问大家是怎么对团队成员进行价值观传播的?对这个问题我一直比较苦恼。

#2 楼 @emanon 让他去改自己半年前写的代码,他就知道厉害了。

#3 楼 @qinfanpeng 这个我觉得不一定吧。改遗留代码好像更多时候仅仅只是让人觉得难改,但是对认识到自己平时开发中的各种坏习惯并没有帮助。而且多数程序员好像总会有各种办法在原来的基础上打补丁的。就更别说从中总结出好的写法了。情况好像往往是程序员一边难过地写代码,一边还认为自己的各种想法和做法始终是对的。你要说一个方法的参数超过 2 个很多时候是个 bad smell, 多数人只会笑话你,然后继续写出自以为正确的代码,然后继续痛苦。

像《重构》这样的书,很多人好像也就是看看而已,用他们的话说:“实际项目是很复杂的”。

#4 楼 @emanon 确实有你说的这类略固执的人,对于接受不了意见的人,确认不好弄。

在代码可读性还说得过去和对性能优化没有影响的的重构显然是没有多大意义的,切记不要一味的追求高大上的代码

要说服 PM 给自己一点重构的时间,会减少后面项目迭代的时间。

#6 楼 @easonlovewan 你去维护一个 10 年以上的项目,你就知道有没有意义了

#4 楼 @emanon 结对和 code diff,给他们看什么是好的代码以及这样写有什么道理,一般的人都能接受意见了。对于影响不了的人,怎么都影响不了,可以提出团队了

#7 楼 @adamshen 我的态度就是 PM 最好还是要懂技术好些,不懂技术就是瞎指挥啊

重构是一项技能,但是这个技能不一定随时都要用。比较认同上面一个哥的说法, "在代码可读性还说得过去和对性能优化没有影响的的重构显然是没有多大意义的,切记不要一味的追求高大上的代码" 如果写代码不能创造价值,那就是玩玩而已

lgn21st mark as excellent topic. 21 Dec 17:01

这是 (一),所以是不是还有 (二),(三),(四),(五),(六),(七),(八),(九),(十) 啊?

#13 楼 @lgn21st 没那么多,就两个。简单期间,故意把 OO 相关的东西给剔除去了。

不少《Refactoring》中有的,实践中用的还真不多,好好督促自己~

只有先解决有和无的问题才能谈重构,其实在写代码的时候执行良好的代码规范以及合理的代码审查比重构效果更好,即使需要重构,也会因为规范的执行而导致重构难度比较低.ps:这个观点不适合遗留项目,也不适合技术能力不是很强的团队。

#8 楼 @reverocean 看项目中别人写的代码都可以感觉到合理结构和注释的重要性。。。可惜,没时间重构别人的代码

支持 这个更应该在写代码的时候注意,而非放到后边重构,感觉前期能够抽象的就抽象;

#18 楼 @wanglizheng 非常赞同,其实 sense 到位了,前期就会写出质量高很多的代码。

tesla_lee in 这种代码有什么好的重构方法吗? mention this topic. 12 Jan 18:36

#4 楼 @emanon 😂 😂 😂 实际的项目不是复杂,而是做了两三个月,上线一个月,老板就喊停了。项目就黄了。代码写那么好干嘛?

#21 楼 @u1440247613 这样说,写代码还得先精通算命,写之前给这个项目算一卦。

其实我现在越来越倾向于认为,写代码的问题只能在招聘这一关解决。

知道吸烟喝酒对身体不好还要吸的大有人在。知道要早睡早起、坚持锻炼身体才能好但还总是熬夜、几乎不怎么运动的更是数不胜数。人们总是抱着侥幸的心理,却没有意识到不好的东西一直在以你很难注意到的程度积累,只是还没爆发。在遇到什么重大变故之前,期望他人改变根深蒂固的习惯和观念几乎只能得到绝望。而软件开发这份工作,似乎代码再怎么乱也不会引起多大变故,对那些早就习惯了在泥潭里打滚的人来,最坏的情况也就是丢掉一份工作罢了。

不停的提醒别人早点睡觉、坚持锻炼是很让人烦的,对别人的代码指指点点人家会觉得你吹毛求疵。对于非家人的身体健康,我们当然可以完全不管不顾,但现在开发软件总是需要“团队”来“合作”完成。

我无意与任何人争论,只想求一个解。

You need to Sign in before reply, if you don't have an account, please Sign up first.