第一章:重构,第一个示例
如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于进行更改,那就先重构那个程序,使其比较容易添加该特性,然后再添加该特性。
- 构建可靠的测试手段,来检测可能的 bug,提高后续程序的稳健性
-
第三章:代码的坏味道
神秘命名(Mysterious Name)
- 重复代码(Duplicated Code)
- 过长函数(Long Function)
- 以查询取代临时变量
- 引入参数对象、保持对象完整
- 条件判断或循环过于复杂则提炼到一个独立的函数中
- 全局数据(Global Data)
- 将变量封装起来,使其处于可控状态
- 发散式变化(Divergent Change)
- 当你修改一个东西的时候多个模块时,应该考虑将其上下文封装到各自的函数中,降低耦合度
- 过长的消息链(Message Chains)
第六章:第一组重构
提炼函数(Extract function)
将意图与实现分开
即要能清晰的知道这个函数的作用、功能点是啥,通常通过函数命名来获取需要知道的信息
封装变量(Encapsulate Variable)
当数据在多处使用(使用域较大)时,以函数形式封装所有对数据的访问,方便监控数据的使用和变化
引入参数对象(Introduce Parameter Object)
就是出现众多参数时,但这些参数带有某个意义上的共通性时,可以考虑使用对象来组合它们
函数组合成类(Combine Function into Class)
function base(aReading){...}
function taxableCharge(aReading){...}
class Reading {
base(){...}
taxableCharge(){...}
}
类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用,并且这样一个对象也可以更方便地传递给系统的其他部分。
class Reading {
constructor(data) {
this._customer = data.customer;
this._quantity = data.quantity;
}
get customer() {return this._customer;}
get quantity() {return this._quantity;}
}
函数组合成变换 (Combine Functions into Transform)
在软件中,经常将数据传递给一组程序,让它再计算出各种派生信息。这些派生数据可能在几个不同的地方用到,因此这些计算逻辑也常会在需要用到派生数据的地方重复。这时候,我们可以把所有计算派生数据的逻辑收拢到一处,这样始终可以在固定地方找到和更新这些逻辑,避免到处重复。
函数组合成变换接受源数据作为输入,计算出所有的派生数据,将派生数据以字段的形式填入输出数据。如果需要更改计算逻辑,只需要在这个变换函数更改。
function base(aReading) {...}
function taxableCharge(aReading) {...}
function enrichReading(argReading) {
const aReading = _.cloneDeep(argReading);
aReading.baseCharge = base(aReading);
aReading.taxableCharge = taxableCharge(aReading);
return aReading;
}
拆分阶段(Split Phase)
如果一块代码在同时处理两件或两件以上不同的事(各自使用不同的一组数据和函数),这时可以把它拆分成各自独立的模块。这样在修改的时候,可以单独处理每个模块,而不必同时考虑其他模块,免去回忆其他模块细节的烦恼。
第七章:封装
类是为了隐藏信息而生的。
封装记录(Encapsulate Record)
organization = {name: "Acme Gooseberries", country: "GB"};
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() {return this._name;}
set name(arg) {this._name = arg;}
get country() {return this._country;}
set country(arg) {this._country = arg;}
}
封装集合(Encapsulate Collection)
使用面向对象的开发者常犯这样的一个错误:在封装集合时,只对集合变量的访问进行封装,但依然让取值函数返回集合本身,这使得集合的成员变量可以直接被修改,而封装它的类则全然不知。为避免这种情况,通常在在类上提供一些修改集合的办法 —— 通常是”add ()” 和”remove ()”。返回的是数据的副本。
class Person {
get courses() {return this._courses;}
set courses(aList) {this._courses = aList;}
class Person {
get courses() {return this._courses.slice();}
addCourse(aCourse) { ... }
removeCourse(aCourse) { ... }
以对象取代基本类型(replace primitive with object)
单纯的基本类型并不能完整表达数据的意图和可能的操作,通过对象达到:更内聚的单元,更可复用的模块
以查询取代临时变量(Replace Temp with Query)
将只被赋值一次的临时变量抽取到一个独立的函数中,利于后续维护复用
提炼类(Extract Class)
将一些相关成员变量移植到新的 class 中
内联类(Inline Class)
和提炼类相反,取消多余的类
隐藏委托关系(Hide Delegate)
“封装”意味着每个模块都应该尽可能少了解系统的其他部分,即充分解耦
const manager = person.department.manager;
const manager = person.manager;
class Person{
get manager(){
return this.department.manager;
}
}
移除中间人(Remove Middle Man)刚好与之相反,一切都是平衡的艺术呀。
替换算法(Substitute Algorithm)
就是发现了更好用的算法。。。这过于简单粗暴了,但这种算法的替换,如果是建立在已经将其封装在一个函数中时,对外是无感知的,这样子才是便于维护的
function foundPerson(people){
for(let i = 0; i < people.length; i++){
if(people[i] === 'John'){
return 'John';
}
if(people[i] === 'Maria'){
return 'Maria';
}
if(people[i] === 'Mike'){
return 'Mike';
}
}
return '';
}
function foundPerson(people){
const candidate = ['John', 'Maria', 'Mike'];
return people.find(p => candidate.includes(p)) || '';
}
第八章:搬移特性
搬移语句到调用者(Move Statements to Callers)
主要用在函数边界发生偏移的情况,把剩下的共用点抽离到新的封装函数中,而差异的边界点重新分散到使用处去。
拆分循环(Split Loop)
一个循环只做一件事件,解耦,利于维护
let youngest = people[0] ? people[0].age : Infinity;
let totalSalary = 0;
for (const p of people){
if (p.age < youngest) youngest = p.age;
totalSalary += p.salary;
}
return `youngestAge: ${youngestAge}, totalSalary: ${totalSalary}`;
function totalSalary() {
let totalSalary = 0;
for (const p of people){
totalSalary += p.salary;
}
return totalSalary;
}
function youngestAge() {
let youngest = people[0] ? people[0].age : Infinity;
for (const p of people){
if (p.age < youngest) youngest = p.age;
}
return youngest;
}
return `youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`;
跟进一步,使用管道取代循环(replace loop with pipeline)
function totalSalary() {
return people.reduce((total, p) => total + p.salary, 0);
}
function youngestAge() {
return Math.min(...people.map(p => p.age));
}
第九章:重新组织数据
拆分变量(Split Variable)
如果变量承担多个职责时,就应该考虑拆成多个变量,每个变量只承担一个责任
public int createUniqueCasenumber(GregorianCalendar date, int departmentID) {
int temp = date.get(GregorianCalendar.DAY_OF_MONTH) * 100 * 100 * 100;
temp += date.get(GregorianCalendar.MONTH) * 100 * 100;
temp += date.get(GregorianCalendar.YEAR) * 100;
temp += date.get(GregorianCalendar.HOUR_OF_DAY) * departmentID;
return temp;
}
public int createUniqueCasenumber(GregorianCalendar date, int departmentID) {
int magnifiedDay = date.get(GregorianCalendar.DAY_OF_MONTH) * 100 * 100 * 100;
int magnifiedMonth = date.get(GregorianCalendar.MONTH) * 100 * 100;
int magnifiedYear = date.get(GregorianCalendar.YEAR) * 100;
int magnifiedHour = date.get(GregorianCalendar.HOUR_OF_DAY) * departmentID;
return magnifiedDay + magnifiedMonth + magnifiedYear + magnifiedHour;
}
以查询取代派生变量(replace derived variable with query)
尽量把可变数据的作用域限制在最小范围
Remove any variables that you could just as easily calculate. A calculation often makes it clearer what the meaning of the data is, and it is protected from being corrupted when you fail to update the variable as the source data changes.
class ProductionPlan {
get production() {
return this._production;
}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
this._production += anAdjustment.amount;
}
}
class ProductionPlan {
get production() {
return this._adjustments
.reduce((sum, a) => sum + a.amount, 0);
}
applyAdjustment(anAdjustment) {
this._adjustments.push(anAdjustment);
}
}
第十章:简化条件逻辑
分解条件表达式(Decompose Conditional)
所有过于复杂的代码语句,都可以考虑抽离成单独的函数,来提高相应的条件逻辑、可读性
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) {
charge = quantity * plan.summerRate;
} else {
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
charge = summer() ? summerCharge() : regularCharge();
function summer() {
return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}
function summerCharge() {
return quantity * plan.summerRate;
}
function regularCharge() {
return quantity * plan.regularRate + plan.regularServiceCharge;
}
以卫语句取代嵌套条件表达式(replace nested conditional with guard clauses)
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
else
result = normalPayAmount();
}
}
return result;
}
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
以多态取代条件表达式(replace conditional with polymorphism)
对于 js 而言的话,大多是使用工厂模式来实现相应的效果
第十一章:重构 API
将查询函数和修改函数分离(separate query from modifier)
无他,就是为了单一原则,功能越单一越好维护
以查询取代参数(replace parameter with query)
函数的参数列表应该总结该函数的可变性,标示出函数可能体现出行为差异的主要方式。和任何代码中的语句一样,参数列表应该尽量避免重复,并且参数列表越短就越容易理解。