代码中,一个大的逻辑块,一定会由很多个函数和变量组成起来的,
函数的存在可以降低人们对业务的理解,把一个大的逻辑,由一个一个的函数组成起来,如果函数写的足够好的话,去梳理逻辑时就会很清晰。函数中也会有很多变量的存在,变量的存在让人们更好的理解函数的意义。
下面看一看从重构的角度,对函数和变量的一些重构方法。
1、提炼函数
提炼函数比较简单,从我对书中作者表达的意思来看,提炼函数就是把啰嗦的代码,按照功能提炼到一个函数里,赋予它意义。
提炼前:
// 打印信用账单
function printCredit(invoice) {
console.log("********************************")
console.log("******** Customer Credit *******")
console.log("********************************")
let totalAmount = 0
for (const order of invoice.orders) {
totalAmount += order.amount
}
// print detail
console.log('name:' + invoice.name)
console.log('amount:' + totalAmount)
console.log('time:' + time.now())
}
提炼后:
// 打印信用账单
function printCredit(invoice) {
printBanner()
let totalAmount = 0
for (const order of invoice.orders) {
totalAmount += order.amount
}
printDetail()
// 提炼成一个
function printBanner() {
console.log("********************************")
console.log("******** Customer Credit *******")
console.log("********************************")
}
// 提炼成一个打印账单详情的函数
function printDetail() {
console.log('name:' + invoice.name)
console.log('amount:' + totalAmount)
console.log('time:' + time.now())
}
}
说明:提炼函数,根据我的理解,就是把比较长的内容里,按照功能和定义,对逻辑进行提炼。由于javascript的特性可以把函数写在函数内部,可以为当前函数调用,java没有这样的特性,需要把函数提炼在同级函数,然后调用。
2、内联函数
内联前:
// 是否需要留资
private function isNeedPublishflow() {
return !$this->appidIsDouniu();
}
// 是否是豆牛app请求
private function appidIsDouniu() {
return in_array(intval($this->app_id), [20, 110, 210]);
}
内联后:
private function isNeedPublishflow() {
return !in_array(intval($this->app_id), [20, 110, 210]);
}
这个是选自UC中的一处我曾经写的代码,为什么要内联,书中是这么表达的大意是:有的代码已经足够简单并且一目了然了,就没有必要再去强行提炼,这种强行提炼看上去是通过对函数的命名仿佛更加已读,但实际上更有可能是反而增加了阅读的难度,因为本来一行就可以看明白,强行提炼以后还要跳出去去看被提炼出来的函数。
3、提炼变量
提炼前:
// 价格等于 (1)订单数量*价格 - (2)超过500件以上的部分给5%的折扣 + (3)总价格1%的运费(运费最高不超过100元)
function getPrice(order) {
return order.num * order.price - Math.max(0, order.num - 500) * order.price * 5% + Math.min(order.num * order.price * 1%, 100)
}
如果有这样一串计算价格的代码存在电脑里的时候,没事的时候在那放着并不起眼,当计算如果是有一些小问题的时候,再有人想精心梳理一下这个逻辑的时候,应该会想着原地去世。
提炼后:
function getPrice(order) {
// 变量提炼,
// 把基础价格提炼出来
const basePrice = order.num * order.price
// 把折扣费用提炼出来
const discountPrice = Math.max(0, order.num - 500) * order.price * 5%
// 再把运费提炼出来
const shippingPrice = Math.min(basePrice * 1%, 100)
return basePrice - discountPrice + shippingPrice
}
这个案例表达了,当需要很费力才能读懂变量相作用在一起的结果的时候,就需要考虑提炼变量了,提炼的标准就是让人理解起来更简单。
4、内联变量
内联前:
function getPrice(order){
const totalPrice = order.num * order.price
return totalPrice
}
内联后:
function getPrice(order){
return order.num * order.price
}
内联变量和提炼变量是相反的,内联变量和内联函数一样,它们都认为已经挺简单的了,不需要再额外多余的提炼了,这多余的提炼反而让代码变得不简洁、不优雅。
5、封装变量
封装前:
// 假如这是一个全局变量
let defaultOwner = {firstName: "Jack", lastName: "Ma"}
// A处代码
defaultOwner = {firstName: "Jack2", lastName: "Ma2"}
// B处代码
defaultOwner = {firstName: "Jack3", lastName: "Ma3"}
封装后:
function getDefaultOwner {
return defaultOwner
}
// 所有的修改统一都要走这里开赋值变量
function setDefaultOwer(owner) {
defaultOwner = owner
}
这里要表达的意思是,可能会有比如一个全局变量,或者一个redis的值,一个项目里到处都是对这个变量的赋值,导致说不清在哪里这个值就被改了,缺少一个控制,把度和读和取放到一个地方。
6、变量改名
改名前:
let a = height * width
改名后:
// 平方米
let squareMetre = height * width
这个是重构的一个方法,把那些命名不好、不规范的命名改名,改成一个有语义的命名,并且用途在上下文中很清晰的状态。
7、引入参数对象
引入前:
function getHistoryChatList(startTime, endTime, customerId, ........) {
// 业务代码
}
引入后:
function getHistoryChatList(condition) {
// 业务代码
// condition.getStartTime()
}
let condition = new Condition(startTime, endTime);
getHistoryChatList(condition)
代码中经常会遇到这种情况,一个函数已经写了十几个参数了,后来随着功能的改变又要往里加参数,越加越多,越加越长,非常不利于代码复用。所以,有必要为这样的长参数的函数,做一下把参数替换成对象,或者map,这样复用更方便、编写更简洁更容易阅读。
8、拆分
拆分,当我看完这一章节之后,我没有理解,这和提炼到底有啥区别,因为在我看来,拆分就是在提炼函数。然后看着看着,再看着看着,我理解了,提炼的意思是把一些逻辑,提炼成一个具有语义的函数,如果在这个被提炼的函数中发现,这个函数存在两个方向,那么就可以继续往更细的拆分成小函数,直到不能拆分为止。如果拆的过细了,就还要再内联回来。所以提炼、拆分、内联是相辅相成的,需要把握他们之间的度,这并没有一个特别标准的标准,代码也是一门艺术,每个人都有每个人自己的理解,把自己的理解在代码中优雅的表达出来,就是代码的艺术。