重构-改善既有代码的设计:简化条件表达式(七)

本文介绍如何通过重构简化复杂的条件表达式,包括分解合并条件、去除重复代码、使用多态及引入空对象等方法,提高代码可读性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简化条件表达式:
一、分解和合并条件
二、去重复代码
三、简化条件表达式:控制标记、卫语句、多态。

目录

          一、分解和合并条件

1.Decompose Conditional 分解条件表达式从if、then、else三个段落中分别提炼出独立函数

2.Consolidate Conditional Expression 合并条件表达式:一系列条件的结果相同,将这些条件合并为一个条件表达式,并将这个条件表达式提炼为一个独立函数。

二、去重复代码

3.Consolodate Duplicate Conditional Fragments 合并重复的条件片段将这段重复代码移到条件表达式之外。

三、简化条件表达式

4.Remove Control Flag 移除控制标记:“单一出口“原则会让你在代码中加入让人讨厌的控制标记,大大降低条件表达式的可读性。可以通过编程语言提供break和continue语句直接跳出复杂的条件语句。

5.Replace Nested Conditional with Guard Clauses 以卫语句取代嵌套条件表达式

6.Replace Conditional with Polymorphism 以多态取代条件表达式:多态的最根本的好处是:如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写某些的条件表达式。

7. Introduce Null Object 引入Null对象将null值替换为null对象

8. Introduce Assertion 引入断言:Assert.notNull(project,"project can not null");


1.分解条件表达式 Decompose Conditional


问题:你有一个复杂的条件语句。

解决:从if、then、else三个段落中分别提炼出独立函数

目的:是减少条件分支的代码行数,通过提炼函数来到达代码自解释。

场景:条件分支中,条件判断很复杂,一般包含两个或以上逻辑操作,并且不通的流程分支都有不同的业务流程控制,那么这种重构手法就可以派上用场了。

当你的条件表达式比较复杂时,你就可以对其进行拆分。一般拆分的规则为:经if后的复杂条件表达式进行提取,将其封装成函数。如果if与else语句块中 的内容比较复杂,那么就将其提取,也封装成独立的函数,然后在相应的地方进行替换。

if (date.before(SUMMER_START) && date.after(WINTER_END))  {
  charge = quantity * winterRate + winterServiceCharge;
} else  {
   charge = quantity * summerRate;
}

if (notSummer(date)){
    charge = winterCharge(quantity);
} else {
    charge = summerCharge(quantity);
}

或者:

charge = notSummer(date) ? winterCharge(quantity) : summerCharge(quantity);

        程序之中,复杂的条件逻辑是最常导致复杂度上升的地点之一。你必须编写代码来检查不同的条件分支、根据不同的分支做不同的事,然后,你很快就会得到一个相当长的函数。大型函数自身就会使代码的可读性下降,而条件逻辑则会使代码更难阅读。在带有复杂条件逻辑的函数中,代码(包括检查条件分支的代码和真正实现功能的代码)会告诉你发生的事,当常常让你弄不清为什么会发生这样的事,这就说明代码的可读性的确大大降低了。

       和任何大块头代码一样,你可以将它分解为多个独立函数,根据每个小块代码的用途,为分解的新函数命名,并将原函数中对应的代码改为调用新建函数,从而更清楚的表达自己的意图。对于条件逻辑,将每个分支条件分解为新函数还可以给你带来更多好处:可以突出条件逻辑,更清楚地表明每个分支的作用,并且突出每个分支的原因。

2.合并条件表达式 Consolidate Conditional Expression


问题:你有一系列条件测试,都得到相同结果。

解决:将这些测试合并为一个条件表达式,并将这个条件表达式提炼为一个独立函数。

目的:合并不必要的逻辑条件判断,从而提高代码的可读性。

场景:在条件分支中,虽然检查的条件不一致,但是最终所流向的行为是一致的,那么我就可以将这些表现行为一致的分支逻辑合并起来,结合上面的“分解条件表达式”的重构手法,合并不必要的逻辑条件判断,从而提高代码的可读性。

//A和B部分就是合并

public String getStuLevel(int score){
    if (score == 100) {
            return "A";
    } else if (score >= 90) {
            return "A";

    } else if (score >= 80) {
            return "B";
    } else if (score >= 70) {
            return "B";

    } else if (score >= 60) {
           return "C";
    } else{
            
        return "D";
    }
}

有时你会发现这样一串条件检查:检查条件各不相同,最终行为却一致。如果发现这种情况,就应该使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。

之所以要合并条件表达式,有2个重要原因:

  •      首先,合并后的条件代码会告诉你“实际上只有一次条件检查,只不过有多个并列条件需要检查而已”,从而使这一次检查的用意更清晰。当然,合并前和合并后的代码有着相同的结果,但原先代码传达出的信息却是“这里有一些各自独立的条件测试,它们只是恰好同时发生”。
  •       其次,这项重构往往可以为你使用Extract Method(提炼方法)做好准备。将检查条件提炼成一个独立函数对于厘清代码意义非常有用,因为它把描述“做什么“的语句换成了“为什么这样做”。

       条件语句的合并理由也同时指出了不要合并的理由:如果你认为这些条件检查的确彼此独立,的确不应该被视为同一次检查,那么就不要使用本项重构。因为在这种情况下,你的代码已经清晰表达出自己的意义。

3. 合并重复的条件片段 Consolodate Duplicate Conditional Fragments


问题:在条件表达式的每个分支上有着相同的一段代码。

解决:将这段重复代码移到条件表达式之外。

目的:更清楚地表明哪些东西是随着条件变化而变化的,哪些东西是永远都不会变化的。

场景:一组条件表达式的所有分支都执行了一段相同的逻辑。如果是这样,你就应该将这段代码移动到条件表达式外面。

一组条件表达式的所有分支都执行了相同的某段代码。你应该将这段代码搬移到表达式外面。这样,代码才能更清楚地表明哪些东西随条件变化而变化、哪些东西保持不变。

4.移除控制标记变量 Remove Control Flag


问题:在一系列布尔表达式中,某个变量带有“控制标记’的作用。赞同“单一入口”原则。

但“单一出口”原则会在代码中加入讨厌的控制标记,大大降低条件表达式的可读性。

解决:以break或return语句取代控制标记。

目的:通过break,continue和return等方式结束循环,删除标志控制,提高程序的可读性。

场景:在循环体以内有一个表达式或者标志用来判断循环条件是否继续的场景,通过修改条件表达式或变量来控制循环体是否继续执行。

 

                                            

在一系列条件表达式中,常常会看到用以判断何时停止条件检查的控制标记。这样的标记带来的麻烦超过了它所带来的便利。人们之所以会使用这样的控制标记,因为结构化编程原则告诉他们:每个子程序只能有一个入口和出口。“单一出口“原则会让你在代码中加入让人讨厌的控制标记,大大降低条件表达式的可读性。这就是编程语言提供break和continue语句的原因:用它们跳出复杂的条件语句。去掉控制标记所产生的效果往往让你大吃一惊:条件语句真正的用途会清晰得多。

对于那些刚从C转到JAVA或者长期从事结构化编程的人员来说,就很重要了。这一点重构手法能够让你摆脱结构化编程的“单一原则(一个程序只能有一个入口和一个出口)”的束缚。嵌套条件代码,往往由那些深心“每个函数只能由一个出口”的程序员写出。此条规则太简单粗暴了。如果对函数剩余部分不再由兴趣,应该立即退出。引导阅读者去看一个没有用的else区段,只会妨碍他们的理解。

5.以卫语句取代嵌套条件表达式 Replace Nested Conditional with Guard Clauses


问题:函数中的条件逻辑使人难以看清正常的执行途径。

解决:使用卫语句表现所有特殊情况。

目的:强调某一个分支的逻辑重要性,保持代码清晰才是最关键的。

场景:在条件分支中,针对一些特殊的分支,为了强调其重要性,就可以使用卫语句来强调某一个分支的逻辑重要性。

条件表达式通常有2种表现形式。

第一:所有分支都属于正常行为。

第二:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。

       这2类条件表达式有不同的用途。如果2条分支都是正常行为,就应该使用形如if…..else…..的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”。

       Replace Nested Conditional with Guard Clauses (以卫语句取代嵌套条件表达式)的精髓是:给某个分支以特别的重视。它告诉阅读者:这种情况很罕见,如果它真的发生了,请做一些必要的整理工作,然后退出。

       “每个函数只能有一个入口和一个出口”的观念,根深蒂固于某些程序员的脑海里。现今的编程语言都会强制保证每个函数只有一个入口,至于“单一出口”规则,其实不是那么有用。保持代码清晰才是最关键的:如果单一出口能使这个函数更清晰易读,那么就使用单一出口;否则就不必这么做。

(卫语句就是把复杂的条件表达式拆分成多个条件表达式,比如一个很复杂的表达式,嵌套了好几层的if - then-else语句,转换为多个if语句,实现它的逻辑,这多条的if语句就是卫语句.:

//未使用卫语句
public void getHello(int type) {
    if (type == 1) {
        return;
    } else {
        if (type == 2) {
            return;
        } else {
            if (type == 3) {
                return;
            } else {
                setHello();
            }
        }
    }
} 

//使用卫语句
public void getHello(int type) {
    if (type == 1) {
        return;
    }
    if (type == 2) {
        return;
    }
    if (type == 3) {
        return;
    }
    setHello();
}

6.以多态取代条件表达式 Replace Conditional with Polymorphism


问题:你手上一个条件表达式,它根据对象类型的不同而选择不同的行为。当需要扩展新的类型时,我们不得不追加if else(或switch)语句块,以及相应的逻辑,这无疑降低了程序的可扩展性,也违反了面向对象的开闭原则。

解决:将这个条件表达式的每个分支放进一个子类的覆写函数中,然后将原始函数声明为抽象函数。

目的:这种重构手法不是用来取代条件分支的,而是当我们的条件分支在现在或者未来会达到一定数目的情况,可以采用这种手法来提高代码的可读性和可扩展性

场景:当然是条件分支中,每个分支都具有同等的又不同的逻辑操作,那么我们就可以利用多来实现程序的扩展。

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;
 
    if ("add".equals(operator)) {
        result = a + b;
    } else if ("multiply".equals(operator)) {
        result = a * b;
    } else if ("divide".equals(operator)) {
        result = a / b;
    } else if ("subtract".equals(operator)) {
        result = a - b;
    }
    return result;
}

当出现大量类型检查和判断时,if else(或switch)语句的体积会比较臃肿,这无疑降低了代码的可读性。另外,if else(或switch)本身就是一个“变化点”,当需要扩展新的类型时,我们不得不追加if else(或switch)语句块,以及相应的逻辑,这无疑降低了程序的可扩展性,也违反了面向对象的开闭原则。

基于这种场景,我们可以考虑使用“多态”来代替冗长的条件判断,将if else(或switch)中的“变化点”封装到子类中。这样,就不需要使用if else(或switch)语句了,取而代之的是子类多态的实例,从而使得提高代码的可读性和可扩展性。很多设计模式使用都是这种套路,比如策略模式、状态模式。

public interface Operation { 
  int apply(int a, int b); 
}

public class Addition implements Operation { 
  @Override 
  public int apply(int a, int b) { 
    return a + b; 
  } 
}

public class OperatorFactory {
    private final static Map<String, Operation> operationMap = new HashMap<>();
    static {
        operationMap.put("add", new Addition());
        operationMap.put("divide", new Division());
        // more operators
    }
 
    public static Operation getOperation(String operator) {
        return operationMap.get(operator);
    }
}

public int calculate(int a, int b, String operator) {
    if (OperatorFactory .getOperation == null) {
       throw new IllegalArgumentException("Invalid Operator");
    }
    return OperatorFactory .getOperation(operator).apply(a, b);
}

      多态的最根本的好处是:如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写某些的条件表达式。

       正因为有了多态,所以你会发现:“类型吗的switch语句”以及 ”基于类型名称的if-then-else语句“在面向对象程序中很少出现。

       多态能够给你带来很多好处。如果同一组条件表达式在程序的许多地点出现,那么使用多态的收益是最大的。使用条件表达式时,如果你想添加一种新类型,就必须查找并更新所有条件表达式。但如果使用多态,只需建立一个新的子类,并在其中提供适当的函数就行了。类的用户不需要了解这个子类,这就大大降低了系统各部分之间的依赖,使系统升级更加容易。

7. Introduce Null Object 引入Null对象


你需要再三检查某对象是否为null,当使用一个方法返回的对象时,而这个对象可能为空,这个时候需要对这个对象进行操作前,需要进行判空,否则就会报空指针。当这种判断频繁的出现在各处代码之中,就会影响代码的美观程度和可读性,甚至增加Bug的几率。

问题:空引用的问题在Java中无法避免,需要使用条件表达式进行判空。

解决:通过代码编程技巧(引入空对象)来改善这一问题,将null值替换为null对象。

目的:通过子类都可以重写一个父类的这个方法,去避免条件表达式所引入的为空判断,提高代码的可读性。

if (customer == null) {
  plan = BillingPlan.basic();
} else {
  plan = customer.getPlan();
}


重构之后的代码:

class NullCustomer extends Customer {
  boolean isNull() {
           return true;
  }
  Plan getPlan() {
    return new NullPlan();
  }
  // Some other NULL functionality.
}


 
// Replace null values with Null-object.
customer = (order.customer != null) ?order.customer : new NullCustomer();
 
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
 

       多态的最根本好处在于:你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为-你只管调用该行为就是了,你只管调用其方法就行了,其他的一切多态机制会为你安排妥当。当某个字段内容是null时,多态可扮演另一个较不直观的用途。

       要运用实现上述方法,我们需要引入一个null对象,这里的null对象不是直接返回一个null,而是顶一个class继承自原来的类,然后提供一个方法isEmpty();父类返回false,子类(null对象)返回true,这样只要在有空判断的地方,我们都可以直接调用isEmpty()等方法解决我们程序中的为空校验,编码这样的ugly代码。

//空对象的例子
public class OperatorFactory { 
  static Map<String, Operation> operationMap = new HashMap<>(); 
  static { 
    operationMap.put("add", new Addition()); 
    operationMap.put("divide", new Division()); 
    // more operators 
  } 
  public static Optional<Operation> getOperation(String operator) { 
    return Optional.ofNullable(operationMap.get(operator)); 
  } 
} 
public int calculate(int a, int b, String operator) { 
  Operation targetOperation = OperatorFactory.getOperation(operator) 
     .orElseThrow(() -> new IllegalArgumentException("Invalid Operator")); 
  return targetOperation.apply(a, b); 
}

//特殊对象的例子
public class InvalidOp implements Operation { 
  @Override 
  public int apply(int a, int b)  { 
    throw new IllegalArgumentException("Invalid Operator");
  } 
}

8. 引入断言 Introduce Assertion


某一段代码需要对程序状态做出某种假设。以断言明确表现这种假设。

常常会有这样一段代码:只有当某个条件为真时,该段代码才能正常运行。

并且Spring框架本身也提供了断言的工具类,比如下面这段代码:

public void getProjectLimit(String project){
    if(project == null){
        throw new RuntimeException("project can not null");
    }
    doSomething();
}

加入Spring的断言后的代码

public void getProjectLimit(String project){
    Assert.notNull(project,"project can not null");
    doSomething();
}

       这样的假设通常并没有在代码中明确表现出来,你必须阅读整个算法才能看出。有时程序员会以注释写出这样的假设。可以使用断言明确标明这些假设。

       断言是一个条件表达式,应该总是为真。如果它失败,不是程序员犯了错误。因此断言的失败应该导致一个非受控异常。断言绝对不能被系统的其他部分使用。实际上,程序最后的成品往往将断言删除。因此,标记“某个东西是个断言”是很重要的。

       断言可以作为交流与调试的辅助。在交流的角度上,断言可以帮助程序阅读者了解代码所做的假设;在调试的角度上,断言可以在距离bug最近的地方抓住它们。

第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hguisu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值