重构:干掉有坏味道的代码

第一次读重构 - 改善既有代码的设计(Refactoring: Improving the Design of Existing Code)这本书仍是在学校的时候,那个时候刚开始写Java代码,师兄推荐了两本书《重构》、《设计模式》。在今日看来,这两本书都是经典好书,得谢谢个人师兄。html

最近,打算从新读一下这两本书,先读了重构,感受仍是收获颇多。想来这两本书都是比较偏向实践的,并非读一遍就能够束之高阁了,而是应该常读常新。java

本文地址:http://www.javashuo.com/article/p-tptsqlsz-dp.htmlpython



去年重读了代码整洁之道这本书,也写了一篇笔记 什么是整洁的代码。今年重读《重构》的时候,发现书中不少内容都是相同的,做者好像都是叫 Martin 什么的,我还想难道是同一我的?程序员

查了一下,并非,重构的做者是 Martin Fowler;而clean code的做者是 Robert C. Martin ,江湖人称 "Uncle Bob"。数据库

不过好像两位都在面向对象、敏捷领域有所建树。By the way,重构的初版写于1999年,(本文也基于初版的译文),而clean code的初版写于2009年,且clean code是有参考 “refactoring: Improving the Design of Existing Code”的。express

在我看来,重构这本书的核心价值有三部分:编程

  • 指出有“坏味道”的代码
  • 对这种代码给出重构的详细步骤,这些步骤保证重构过程是安全的
  • 关于引入新技术、新思想的一些思考,如重构、代码复用、TDD

固然,第二部 -- 针对各类有问题的代码的重构步骤 -- 是本书的重点,不过如今的IDE都提供了对重构的支持,大大提高了重构的效率和安全性。设计模式

认清重构的事实

书名叫 Refactoring: Improving the Design of Existing Code ,做者也着重强调:重构是在不改变软件可观察行为的前提下改善其内部结构。也就是说,对外的API,以及API的行为不该该被改变,不要在重构的同时修bug,或者添加新功能。数组

重构是为了改善程序的内部结构,而改善的目的在于增长代码的可读性,让代码更容易维护和修改。安全

咱们也经常为了提高性能而修改代码,不幸的是,为了性能而实施的修改一般让代码变得难以维护,标志就是得加注释说明为何要这么修改。

重构的前提

无论怎么样,手动仍是借助工具,重构仍是会修改代码,只要修改代码,就可能引入错误。那么重构给出了就是一套通过验证的、有条不紊整理代码的方法,经过逐步改进、及时测试、出错则回滚的方法来最小化引入bug的几率。

上面提到了逐步验证,这就须要在重构的时候须要有可靠的、自动的测试环境,若是没有靠谱的测试方案,那么最好仍是不要重构。

何时重构

程序员新学得一个技能, 好比重构,就很容易认为这是解决编程问题的屠龙技,火烧眉毛想找个环境用起来,但只有在合适的时机使用才能发挥其效用。

  • 增长新的功能前
  • 修改bug前
  • code review时

成功的软件都须要长时间的维护、迭代,那么咱们程序员不免就会接受其余程序员的遗产:代码以及bug。若是须要在旧代码上加新功能,但旧代码的混乱程度又让人无从下手,该怎么办呢?

  • 重写:既然以前的代码很SB,那我就从新写点NB的代码。但现实很残酷,重写的时间、人力成本是多少?引入的新BUG怎么算?何况,若是贸然动手,新造的轮子还可能不如原来的轮子。
  • 复制、修改:看看系统中有没有相似的功能模块,复制过来,改一改,若是刚好能工做,那就万事大吉。但咱们知道,重复的代码是很差的,bug、“有坏味道”的代码也被复制和传播。
  • 重构:处于重写与复制的中间状态,在不修改程序的外在表现的状况下,改善代码的质量,也让新功能的添加更加容易。这也符合clean code中提到的童子军军规:

让代码愈来愈好,而不是愈来愈坏

重构与设计

无论是瀑布流模型开发,仍是敏捷开发,都是须要有设计的。过分设计和不作设计都是有问题的,而重构简化了设计:无需过分追求灵活些,合理便可。所谓灵活些,便可应对各类需求变化,但灵活的系统比简单的系统复杂得多,且难以维护。

重构使得修改面向对象的程序设计变的很容易,由于能够重构继承体系,将field、method移动到不一样的类中,经过多态移除各类复杂的条件判断。某种程度上,重构能够简化详细设计,但不能替代架构设计,或者说概要设计。

值得注意的是:

  • 本书的重构手法只适合单进程单线程程序,而不必定适合多线程、分布式。对于多线程,一个简单的inline就可能致使各类问题。而对于分布式系统的重构,更多的是架构层面的设计。
  • 越难重构的地方,越须要精心设计,好比数据库字段,通讯协议,对外接口。保持对旧协议的兼容是一件很是麻烦的事情。

有“坏味道”的代码

须要重构的代码每每都散发着“坏味道”,让专业的程序员感觉到不舒服。这一部分,罗列了做者总结的“坏味道”。

须要注意的是,本书罗列的坏味道不必定很全面,好比一个变量命名为temp,大几率就是一个坏味道,但本书中就未说起这种状况。所以,很是建议配合clean code一块儿阅读

另外,我的以为本节还有一个严重问题:那就是缺少例子。“坏味道”是咱们为何要重构,然后面的具体手法是如何重构,why 比 how 更重要些,因此我的感受应该在描述"坏味道"的时候给出代码示例。

重复的代码 -- duplicated code

最简单的状况,就是两段代码有相同的表达式语句,处理方法也很明确,那就是extract method,而后应用这个新的方法。另一种常见状况,就是这两段相同的代码位于不一样的子类 -- 每每是新增子类的时候部分复制了其余子类的代码,这个时候就应该使用pull up method将公共代码抽取到基类去。

固然,两段代码也多是类似但不彻底相同,那么能够考虑将差别部分子类化,即便用form template method。或者将差别部分参数化,即经过参数控制不一样的逻辑,但须要注意的是,参数会不会致使两种大相径庭的行为,即parameterize methodreplace parameter with explicit methods的区别。

最后,也常常发现两个类之间有相同的重复代码,可是两者之间并无继承关系(并非is-a关系),那么能够extract class将公共部分提取出来,以组合的方式使用,或者使用多继承--Mixin 继承其实现。

过长的函数 -- long method

过长的函数每每冗杂着过多的细节,在什么是整洁的代码一文就曾经中, 代码的组织应该像金字塔同样,“每一个函数一个抽象层次,函数中的语句都要在同一个抽象层级,不一样的抽象层级不能放在一块儿”。

对于过长的函数,负责任的代码做者每每会给出一些注释:解释某一小段代码的做用,这其实就暗示着咱们能够把这段代码移到一个独立的函数,而后取一个恰当的名字来展示其意图。这个新函数的名字应该体现作什么,而不是怎么作,这样,新函数的名字就能够取代原来的注释。

若是新抽取出来的子函数须要用到原函数中的参数或者临时变量,那么这些都须要参数化到子函数,这可能致使子函数参数列表过长的问题,这个问题及其解决办法在后面阐述。

除了注释,还有什么“味道”暗示应该提取子函数呢,好比 if then else中有大段的代码,这个时候可使用Decompose conditional处理条件表达式。

过大类 -- large class

单个类有太多的实例属性,并且其中某些属性常常独立于其余属性一块儿使用,那么可使用extract class

好比一个课程信息类 Course,里面包含了 CourseId、CourseName、TeacherId、TeacherName、TeacherSex 等属性,那么坏味道就是:不少属性名拥有相同的前缀。所以能够经过extrace classCTeacherId、TeacherName、TeacherSex 抽取到新的类 Teacher。而后就能够去掉这些属性名的前缀,同时Course类持有 Teacher便可。

或者一些属性只在某些特殊状态下使用,那么能够考虑extrace subclass

过长参数列表 -- long parameter list

过长的参数列表让代码变得难以阅读和理解,要搞清楚每一个参数的意义就须要大费周折。

若是某个参数能够从函数内可访问的对象(类属性或者其余参数)得到,那么这个参数就是冗余的,就能够 replace parameter with method

另外,传递的若干个参数可能只是某个对象的一堆属性,那么就能够考虑直接传递该对象 preserve whole object,不过须要注意,preserve whole object可能会致使非预期的依赖关系,这在静态类型语言(如C++)中又是一个复杂问题。

发散式变化 -- Divergent change

某个类因为不一样的缘由要在不一样的地方进行修改,事实上,这违背了类的单一职责原则(SRP),一般也是过大类。解决的办法就是拆分红不一样的类(子类)。extract class or extract subclass

散弹式修改 -- shotgun surgery

Divergent change 刚好相反,为了须要响应一个变化而修改大量的类

依恋情结 -- feature envy

函数对某个类的兴趣高于本身所在的类。如大量使用其它类的数据,常见的是取出其余对象的属性,而后一通计算后再赋值。解决办法,将老是一起变化的东西放在一块儿:数据与对数据的操做。

数据泥团 -- Data clumps

若是某些数据常常一块儿变化,那么应该将这些数据提取到某个类中,正如以前过大类中的例子。提取出单独的类,减小了属性和参数的个数,并且接下来就能够找出 feature envy,进一步重构。

基本型别偏执 -- primitive obsession

相似于上一条“数据泥团”,不过更强调基本数据的封装

使用基本类型,好比用两个字段 begin, end 来表示区域[begin, end),仅从可读性上来讲确定不如封装成一个类 range

switch

switch 的问题在于重复,这里须要switch case,那么极可能其余地方也要switch case。若是增长一种case,那就获得处修改,违背OCP原则。

使用多态是经常使用的解决办法,replace condition with polymorphrsim,过程是这样子的:

  1. extract_method
  2. move method
  3. replace type code with subclass(strategy、state)
  4. replace condition with polymorphrsim

平行继承体系 -- parallel inheritance hiearachies

这是shotgun surgery的一种特化,某各种增长了一个子类致使另一个类也必须增长一个子类,虽然设计模式中可能出现这样的状况,但坏味道能够帮助咱们加以区分:某个继承体系的类名前缀和另外一个继承体系的类名前缀彻底相同

冗余类 -- Lazy class

没有什么价值的类。类中不在有什么实质性工做,多是由于逻辑变化,多是由于重构,这个时候可用经过collapse hierarchy 或者 inline class去掉这样的类。

夸夸其谈将来 -- speculative generality

过分的设计、抽象、泛化,各式各样的钩子和特殊状况处理,越灵活越复杂,越是难以维护。坏味道:函数或类的惟一用户是测试用例

使人迷惑的暂时字段 -- Temporary Field

某个成员变量只是在某些特殊状况才会用到,不用到的时候会致使迷惑,或者某个成员变量的赋值只是为了后续方便某个成员方法的调用,根据不一样的状况能够参考一下重构手法:

  • extract class将这些特殊的field移到新的类
  • 使用 null object避免写出条件分支
  • 函数调用时传入这些特殊变量

过分耦合的消息链 -- message chain

对某一个对象不停索求另外一个对象,坏味道就是 A.getB().getC().dosth(),这就是 clean code 中提到的火车失事,违背了德墨忒尔律(The Law of Demeter):模块不该了解他所操做的对象的内部状况

解决的办法是Hide delegate, 但这样的重构又可能致使下一个问题:middle man

中间人 -- middle man

过度使用委托,若是一个类的多半接口都是委托给其余类,那么能够考虑remove middle man。这有点相似软件架构模式中提到的污水池反模式(architecture sinkhole anti pattern)

若是middle man也有一些职责,能够考虑 replace delagate with inheritance 让其变成最终对象的子类。

狎昵关系 -- inappropriate intimacy

两个class过于亲密,使用彼此的private。抽取出新的类,或者move filed

不完美的类库 -- incomplete library class

类库是代码复用的绝佳体现,可是类库的做者不可能预料到全部的需求,所以怎么在不改源码的基础上完成想要的工做:

  • introduce foreign method
  • introduce local extension

被拒绝的馈赠 -- Refused Bequest

坏味道:子类复用了基类的行为(实现),但却不想支持基类的接口,这违背了LSP原则:子类型必须可以替换它们的基类型。

C++中public继承的其实就是接口,而private继承的则是实现,经过private继承,基类中的全部方法都变成private。更通用的重构手法: replace inheritance with delagate

过多的注释 -- comments

注释是好东西,散发着香味,但你不该该用它来掩盖臭味

使用extract method或者rename method来解释注释的行为。对于参数的命令也应该能望文知义

具体的重构手法

找到坏味道以后,就是如何安全的进行重构,书中罗列了各类重构手法的具体的实施步骤,按照这种逐步推动、逐步测试的方法,保证重构没有影响到代码的外在表现。固然,IDE提供的重构工具让部分重构变得更加容易和安全。

从新组织函数

函数老是过长,尤为是在漫长的维护过程当中,函数内的代码数量会逐渐膨胀。

Extract method

须要注意:

  • 保证函数名称与函数本体之间的语义距离 -- 一个好的函数名
  • 对于局部变量和参数的处理:参数

Inline Method

难点:

  • 是不是多态
  • 得找出全部引用点

Inline temp

临时变量只是被一个简单表达式赋值一次。有助于后续的Extract method,也能够做为replace temp with query的一部分使用。

注意:

  • 若是表达式较为复杂不该内联,影响可读性与效率
  • 屡次赋值的话也不能内联

replace temp with query

将一个表达式提取为一个单独的函数,新函数能够被其它函数调用。之中有一段实例代码,用python改写以下:

def calc_price(self):
	base_price = self._quality * self._item_price
	if base_price > 1000:
		return base_price * 0.95
	else:
		return base_price * 0.98

重构后是这样的

def calc_price(self):
	if self.base_price() > 1000:
		return self.base_price() * 0.95
	else:
		return self.base_price() * 0.98

def base_price(self):
	return self._quality * self._item_price

我的以为这个例子并非很恰当

  • 在没有改善可读性的状况下,引入了重复调用带来的开销
  • 有时也会有问题,原始的代码base_price一旦计算后是不会发生变化的,都提取成query以后就不能保证了

我的认为,即便为了解决temp只在函数内部生效而没法复用的问题,也应该改为:

def base_price(self):
	return self._quality * self._item_price

def calc_price(self):
	base_price = self.base_price()
	if base_price > 1000:
		return base_price * 0.95
	else:
		return base_price * 0.98

对于python,query还能够实现为property的形式,若是肯定query的结果是固定的,还可使用cached_porperty优化。

introduce explaining variable

将复杂表达式变成一个解释性的局部变量,解决可读性问题

split temporary variable

一段代码中,一个临时变量只能表明一个意思,不然应使用不一样的临时变量。

remove assignment to parameter

移除对参数的赋值,防止误改、不当心的覆盖,可读性更好

  • 不要对参数进行赋值,以一个临时变量取代参数的位置
  • java只采用pass by value传递方式。对于基本类型,同C++同样;对于引用类型,能够改变参数内部的状态(调用者实参的内部状态随之改变),但对参数从新赋值没有任何意义。
  • 能够给参数强制加上final修饰符,保证参数不被赋值

在对象之间搬移特性

move method

迁移的过程当中可能须要用到source class的特性(成员变量或者成员方法)。处理方式:

  1. 将这个特性移到target class中;
  2. 在target class中创建一个对source class的引用;
  3. 将source object做为一个参数传递给target method(eclipse中的move就是该方法);
  4. 将特性做为参数传递给target method

movie field

经常是extract class的一部分,先移动field,在移动method

extract class

先 move field,再move 必要的 method

须要考虑的是,新的类要不要对外公布

inline class

Hide delegate

eg:

value = AObject.getBObject().getVlaue()

public int AObject::getValue(){ return bObject.getgetVlaue()}
value = AObject.getVlaue()

并且应该考虑要不要干掉 AObject.getBObject

remove middle man

Hide delegate相反,若是一个server全是各类简单委托

introduce foreign method

须要调用的类缺乏一个你须要的方法

良好的建议在于:这个方法应该属于服务类,所以只需将类的对象做为第一个参数就行,(其余参数应该是服务类 “新方法”的参数)

introduce local extension

  • 已有且不能修改的类没法完成需求
  • 使用继承或者组合解决

从新组织数据

self encapsulate field

对属性的访问经过getter和setter实现

适用状况:

  • 可能对属性访问作控制
  • 可能会有subclass,且subclass的getter、setter方法不一样于superclass

replace array with object

一个数组,其中的元素表示不一样的东西

duplicated observed date

有一些domain data(业务处理逻辑相关的)置身于GUI控件中,而domain method须要访问之。

domain class 和GUI呈现分离,共享的数据经过观察者模式实现同步控制

replace magic number with symbolic constant

magic number 真的是人见人恨

encapsulate collection

若是函数返回一个集合,那么这个返回值应该是只读的,并且不该该提供群集合的 setter 方法,而应提供加入、删除集合元素的方法

Java中的unmodifiable系列就是返回只读集合

replace record with data class

record 好比来自数据库,用一个 dataclass 将全部 field 声明为 private ,提供对应的访问函数

replace type code with class

类型编码(type code)是一些常量或变量,通常有多个可能的值。普一般量使用的时候缺少类型检查,相似C++中的define,而class强加类型检查。

好比血型若是用4个整数(c语言中的enum)表示,那么是传参的时候没法限制类类型,可读性也差。C++11中enum class就解决了这个问题

前提是类型码不会用于switch中,不然就得使用下面的重构手法

replace type code with subclass

如图所示:

type code影响到了其所在类的行为, 那么就得使用多态,该方法为replace conditional with polymorphism作准备。

前提是type code在对象建立的时候就肯定,且声明周期内不可变。若是 type code多是变化的,只能使用replace type code with state/strategy

replace type code with state/strategy

replace type code with subclass同样,都是为replace conditional with polymorphism作准备

简化条件表达式

Decompose conditional

从if,then,else三个段落中提炼出独立函数,使代码更加清晰

consolidate conditional expression

一系列条件测试若是获得的是相同的结果,那么将这些条件合并为一个表达式,并将这个表达式提炼为一个独立函数。extract method也更好体现了作什么,而不是怎么作。

若是这些条件逻辑上原本是彼此独立的,那么不该该使用本项重构

consolidate duplicated conditional Fragments

在条件分支上有相同的一段代码,那么应该将这一段代码移到条件式以外,这是常常遇到的状况。

关键是这样更好体现了哪些是随条件变化而变化的,同时避免 duplicated code。

remove control flag

在循环的布尔表达式中,某个变量起控制标记,如 while(exit) ,以break语句或者return语句代替控制语句

replace nested conditional with guard clause

卫语句(guard clause):若是某一条件极其罕见,就应该单独检查该条件,并在该条件为真时马上返回,这样的单独检查成为卫语句。固然,我更喜欢称之为early return,每每能减小嵌套的深度,让代码可读性更好。

本质:给予某一条件特别的重视(if then else表示对分支的重视是相同的),卫语句表示:一旦这种状况发生,应该作一些必要的清理工做,而后退出。

replace conditional with polymorphism

将一个条件表达式的分支放进一个subclass的覆写函数内,并将原始函数声明为抽象函数

关于对replace type code with state/strategyreplace type code with subclass的选择:核心在于 type code 是否可能会在对象的生命周期内改变。

introduce null object

若是须要再三检查一个对象是否是null,那么以一个null object替换为null时的状况。null object 通常是常量,能够用 singleton 封装,其属性不会发生改变

须要注意的是:

  • 只在有大多数的客户代码须要 null object 作出相应相应时,才有必要使用 null object。固然,若是少数地方须要作出不一样响应,那么也能够用object.isNull区分
  • null object 是逻辑上可能出现的,是一种特殊状况,并非异常。好比书中的例子:一个出租房确实可能暂时没有租客。

introduce assertion

assertion 应该是一个永远为真的表达式,若是失败,表示程序出了错误。assert既能够帮助排查bug,也能够帮助读者理解代码做者的假设、约束

简化函数调用

rename method

add parameter

须要考虑增长参数是否会致使坏味道,long parameter list。若是能够经过已有的参数、属性得到新参数的值,那么就不该该增长。

remove parameter

重构的时候要注意多态(继承)的状况,不要遗漏。上同

separate query from modifier

将查询操做和修改操做分开

parameterize method

若干函数作了相似的工做,只是函数本体中包含了不一样的值。将致使函数差别的值做为参数传入,以下面的代码:

def tenPercentRaise(self):
        self._salary *= 1.1

    def fivePercentRaise(self):
        self._salary *= 1.05

    # 重构后的代码
    def raiseWithFactor(self, factor):
        self._salary *= (1 + factor)

重构后,只保留一个方法raiseWithFactor,但新函数应该加上参数合法性的检查。我的认为,若是factor的取值固定为少数的几个值,那么提供不一样的接口也是能够的,只不过对外接口统一调用同一个私有接口。

replace parameter with explicit methods

函数的操做彻底取决于参数值,则针对参数的每一个参数值,创建一个独立的函数

坏味道很明显,参数是离散的,函数内以条件式检查这些参数值,并根据不一样参数值作出不一样反应。好比下面这种类型的代码

def setSwitch(self, is_on):
        if is_on:
            # do a lot of thing let switch on 
        else:
            # do a lot of thing let switch off

preserve whole object

解决long parameter list的一种重构手法

replace parameter with methods

对象调用某个函数,将其返回值做为参数传递给另外一个函数,然后面一个函数也能够调用前一个函数 。那么在后一个函数中取出该项参数,并直接调用前面一个函数。动机在于若是能够经过非参数列表的方式得到参数值,那么就不要使用参数列表

使用前提

  • 参数计算过程(即前一个函数)不会依赖调用端的某个参数
  • 参数的存在多是为了未来的弹性时也不能使用本项重构

introduce parameter object

某些参数老是很天然地同时出现----把这些参数抽象为一个对象,好比常常遇到的是以一对值表明一个范围,如(start、end)、(lower、upper),用Range取代之

好处:

  • 缩减参数列表长度;
  • 更易理解和修改;
  • 能够把一些反复进行的计算移到对象里面(好比计算range差值)

remove setting method

若是class中某个属性在初始化的时候就设置,之后就不在改变了,那么应该去掉改属性的setter,还能够将属性设置为final(or const)。

hide method

一个函数,历来没有被其它class使用过,那么它应该为private。最小化对外接口,须要的时候再开放。

replace constructor with factory method

经常在多态 或者replace type code with subclass中使用。能够用来实现change value to reference,或者单例。同时,工厂方法也会比构造函数重载可读性更好

encapsulate downcast

不要让用户对你的函数返回值进行downcast,返回用户须要的类型

这是低版本Java的问题(没有模板),Java5.0以后没问题了。这也是强类型OO语言的问题,python就没有这个问题。

replace error code with exception

错误,异常与自定义异常 这篇文章对error code 和 exception 有较多讨论

replace exception with test

exception 不该该做为流程控制的手段,若是某种状况的出现不是意料以外的,那么就不该该抛出异常

处理继承体系

pull up field

把子类重复的属性移动到基类

pull up method

把子类重复的函数移动到基类,若是两个子函数类似但不尽相同,能够考虑使用form template method

pull up constructor body

各个子类的构造函数代码有同样的部分,则在基类中建立构造函数,在子类中调用

push down method

push down field

extract subclass

class中一些特性只被某些实体使用,那么新建一个subclass中,将这些特性转移到subclass

须要考虑到extract class与extract subclass的区别(委托与继承)

extract superclass

两个类有类似特性,把类似的部分移动到superclass,有时候为了代码复用也能够这么作

collapse hierarchy

基类与子类没有太大区别----合并之

form template method

replace inheritance with delegation

某个subclass只使用superclass的一部分,或是根本不须要继承而来的数据,则能够改继承为委托。

继承和委托也是adapter模式的两种实现方式,本人也倾向于delegation

replace Delegation with inheritance

太多的简单委托关系

关于引入新技术、新思想的思考

即便一个新技术(新思想)已经通过社区的验证,要引入到开发团队来也不是一件容易的事情,也会遇到重重阻力:

  • 大多数人还不知道如何使用这项新技术
  • 引入新技术的收益要长期才能看出来,那么何须如今去付出呢?若是回报周期过长,那么在收获的时候可能已经再也不当前的位置了
  • 新技术的引入并非非用不可,还须要花掉一些时间,老板(项目经理)愿意吗?
  • 在线上项目使用新技术,反而可能引入BUG,冒险是否值得?

若是你自己就是老板(技术Leader),且可以顶住来自产品的压力,那么能够强推一项新技术,虽然强推效果也不必定好。但若是是做为平级,怎么推广呢?如何解决这些障碍?

第一:培训与工具,经过培训、分享让团队快速掌握新技术,使用工具让成员掌握新思想。好比,想要遵照同一套代码规范,那么最好配上相应的代码检查。

第二:展示短时间、肉眼可见的利益,新技术不只要有长期收益,还得在短时间内就展示出其优势。若是短时间内就能看到好处,你们就愿意去积极尝试。

第三:下降开销,下降上手难度,技术的投入也是讲究投入产出比的,使用成本越低,你们就不会排斥。

第四:安全过渡,逐步进行,若是是线上项目,最好能有健全的回滚机制。

本质上,都是经过向你的Leader或者小伙伴展现,这个新东西又好又不贵,使用起来还很方便。

更有意思的是,书中提到Geoffrey Moore提出来的技术接纳曲线:

一个思想、技术、产品即便有先行者、尝鲜者的支持,但想要大众市场接受,还要跨过一条鸿沟。鸿沟的存在源于不一样人的不一样诉求:先行者关注的是新技术自己,而普罗大众关注的是成熟度、引入(使用)成本。

在构建之法也有很详细的分析。

Reference

重构 - 改善既有代码的设计
代码整洁之道
什么是整洁的代码
软件架构模式
错误,异常与自定义异常

相关文章
相关标签/搜索