重构,第一个案例(三)

21 篇文章 2 订阅
13 篇文章 2 订阅

知识点的梳理:

  • 如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性;
  • 重构的节奏:测试,小修改,测试,小修改,测试,小修改. ..重要的事情说三遍;

      

  • 案例描述
    • 此实例是一个影片出租店使用的程序,计算每一位顾客的消费金额并打印详单;操作者告诉程序:顾客租了哪些影片,租期多长,程序便根据租赁时间和影片类型算出费用。影片分为三类:普通片,儿童片和新片。除了计算费用,还要位常客计算积分,积分会根据租片种类是否位新片而有不同;
      • 此实例提到的类:
      • Movie(影片)与Rental(租赁)

public class Movie {

public static final int CHILDRENS = 2;

public static final int REGULAR = 0;

public static final int NEW_RELEASE = 1;

private String _title;

private int _priceCode;

public Movie(String title,int priceCode){

_title =title;

_priceCode = priceCode;

}

public int getPriceCode(){

return _priceCode;

}

public void setPriceCode(int arg){

_priceCode =arg;

}

public String getTitle(){

return _title;

}

}

public class Rental {

private Movie _movie;

private int _daysRented;

public Rental(Movie movie , int daysRented){

_movie = movie;

_daysRented = daysRented;

}

public int getDaysRented(){

return _daysRented;

}

public Movie getMovie(){

return _movie;

}

}

  

  • Customer(顾客)

import java.util.Enumeration;

import java.util.Vector;

  

public class Customer {

private String _name;

private Vector _rentals = new Vector();

public Customer(String name){

_name = name;

}

public void addRental(Rental arg){

_rentals.addElement(arg);

}

public String getName(){

return _name;

}

public String statement(){

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

double thisAmount = 0;

Rental each = (Rental)rentals.nextElement();

switch(each.getMovie().getPriceCode()){

case Movie.REGULAR:

thisAmount += 2;

if(each.getDaysRented() > 2){

thisAmount += (each.getDaysRented() -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

thisAmount += 1.5;

if(each.getDaysRented() > 3){

thisAmount += (each.getDaysRented() - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

thisAmount +=1.5;

if(each.getDaysRented() >3){

thisAmount += (each.getDaysRented() -3) * 1.5;

}

break;

}

frequentRenterPoints ++;

if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1){

frequentRenterPoints ++;

}

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

result += " Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return null;

}

}

Customer提供了一个用于生成详单的函数,下图是这个函数带来的交互过程:
 

  

  • 该段代码的问题:
    • 不符合面向对象,代码复用率低;
    • Customer中的statement()函数实在是~~~你懂的~
  • 重构第一步
    • 为即将修改的代码建立一组可靠的测试环境;
      • 重构时,要依赖测试,让它告诉我们是否引入了BUG;
    • 分解并重组statement():
      • 代码块愈小,愈容易管理;
      • statement的第一个问题是swtich()语句,将它单独作为一个函数较好;
        • 重构时,当我们提炼一个函数,我们必须知道可能出现的错误。所以在提炼之前,要先想出安全做法,避免引入BUG;
          • 首先要找出这段函数中的局部变量和参数,each和thisAmount;前者会被修改,后者不会;任何不会被修改的变量都可以当成参数传入新的函数;
          • 如果只有变量会被修改,可以把它当作返回值;
          • thisAmount是个临时变量,其值在每次循环起始处被设为0,并且在switch语句之前不会被改变,可以把新函数的返回值赋给它;
      • 代码重构:

import java.util.Enumeration;

import java.util.Vector;

  

public class Customer {

private String _name;

private Vector _rentals = new Vector();

public Customer(String name){

_name = name;

}

public void addRental(Rental arg){

_rentals.addElement(arg);

}

public String getName(){

return _name;

}

public String statement(){

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

double thisAmount = 0;

Rental each = (Rental)rentals.nextElement();

thisAmount = amountFor(each);

frequentRenterPoints ++;

if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1){

frequentRenterPoints ++;

}

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

result += " Amount owed is " + String.valueOf(totalAmount) + "\n";

result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return result;

}

private double amountFor(Rental aRental) {

double result = 0;

switch(aRental.getMovie().getPriceCode()){

case Movie.REGULAR:

result += 2;

if(aRental.getDaysRented() > 2){

result += (aRental.getDaysRented() -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

result += 1.5;

if(aRental.getDaysRented() > 3){

result += (aRental.getDaysRented() - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

result +=1.5;

if(aRental.getDaysRented() >3){

result += (aRental.getDaysRented() -3) * 1.5;

}

break;

}

return result;

}

}

仔细观察amountFor()函数,你会发现这个类使用了Rental类的信息,却没有它的所在类Customer类的信息;

大多数情况下,函数应该放在它所使用的数据的所属对象内,所以amountFor()应该移到Rental类去;

public class Rental {

private Movie _movie;

private int _daysRented;

public Rental(Movie movie , int daysRented){

_movie = movie;

_daysRented = daysRented;

}

public int getDaysRented(){

return _daysRented;

}

public Movie getMovie(){

return _movie;

}

double getCharge() {

double result = 0;

switch(getMovie().getPriceCode()){

case Movie.REGULAR:

result += 2;

if(getDaysRented() > 2){

result += (getDaysRented() -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

result += 1.5;

if(getDaysRented() > 3){

result += (getDaysRented() - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

result +=1.5;

if(getDaysRented() >3){

result += (getDaysRented() -3) * 1.5;

}

break;

}

return result;

}

}

//为了适应新类,要去掉参数,同时变更函数名称

改变Customer.amountFor()函数内容。使它委托调用新函数即可:

public class Customer {

//。。。

private double amountFor(Rental aRental){

return aRental.getCharge();

}

}

实际上,由于重构后的函数,只有一条语句,那么我们完全可以废弃它,直接将此语句挪出来:
public class Customer {

private String _name;

private Vector _rentals = new Vector();

public Customer(String name){

_name = name;

}

public void addRental(Rental arg){

_rentals.addElement(arg);

}

public String getName(){

return _name;

}

public String statement(){

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

double thisAmount = 0;

Rental each = (Rental)rentals.nextElement();

thisAmount = each.getCharge();

frequentRenterPoints ++;

if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1){

frequentRenterPoints ++;

}

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";

totalAmount += thisAmount;

}

result += " Amount owed is " + String.valueOf(totalAmount) + "\n";

result += " You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return result;

}

}

  • 重构后,各个类的状态:
  • 现在看来局部变量thisAmount有些多余了。它用来接受each.getCharge()的执行结果,然后就不再有任何改变。

public class Customer {

private String _name;

private Vector _rentals = new Vector();

public Customer(String name){

_name = name;

}

public void addRental(Rental arg){

_rentals.addElement(arg);

}

public String getName(){

return _name;

}

public String statement(){

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

frequentRenterPoints ++;

if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1){

frequentRenterPoints ++;

}

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";

totalAmount += each.getCharge();

}

result += " Amount owed is " + String.valueOf(totalAmount) + "\n";

result += " You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return result;

}

}

临时变量有时会引发许多问题,会导致大量参数传来传去,所以在可以省略的时候,要尽量省略;
在此实例中,去掉局部变量thisAmount而使用
each.getCharge()函数,会导致费用被计算两次。但是在Rental类中会被优化。如果代码有合理的组织和管理,优化就会有很好的效果;

  • 提炼"常客积分计算"代码
    • 可以把积分计算责任放在Rental类身上。

public class Customer {

//...

public String statement(){

double totalAmount = 0;

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

frequentRenterPoints += each.getFrequentRenterPoints();

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";

totalAmount += each.getCharge();

}

result += " Amount owed is " + String.valueOf(totalAmount) + "\n";

result += " You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return result;

}

}

public class Rental {

//。。。

int getFrequentRenterPoints() {

if((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1){

return 2;

} else{

return 1;

}

}

}

  

  • 重构前后的UML对比,左边为原始图,右边为重构图

  • 去除临时变量
    • Customer中,有totalAmount和frequentRenterPoints两个临时变量。它们都是用来从Customer对象相关的Rental对象中获得某个总量。现在利用查询函数(queryMethod)来取代totalAmount和frequentRentalPoints这两个临时变量。利用Customer中的getTotalCharge()取代totalAmount:

public class Customer {

//。。。

public String statement(){

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

frequentRenterPoints += each.getFrequentRenterPoints();

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";

}

result += " Amount owed is " + String.valueOf(getTotalCharge()) + "\n";

result += " You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points ";

return result;

}

private double getTotalCharge(){

double result = 0;

Enumeration rentals = _rentals.elements();

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

result += each.getCharge();

}

return result;

}

}

totalAmount在循环内部被复制,我们要把循环复制到查询函数中:
public class Customer {

//...

public String statement(){

int frequentRenterPoints = 0;

Enumeration rentals = _rentals.elements();

String result = "Rental Record for " + getName() + "\n";

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

frequentRenterPoints += each.getFrequentRenterPoints();

result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";

}

result += " Amount owed is " + String.valueOf(getTotalCharge()) + "\n";

result += " You earned " + String.valueOf(getTotaIFrequentRenterPoints()) + " frequent renter points ";

return result;

}

private int getTotaIFrequentRenterPoints(){

int result = 0;

Enumeration rentals = _rentals.elements();

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

result += each.getFrequentRenterPoints();

}

return result;

}

//...

}

此步骤后的UML交互图:

在Customer类中加入新的功能代码:

public String htmlStatement(){

Enumeration rentals = _rentals.elements();

String result = "<H1>Rentals for<EN>" + getName() + "</EN></H1><P>\n";

while(rentals.hasMoreElements()){

Rental each = (Rental)rentals.nextElement();

result += each.getMovie().getTitle() + ": " + String.valueOf(each.getCharge()) + "<BR>\n";

}

result +="<P>You owe<EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n";

result +="On this rental you earend<EM>" + String.valueOf(getTotaIFrequentRenterPoints()) + "</EM> frequent renter points<P>";

return result;

}

  • 运用多态取代与价格相关的条件逻辑
    • switch:最好不要在另一个对象的属性基础上运用switch语句。如果非要使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用;在getCharge中,使用Movie中的getPriceCode方法。所以我们最好把getCharge函数移到Movie中,同时该方法又需要Rental的_daysRented数据,将租期长度作为参数传递进去。
    • 计算费用时需要两项数据:租期长度和影片类型。在这里我们是将Rental中的租期长度传给了Movie,之所以这样做的原因使该实例可能发生变化的是加入新影片类型,这种变化会带来不稳定倾向。如果影片类型有所变化,我们应该尽量控制它造成的影响,所以选在Movie对象内计算费用;

public class Movie {

//。。。

double getCharge(int daysRented) {

double result = 0;

switch(getPriceCode()){

case Movie.REGULAR:

result += 2;

if(daysRented > 2){

result += (daysRented -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

result += 1.5;

if(daysRented > 3){

result += (daysRented - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

result +=1.5;

if(daysRented >3){

result += (daysRented -3) * 1.5;

}

break;

}

return result;

}

}

将计算方法放入Movie类后,然后修改Rental的getCharge(),让它使用这个新函数:
public class
Rental {

private Movie _movie;

private int _daysRented;

public Rental(Movie movie , int daysRented){

_movie = movie;

_daysRented = daysRented;

}

public int getDaysRented(){

return _daysRented;

}

public Movie getMovie(){

return _movie;

}

int getFrequentRenterPoints() {

if((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1){

return 2;

} else{

return 1;

}

}

double getCharge(){

return _movie.getCharge(_daysRented);

}

}

  • 处理完成getCharge()后,Rental中的getFrequentRenterPoints()也存在这样的问题,使用了Movie中的priceCode参数,所以将这个函数搬移至Movie:

public class Rental {

//...

int getFrequentRenterPoints() {

return _movie.getFrequentRenterPoints(_daysRented);

}

double getCharge(){

return _movie.getCharge(_daysRented);

}

}

public class Movie {

//...

double getCharge(int daysRented) {

double result = 0;

switch(getPriceCode()){

case Movie.REGULAR:

result += 2;

if(daysRented > 2){

result += (daysRented -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

result += 1.5;

if(daysRented > 3){

result += (daysRented - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

result +=1.5;

if(daysRented >3){

result += (daysRented -3) * 1.5;

}

break;

}

return result;

int getFrequentRenterPoints(int daysRented) {

if((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1){

return 2;

} else{

return 1;

}

}

}

  • 实现继承
    • 因为有数种影片类型,它们以不同的方式回答相同的问题。这很像子类的工作,建立Movie的三个子类,每个都有自己的计费法:

可用多态来取代Switch语句。但在这里,一部影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的类。但是,我们可以利用"状态模式"。如果这样做的话,类看起来就像下图:

  • 为了引入"状态模式",我们需要将与类型相关的行为搬移至"状态模式"内。之后将switch语句移动Price类。
    • 首先,要针对类型代码使用"自封装字段",确认任何时候都通过取值函数和设置函数来访问类型代码。多数访问操作来自其他类,它们已经在使用取值函数。

看看Movie的构造函数,它仍然直接访问价格代码:

public Movie(String title,int priceCode){

_title =title;

_priceCode = priceCode;

}

修改它,使用set函数为它设值:

public Movie(String title,int priceCode){

_title =title;

setPriceCode(priceCode);

}

  • 新建Price类,在其中添加一个抽象函数,提供类型相关的行为。所有的子类中也要加上对应的具体函数:

public abstract class Price {

abstract int getPriceCode();

}

public class ChildrensPrice extends Price {

@Override

int getPriceCode() {

return Movie.CHILDRENS;

}

}

public class NewReleasePrice extends Price {

@Override

int getPriceCode() {

return Movie.NEW_RELEASE;

}

}

public class RegularPrice extends Price {

@Override

int getPriceCode() {

return Movie.REGULAR;

}

}

  • 现在我们要在Movie类中的设值函数中,做出类型判断。修改Movie类内的"价格代号"访问函数(也就是setter和getter),让它们使用新类:
    • 引入了"状态模式",就需要要使用"状态对象"。所以在Movie类中,引入Price对象,不再保存一个_priceCode变量:
    • 同时修改getPriceCode(),使用抽象类的方法,做到多态;

public int getPriceCode(){

return _price.getPriceCode();

}

private Price _price;

public void setPriceCode(int arg){

switch(arg){

case REGULAR:

_price = new RegularPrice();

break;

case CHILDRENS:

_price = new ChildrensPrice();

break;

case NEW_RELEASE:

_price = new NewReleasePrice();

break;

default:

throw new IllegalArgumentException("Incorrect Price Code");

}

}

  • 现在要移动getCharge()。将Movie中的代码权责,移交给"状态对象"Price

public class Movie {

//。。。

double getCharge(int daysRented) {

return _price.getCharge(daysRented);

}

//...

}

public abstract class Price {

abstract int getPriceCode();

double getCharge(int daysRented) {

double result = 0;

switch(getPriceCode()){

case Movie.REGULAR:

result += 2;

if(daysRented > 2){

result += (daysRented -2 ) * 1.5;

}

break;

case Movie.NEW_RELEASE:

result += 1.5;

if(daysRented > 3){

result += (daysRented - 3) * 1.5;

}

break;

case Movie.CHILDRENS:

result +=1.5;

if(daysRented >3){

result += (daysRented -3) * 1.5;

}

break;

}

return result;

}

}

  • 用多态来取代条件表达式switch.每次取出一个case分支,在相应的类建立一个覆盖函数。先从RegularPrice开始:

public class RegularPrice extends Price {

@Override

int getPriceCode() {

return Movie.REGULAR;

}

double getCharge(int daysRented){

double result = 2;

if(daysRented > 2){

result += (daysRented -2) * 1.5;

}

return result;

}

}

public class ChildrensPrice extends Price {

@Override

int getPriceCode() {

return Movie.CHILDRENS;

}

double getCharge(int daysRented){

double result = 1.5;

if(daysRented > 3){

result += (daysRented -3) * 1.5;

}

return result;

}

}

public class NewReleasePrice extends Price {

@Override

int getPriceCode() {

return Movie.NEW_RELEASE;

}

double getCharge(int daysRented){

return daysRented * 3;

}

}

  

  • 将case分解完成后,修改Price中的getCharge函数:

public abstract class Price {

abstract int getPriceCode();

abstract double getCharge(int daysRented);

}

  • 现在运用同样的手法来处理Movie中的getFrequentRenterPoints(),将权责移交给Price类:

public class Movie {

public static final int CHILDRENS = 2;

public static final int REGULAR = 0;

public static final int NEW_RELEASE = 1;

private String _title;

private int _priceCode;

public Movie(String title,int priceCode){

_title =title;

setPriceCode(priceCode);

}

public int getPriceCode(){

return _price.getPriceCode();

}

private Price _price;

public void setPriceCode(int arg){

switch(arg){

case REGULAR:

_price = new RegularPrice();

break;

case CHILDRENS:

_price = new ChildrensPrice();

break;

case NEW_RELEASE:

_price = new NewReleasePrice();

break;

default:

throw new IllegalArgumentException("Incorrect Price Code");

}

}

public String getTitle(){

return _title;

}

  

double getCharge(int daysRented) {

return _price.getCharge(daysRented);

}

int getFrequentRenterPoints(int daysRented) {

return _price.getFrequentRenterPoints(daysRented);

}

}

public abstract class Price {

abstract int getPriceCode();

abstract double getCharge(int daysRented);

int getFrequentRenterPoints(int daysRented) {

if((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1){

return 2;

} else{

return 1;

}

}

}

  

  • getFrequentRenterPoints()函数涉及的内容与子类NewReleasePrice相关,所以将该函数与NewReleasePrice类相关的部分抽离出来,单独重写放在NewReleasePrice类中。而与该类无关的函数部分还保留在Price中:

public class NewReleasePrice extends Price {

@Override

int getPriceCode() {

return Movie.NEW_RELEASE;

}

double getCharge(int daysRented){

return daysRented * 3;

}

int getFrequentRenterPoints(int daysRented){

return (daysRented >1) ? 2:1;

}

}

public abstract class Price {

abstract int getPriceCode();

abstract double getCharge(int daysRented);

int getFrequentRenterPoints(int daysRented) {

return 1;

}

}

  • 引入"状态模式"的好处:
    • 对修改任何与价格有关的行为,或是添加新的定价标准,或者加入其他取决于价格的行为,程序的修改会容易很多;
    • UML图:
  • 类图:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 原始需求背景: * 网宿CDN要按月收取客户的服务费用,根据流量的大小、 * 服务的类型等,收取不同的费用,收费规则如下: * web应用:1000元/M * 流媒体应用:1000元/M*0.7 * 下载应用:1000元/M*0.5 * 月末打印报表时,要罗列每个用户每个频道的费用、客户总费用, * 还要打印该客户的重要性指数,重要性指数=网页流/100+下载流量/600; * * 需求变更场景: * 系统已经开发出来了,接下来,运维部门现在希望对系统做一点修改, * 首先,他们希望能够输出xml,这样可以被其它系统读取和处理,但是, * 这段代码根本不可能在输出xml的代码中复用report()的任何行为,唯一 * 可以做的就是重写一个xmlReport(),大量重复report()中的行为,当然, * 现在这个修改还不费劲,拷贝一份report()直接修改就是了。 * 不久,成本中心又要求修改计费规则,于是我们必须同时修改xmlReport() * 和report(),并确保其一致性,当后续还要修改的时候,复制-黏贴的问题就 * 浮现出来了,这造成了潜在的威胁。 * 再后来,客服部门希望修改服务类型和用户重要性指数的计算规则, * 但还没决定怎么改,他们设想了几种方案,这些方案会影响用户的计费规则, * 程序必须再次同时修改xmlReport()和report(),随着各种规则变得越来越复杂, * 适当的修改点越 来越难找,不犯错误的机会越来越少。 * 现在,我们运用所学的OO原则和方法开始进行改写吧。 */
重构是一种对既有代码进行重新设计和优化的过程。重构的目的是为了使代码更易于理解,更易于维护,更易于扩展。第二版的《重构改善既有代码的设计》pdf收录了更多实践经验和案例,以帮助读者更好地理解和应用重构技术。 首先,第二版pdf对于重构的核心概念和原则进行了深入的阐述和解释。通过详细的案例分析和实用的技巧,读者可以更加清晰地理解何时以及如何进行重构。同时,还介绍了一些新的重构技术和工具,帮助读者更高效地进行代码设计和优化。 其次,第二版pdf在案例分析方面进行了丰富的补充。通过各种不同类型的案例,涵盖了各种语言和开发环境下的重构实践,帮助读者更好地理解重构的实际应用。此外,还介绍了一些最新的行业趋势和实践经验,使得读者可以更好地掌握重构的最新发展动态。 最后,第二版pdf还增加了一些实用的工具和资源,帮助读者更好地进行重构。这些工具包括一些常用的重构插件和扩展,以及一些优秀的开源项目和社区资源,使得读者可以更加便捷地进行重构工作。同时,还提供了一些实用的技巧和建议,帮助读者更好地进行代码设计和优化。 总之,第二版《重构改善既有代码的设计》pdf通过更加详实的案例分析和丰富的实践经验,帮助读者更好地掌握重构的核心原则和技术,从而更加高效地进行代码设计和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值