当前位置: 首页 > Web前端 > JavaScript

JavaScript重构

时间:2023-03-27 15:28:42 JavaScript

1.基础重构1.提取函数1.1使用场景如果你需要花时间浏览一段代码来弄清楚它在做什么,那么你应该将它提取成一个函数并根据它的作用命名它.1.2使用steps新建一个函数,根据这个函数的用途命名,将源函数中要提取的代码复制到新建的目标函数中。仔细检查提取的代码以查找对作用域为源函数且在新提取的函数中不可访问的变量的引用。如果是这样,将它们作为参数传递给新函数。处理完所有变量后,进行编译。在源函数中,提取的代码段被替换为对目标函数的调用。测试。查看其他代码是否与提取的代码段相同或相似。如果是这样,请考虑将内联代码替换为调用提取的新函数的函数调用。1.3范例functionprintOwing(invoice){letoutstanding=0;//计算未付金额(constoofinvoice.orders){outstanding+=o.amount;}//记录截止日期consttoday=Clock.today;invoice.dueDate=newDate(today.getFullYear(),today.getMonth(),today.getDate()+30);console.log(`name:${invoice.customer}`);console.log(`金额:${outstanding}`);console.log(`due:${invoice.dueDate.toLocaleDateString()}`);}//重构造functionprintOwing(invoice){letoutstanding=calculateOutstanding(invoice)recordDueDate(invoice)printDetails(invoice,outstanding)}functioncalculateOutstanding(invoice){letoutstanding=0;for(constoofinvoice.orders){outstanding+=o.amount;}returnoutstanding;}functionrecordDueDate(invoice){consttoday=Clock.today;invoice.dueDate=newDate(today.getFullYear(),today.getMonth(),today.getDate()+30);}functionprintDetails(invoice,outstanding){console.log(`name:${invoice.customer}`);console.log(`金额:${outstanding}`);console.log(`due:${invoice.dueDate.toLocaleDateString()}`);}2。InlineFunction2.1使用场景如果代码中的间接层太多,以至于系统中的每个函数似乎都是对另一个函数的简单委托,让我在这些委托动作之间头晕目眩,那么我通常会使用Inlinefunctions2.2使用步骤检查函数以确保它不是多态的。找到这个函数的所有调用点。用函数体替换这个函数的所有调用点。每次更换后,进行测试。删除函数的定义。2.3示例函数reportLines(aCustomer){constlines=[];收集客户数据(线路,客户);返回行;}functiongatherCustomerData(out,aCustomer){out.push(["name",aCustomer.name]);出去。推送([“位置”,aCustomer.location]);}//重构函数reportLines(aCustomer){constlines=[];lines.push(["姓名",aCustomer.name]);lines.push(["location",aCustomer.location]);returnlines;}3.ExtractVariable3.1UsageScenario表达式可能非常复杂且难以阅读。3.2使用步骤确认待精化的表达式没有副作用。声明一个不可修改的变量,复制你要提取的表达式,并用表达式的结果值赋值给变量。用这个新变量替换原来的表达式。测试。3.3示例在一个函数中,将它们细化为变量functionprice(order){returnorder.quantity*order.itemPrice-Math.max(0,order.quantity-500)*order.itemPrice*0.05+Math.min(order.quantity*order.itemPrice*0.1,100);}//重构函数price(order){constbasePrice=order.quantity*order.itemPrice;constquantityDiscount=Math.max(0,order.quantity-500)*order.itemPrice*0.05;constshipping=Math.min(basePrice*0.1,100);returnbasePrice-quantityDiscount+shipping;}在一个类中,将它们提炼成方法classOrder{constructor(aRecord){this._data=aRecord;}getquantity(){returnthis._data.quantity;}getitemPrice(){returnthis._data.itemPrice;}getprice(){returnthis.quantity*this.itemPrice-Math.max(0,this.quantity-500)*this.itemPrice*0.05+Math.min(this.quantity*this.itemPrice*0.1,100);}}//重构类Order{constructor(aRecord){this._data=aRecord;}getquantity(){返回this._data.quan实体;}getitemPrice(){returnthis._data.itemPrice;}getprice(){returnthis.basePrice-this.quantityDiscount+this.shipping;}getbasePrice(){returnthis.quantity*this.itemPrice;getquantityDiscount(){returnMath.max(0,this.quantity-500)*this.itemPrice*0.05;}getshipping(){returnMath.min(this.basePrice*0.1,100);}}4。InsideInlineVariable4.1使用场景有时,变量并不比表达式本身更具表现力,这可能会阻碍附近代码的重构。如果是这种情况,则应通过内联消除该变量。4.2使用步骤检查变量赋值语句的右侧表达式是否有副作用。如果变量没有被声明为不可变的,先让它不可变,然后进行测试。找到变量的第一次使用,将其替换为直接使用赋值语句的右侧表达式。测试。重复前面两步,将其他所有使用该变量的地方一一替换。删除变量的声明点和赋值语句。测试。4.3例子letbasePrice=anOrder.basePrice;return(basePrice>1000);//重构returnanOrder.basePrice>1000;5.改变函数声明5.1使用场景如果我有一个函数,函数参数的名字你一眼就看不出它的用途。一旦找到它,您必须尽快重命名它。5.2使用步骤首先写一段注释描述这个函数的用途。然后把这个注释变成函数名。先完成函数重命名。测试。然后添加参数。测试。5.3示例函数inNewEngland(aCustomer){return["MA","CT","ME","VT","NH","RI"].includes(aCustomer.address.state);}constnewEnglanders=someCustomers.filter(c=>inNewEngland(c));//重构函数inNewEngland(stateCode){return["MA","CT","ME","VT","NH","RI"].includes(stateCode);}constnewEnglanders=someCustomers.filter(c=>inNewEngland(c.address.state));6.封装变量6.1使用场景对于所有可变数据,只要其作用域超出单个函数,就进行封装,只允许通过函数访问。6.2使用步骤创建包装函数,在其中访问和更新变量值。执行静态检查。一个一个地修改使用变量来调用适当的包装函数的代码。每次更换后,进行测试。限制变量的可见性。测试。如果变量的值是一个记录,可以考虑使用封装记录6.3例子7.变量重命名(RenameVariable)7.1使用场景变量为了能够很好的解释一个程序在做什么,对于范围超出函数调用的字段,您需要更改它们用心命名。7.2使用步骤如果变量被广泛使用,可以考虑用wrapper变量封装。找出所有使用这个变量的代码,一一修改。测试。7.3实例8.引入参数对象(IntroduceParameterObject)8.1使用场景变量为了能够很好地说明程序在做什么,对于超出函数调用范围的字段,需要仔细命名。8.2使用步骤如果您还没有合适的数据结构,请创建一个。测试。使用更改函数声明为原函数添加一个新参数,其类型为新创建的数据结构。测试。调整所有调用者以传入新数据结构的适当实例。每次进行修改时,都会执行测试。用新数据结构中的每一个元素逐一替换参数列表中对应的参数项,然后删除原来的参数。测试。8.3示例conststation={name:"ZB1",readings:[{temp:47,time:"2016-11-1009:10"},{temp:53,time:"2016-11-1009:20"},{temp:58,time:"2016-11-1009:30"},{temp:53,time:"2016-11-1009:40"},{temp:51,time:"2016-11-1009:50"},]};functionreadingsOutsideRange(station,min,max){returnstation.readings.filter(r=>r.tempmax);}常量警报=readingsOutsideRange(station,operatingPlan.temperatureFloor,operatingPlan.temperatureCeiling);//重构函数readingsOutsideRange(station,range){returnstation.readings.filter(r=>!range.contains(r.temp));}functioncontains(arg){return(arg>=this.min&&arg<=this.max);}constalerts=readingsOutsideRange(station,range);9.CombineFunctionsintoClass(将函数合并到类中)9.1使用场景如果一个Group函数不可分割地操作同一条数据(通常是将这条数据作为参数传递给函数),需要组成一个类.9.2使用步骤使用打包记录将多个函数共享的数据记录打包。对于每个使用记录结构的函数,使用移动函数将其移动到新类中。可以使用extract函数提取处理此数据记录的逻辑并将其移至新类中。9.3范例constreading={customer:"ivan",quantity:10,month:5,year:2017};constaReading=acquireReading();constbaseCharge=baseRate(aReading.month,aReading.year)*aReading.quantity;constbase=(baseRate(aReading.month,aReading.year)*aReading.quantity);consttaxableCharge=Math.max(0,base-taxThreshold(aReading.year));constbasicChargeAmount=calculateBaseCharge(aReading);functioncalculateBaseCharge(aReading){returnbaseRate(aReading.month,aReading.year)*aReading.quantity;}//重结构类Reading{constructor(data){this._customer=data.customer;this._quantity=data.quantity;this._month=data.month;this._year=data.year;}getcustomer(){returnthis._customer;}getquantity(){returnthis._quantity;}getmonth(){returnthis._month;}getyear(){returnthis._year;}getcalculateBaseCharge(){returnbaseRate(this.month,this.year)*this.quantity;}getbaseCharge(){returnbaseRate(this.month,this.year)*this.quantity;}gettaxableCharge(){returnMath.max(0,this.baseCharge-taxThreshold(this.year));}}10。CombineFunctionsintoTransform(将函数组合成变换)10.1使用场景如果源数据在代码中会更新,使用类会好很多;如果使用transform,导出的数据会存储在新生成的记录中,一旦修改源数据,就会遇到数据不一致的情况10.2使用步骤创建一个转换函数,入参为要转换的记录,以及直接返回记录的值。选择一段逻辑,将其主体移动到一个转换函数中,并将结果作为一个字段添加到输出记录中。修改客户端代码以使用这个新字段。测试。对其他相关的计算逻辑重复上述步骤。10.3例子constreading={customer:"ivan",quantity:10,month:5,year:2017};constaReading=acquireReading();constbaseCharge=baseRate(aReading.month,aReading.year)*aReading.quantity;constbase=(baseRate(aReading.month,aReading.year)*aReading.quantity);consttaxableCharge=Math.max(0,base-taxThreshold(aReading.year));constbasicChargeAmount=calculateBaseCharge(aReading);functioncalculateBaseCharge(aReading){returnbaseRate(aReading.month,aReading.year)*aReading.quantity;}//重构函数enrichReading(original){constresult=_.cloneDeep(original);result.baseCharge=calculateBaseCharge(result);结果.taxableCharge=Math.max(0,result.baseCharge-taxThreshold(result.year));返回结果;constrawReading=acquireReading();constaReading=enrichReading(rawReading);constbase=aReading.baseCharge;consttaxableCharge=aReading.taxableCharge;11.拆分阶段(SplitPhase)11.1场景一段代码同时处理两个不同的事情,因此需要拆分成独立的模块。也许你有一段处理逻辑,其输入数据格式不符合计算逻辑的要求,所以你不得不对输入数据做一些调整,使其更容易处理。也可能是你把数据处理逻辑分成了多个顺序执行的步骤,每个步骤负责一个完全不同的任务。11.2使用步骤将第二阶段的代码提炼成单独的函数。测试。引入一个传递数据结构并将其作为参数添加到提取的新函数的参数列表中。测试。对提取出来的“二级函数”的各个参数逐一检查。如果一个参数被第一阶段使用,它被移动到中间数据结构中。每次移动后都会进行测试。在第一阶段的代码中应用抽取函数,让抽取函数返回中转数据结构。11.3范例functionpriceOrder(product,quantity,shippingMethod){constbasePrice=product.basePrice*quantity;constdiscount=Math.max(quantity-product.discountThreshold,0)*product.basePrice*product.discountRate;constshippingPerCase=(basePrice>shippingMethod.discountThreshold)?shippingMethod.discountedFee:shippingMethod.feePerCase;constshippingCost=数量*shippingPerCase;constprice=basePrice-discount+shippingCost;returnprice;}//重组函数priceOrder(product,quantity,shippingMethod){constpriceData=calculatePricingData(product,quantity);returnapplyShipping(priceData,shippingMethod);}functioncalculatePricingData(product,quantity){constbasePrice=product.basePrice*quantity;constdiscount=Math.max(quantity-product.discountThreshold,0)*product.basePrice*product.discountRate;返回{basePrice:basePrice,quantity:数量,discount:dis数数}};functionapplyShipping(priceData,shippingMethod){constshippingPerCase=(priceData.basePrice>shippingMethod.discountThreshold)?shippingMethod.discountedFee:shippingMethod.feePerCase;constshippingCost=priceData.quantity*shippingPerCase;返回priceData.basePrice-priceData.discount+shippingCost;}