zoukankan      html  css  js  c++  java
  • 《重构》读书笔记(一)

    一.说明

    1.《重构》读书笔记指的是对fowler先生《重构》第二版的阅读观感(以下称重构2),这个版本或许像译者所说的fowler先生想要传达的理念是:

      千里之行积于跬步,越是面对复杂多变的外部环境,越是要做好基本功、迈出扎实步。

    2. 译者认为重构2的重构原则是: " 旧的不变,新的创建,一步切换,旧的再见。"

    3.《重构》传达的是一种工匠精神。我喜欢的译者观点是:一个对匠艺上心的专业人士,日积月累对过程与方式的重视,是能有所成就的。

    二.第一章

    1.何为重构?

    重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减小整理过程中引入错误的概率。本质上说,重构就是在代码写好之后改进它的设计。

    2.为何重构?

    在软件开发的大部分历史时期,大部分人相信应该先设计而后编码:首先得有一个良好的设计,然后才能开始编码。

    但是,随着时间流逝,人们不断修改代码,于是根据原先设计所得的系统,整体结构逐渐衰弱。代码质量慢慢沉沦,编码工作从严谨的工程堕落为胡砍乱劈的随性行为。

    设计不是在一开始完成的,而是在整个开发过程中逐渐浮现出来。在系统构筑过程中,我学会了如何不断改进设计。这个“构筑-设计”的反复互动,可以让一个程序在开发过程中持续保有良好的设计。
     3.第一个重构实例
    设想有一个戏剧演出团,演员们经常要去各种场合表演戏剧。通常客户(customer)会指定几出剧目,而剧团则根据观众(audience)人数及剧目类型来向客户收费。该团目前出演两种戏剧:悲剧(tragedy)和喜剧(comedy)。给客户发出账单时,剧团还会根据到场观众的数量给出“观众量积分”(volumecredit)优惠,下次客户再请剧团表演时可以使用积分获得折扣——你可以把它看作一种提升客户忠诚度的方式。
    plays.json:
    {  "hamlet": {"name": "Hamlet", "type": "tragedy"},  "as-like": {"name": "As You Like It", "type": "comedy"},  "othello": {"name": "Othello", "type": "tragedy"}}
    invoices.json:
    [  {    "customer": "BigCo",    "performances": [      {        "playID": "hamlet",        "audience": 55      },      {        "playID": "as-like",        "audience": 35      },      {        "playID": "othello",        "audience": 40      }    ]  }];
    账单函数:
    function statement (invoice, plays) {  
      let totalAmount = 0;  
      let volumeCredits = 0; 
      let result = `Statement for ${invoice.customer}
    `;  
      const format = new Intl.NumberFormat("en-US",
          { style: "currency", currency: "USD",
            minimumFractionDigits: 2 }).format;   for (let perf of invoice.performances) {   const play = plays[perf.playID];   let thisAmount = 0;   switch (play.type) {   case "tragedy":     thisAmount = 40000;     if (perf.audience > 30) {     thisAmount += 1000 * (perf.audience - 30);     }     break;   case "comedy":     thisAmount = 30000;     if (perf.audience > 20) {      thisAmount += 10000 + 500 * (perf.audience - 20); }        thisAmount += 300 * perf.audience;        break;     default:       throw new Error(`unknown type: ${play.type}`);   }
      // add volume credits   volumeCredits += Math.max(perf.audience - 30, 0);   // add extra credit for every ten comedy attendees    if ("comedy" === play.type)     volumeCredits += Math.floor(perf.audience / 5);     // print line for this order     result += ` ${play.name}: ${format(thisAmount/100)} (${perf.audience} seats) `;    totalAmount += thisAmount;
      }   result
    += `Amount owed is ${format(totalAmount/100)} `;  result += `You earned ${volumeCredits} credits `; return result;
     } 用上面的数据文件(invoices.json和plays.json)作为测试输入,运行这段代码,会得到如下输出: Statement
    for BigCo Hamlet: $650.00 (55 seats) As You Like It: $580.00 (35 seats) Othello: $500.00 (40 seats) Amount owed is $1,730.00 You earned 47 credits

    这样的设计:代码组织不太清晰,但还在可以忍受的限度内。但如果这段代码身处一个更大规模--也许几百行--的程序中,把所有代码放在一个函数里就很难理解了。

    虽然结构不清晰却是可以使用,对编译器来说没有影响,但是,当我需要修改的时候,差劲的系统就很难找到修改点。就很可能犯错误,引入bug。

    因此如果要修改一个几百行的程序,我们会期望它有良好的结构,并且已经分解成一系列函数和其他程序要素,这能帮我们更易于清楚的了解这段代码在做什么。但如果程序杂乱无章,先为它整理出结构来,再做需要的修改,通常更加简单。

    * 如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于进行更改,那就先重构那个程序,
       使其比较容易添加该特性,然后再添加该特性。

     试想修改:1.以HTML格式输出详单;2.戏剧分类规则和计费规则的变化;这些修改可能在几个月后还会出现变化。

    如果不重构,函数则会变得越来越杂乱,需求的变化使重构变得必要!!!

    重构过程:

    A.重构第一步:得确保即将修改的代码拥有一组可靠的测试。使测试能自我检验至关重要,否则就得耗费大把时间来回比对,这会降低开发速度。

    * 重构前,先检查自己是否有一套可靠的测试集。这些测试必须有自我检验能力。

    B.分解statement函数:

    [原文]:每当看到这样长长的函数,我便下意识地想从整个函数中分离出不同的关注点。第一个引起我注意的就是中间那段switch语句。

     switch (play.type) {   
       case "tragedy":      
        thisAmount = 40000;      
        if (perf.audience > 30) {
          thisAmount += 1000 * (perf.audience - 30);
        }
        break;
      case "comedy":
        thisAmount = 30000;
        if (perf.audience > 20) {
          thisAmount += 10000 + 500 * (perf.audience - 20);
        }
        thisAmount += 300 * perf.audience;
        break;
      default:
        throw new Error(`unknown type: ${play.type}`);
      }
    先将这块代码抽取成一个独立的函数,按它所干的事情给它命名,比如叫amountFor(performance)。 
    function amountFor(perf, play) {  let thisAmount = 0;  switch (play.type) {  case "tragedy":    thisAmount = 40000;    if (perf.audience > 30) {      thisAmount += 1000 * (perf.audience - 30);    }    break;  case "comedy":    thisAmount = 30000;    if (perf.audience > 20) {      thisAmount += 10000 + 500 * (perf.audience - 20);    }    thisAmount += 300 * perf.audience;    break;  default:      throw new Error(`unknown type: ${play.type}`);  }  return thisAmount;}

    做完这个改动后,我会马上编译并执行一遍测试,看看有无破坏了其他东西。无论每次重构多么简单,养成重构后即运行测试的习惯非常重要。小步修改,以及它带来的频繁反馈,正是防止混乱的关键。

    重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它
    做完上面的修改,测试是通过的,因此下一步我要把代码提交到本地的版本控制系统。我会使用诸如git或mercurial这样的版本控制系统,因为它们可以支持本地提交。每次成功的重构后我都会提交代码,如果待会不小心搞砸了,我便能轻松回滚到上一个可工作的状态。把代码推送(push)到远端仓库前,我会把零碎的修改压缩成一个更有意义的提(commit)。 
    傻瓜都能写出计算机可以理解的代码。唯有能写出人类容易理解的代码的,才是优秀的程序员。

    C.移除play变量

    当分解一个长函数时,我喜欢将play这样的变量移除掉,因为它们创建了很多具有局部作用域的临时变量,这会使提炼函数更加复杂。

     编译、测试、提交。完成变量内联后,我可以对amountFor函数应用改变函数声明(124),移除play参数。我会分两步走。首先在amountFor函数内部使用新提炼的函数。

     
    编译、测试、提交,最后将参数删除。

     

    然后再一次编译、测试、提交。

     这次重构可能在一些程序员心中敲响警钟:重构前,查找play变量的代码在每次循环中只执行了1次,而重构后却执行了3次。我会在后面探讨重构与性能之间的关系,但现在,我认为这次改动还不太可能对性能有严重影响,即便真的有所影响,后续再对一段结构良好的代码进行性能调优,也容易得多。

    D.提炼计算关总量积分的逻辑

     

     E.移除format变量

     正如我上面所指出的,临时变量往往会带来麻烦。它们只在对其进行处理的代码块中有用,因此临时变量实质上是鼓励你写长而复杂的函数。因此,下一步我要替换掉一些临时变量,而最简单的莫过于从format变量入手。这是典型的“将函数赋值给临时变量”的场景,我更愿意将其替换为一个明确声明的函数。

     

    format函数更名

     F.移除观众量积分总和

    把与更新volumeCredits变量相关的代码都集中到一起,有利于以查询取代临时变量(178)手法的施展。第一步同样是先对变量的计算过程应用提炼函数(106)手法。

     

     与复杂代码打交道时,细小的步子是快速前进的关键。

     

     现在代码结构已经好多了。顶层的statement函数现在只剩7行代码,而且它处理的都是与打印详单相关的逻辑。与计算相关的逻辑从主函数中被移走,改由一组函数来支持。每个单独的计算过程和详单的整体结构,都因此变得更易理解了。

    G. 分离到两个文件
    statement.js
     

    createStatementData.js

     

     

     代码行数由我开始重构时的44行增加到了70行(不算htmlStatement),这主要是将代码抽取到函数里带来的额外包装成本。虽然代码的行数增加了,但重构也带来了代码可读性的提高。额外的包装将混杂的逻辑分解成可辨别的部分,分离了详单的计算逻辑与样式。这种模块化使我更容易辨别代码的不同部分,了解它们的协作关系。虽说言以简为贵,但可演化的软件却以明确为贵。通过增强代码的模块化,我可以轻易地添加HTML版本的代码,而无须重复计算部分的逻辑。

    H.按类型重组计算过程

    支持更多类型的戏剧,以及支持它们各自的价格计算和观众量积分计算。

    创建演出计算器:

    enrichPerformance函数是关键所在,因为正是它用每场演出的数据来填充中转数据结构。目前它直接调用了计算价格和观众量积分的函数,我需要创建一个类,通过这个类来调用这些函数。由于这个类存放了与每场演出相关数据的计算函数,于是我把它称为演出计算器(performance calculator)。

    使用以工厂函数取代构造函数:

     createStatementData.js

     

      代码量仍然有所增加,因为我再次整理了代码结构。新结构带来的好处是,不同戏剧种类的计算各自集中到了一处地方。如果大多数修改都涉及特定类型的计算,像这样按类型进行分离就很有意义。当添加新剧种时,只需要添加一个子类,并在创建函数中返回它。

    三.第一章结语

    本章的重构有3个较为重要的节点,分别是:

    (1)将原函数分解成一组嵌套的函数

    (2)应用拆分阶段(154)分离计算逻辑与输出格式化逻辑

    (3)为计算器引入多态性来处理计算逻辑

    每一步都给代码添加了更多的结构,以便我能更好地表达代码的意图。

    好代码的检验标准就是人们是否能轻而易举地修改它。好代码应该直截了当:有人需要修改代码时,他们应能轻易找到修改点,应该能快速做出更改,而不易引入其他错误。

    一个健康的代码库能够最大限度地提升我们的生产力,支持我们更快、更低成本地为用户添加新特性。为了保持代码库的健康,就需要时刻留意现状与理想之间的差距,然后通过重构不断接近这个理想。

    a.好代码的检验标准就是人们是否能轻而易举地修改它。
    b.开展高效有序的重构,关键的心得是:
      小的步子可以更快前进,请保持代码永远处于可工作状态,小步修改累积起来也能大大改善系统的设计。
  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/coder-ydq/p/13970294.html
Copyright © 2011-2022 走看看