资讯详情

【读书笔记】从零开始了解重构(二)

目录

  • 一、前言
  • 二、通过例子了解重构
    • 2.1 拆解statement()函数
      • 2.1.1 提炼计算观众积分的逻辑
      • 2.1.2 移除观众积分总和
      • 2.1.3 移除总收费
      • 2.1.4 小结
    • 2.2 拆分计算阶段和格式阶段
      • 2.2.1 提炼渲染函数
    • 2.3 将代码分为多个文件
    • 2.4 过程按类型重组计算
      • 2.4.1 多态取代条件表达式
      • 2.4.2 构建戏剧基类
      • 2.4.3 构建喜剧子类
      • 2.4.4 构建悲剧子类
    • 2.5 使用多态计算器提供数据
  • 三、总结

一、前言

上篇笔记 【阅读笔记】从零开始了解重构(1) 本文主要介绍了什么是重建和重建的准备工作,以及一些拆分函数的方法,如提炼函数、查询取代临时变量和内联变量。本文继续以戏剧表演小组计算费用的例子了解重建。

二、通过例子了解重构

: 【阅读笔记】从零开始了解重构(1) 。

在上一篇笔记中,我们通过以下方法将其拆分 statement() 函数,让我们继续这个过程,提高代码的可读性和可扩展性。

  • 提炼函数;
  • 查询取代临时变量;
  • 内联变量技术。

2.1 拆解statement()函数

2.1.1 提炼计算观众积分的逻辑

现在 statement() 函数的内部实现如下:

str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
           double_t total_amount = 0;   uint32_t volume_credits = 0;   str_t* result = TKMEM_ZALLOC(str_t);   const char* customer = tk_object_get_prop_str(invoice, "[0].customer");   tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");   uint32_t perf_num = tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0);    str_init(result, 0);   str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", customer);    for (uint32_t i = 0; i < perf_num; i++) { 
        
    value_t v;
    uint32_t audience = 0;
    tk_object_t* perf = NULL;
    const char* play_name = NULL;
    const char* play_type = NULL;

    object_array_get(performances, i, &v);
    perf = value_object(&v);
    audience = tk_object_get_prop_uint32(perf, "audience", 0);

    play_name = tk_object_get_prop_str(play_for_perf(perf, plays), "name");
    play_type = tk_object_get_prop_str(play_for_perf(perf, plays), "type");

    /* 增加观众量积分 */
    volume_credits += tk_max(audience - 30, 0);
    /* 每增加10名戏剧观众可以获得额外积分 */
    if (tk_str_eq(play_type, "comedy")) { 
        
      volume_credits += floor(audience / 5);
    }

    total_amount += amount_for_perf(perf, plays);

    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", play_name,
                      amount_for_perf(perf, plays) / 100, audience);
  }

  /* 格式化输出总收费和获得的积分 */
  str_append_format(result, STATEMENT_SIZE, "Amount owed is $%.2f\n", total_amount / 100);
  str_append_format(result, STATEMENT_SIZE, "You earned %d credits\n", volume_credits);

  return result;

这里我们可以看到移除了 play 变量的好处,移除了一个局部作用域的变量,提炼观众量积分的计算逻辑又更简单一些。

这里我们仍然需要处理其他两个局部变量。 perfvolume_credits 同样可以作为参数传入,将它们的获取逻辑提炼到新函数中并修改新函数中的变量名,代码如下:

static tk_object_t* getPerf(tk_object_t* performance_list, uint32_t index) { 
        
  char str_i[4] = { 
        0};
  return tk_object_get_prop_object(performance_list, tk_itoa(str_i, sizeof(str_i), index));
}
static uint32_t volume_credits_perf(tk_object_t* a_performance, tk_object_t* plays) { 
        
  uint32_t result = 0;
  result += tk_max(tk_object_get_prop_uint32(a_performance, "audience", 0) - 30, 0);
  if (tk_str_eq(tk_object_get_prop_str(play_for_perf(a_performance, plays), "type"), "comedy")) { 
        
    result += floor(tk_object_get_prop_uint32(a_performance, "audience", 0) / 5);
  }
  return result;
}
str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  double_t total_amount = 0;
  uint32_t volume_credits = 0;
  str_t* result = TKMEM_ZALLOC(str_t);
  const char* customer = tk_object_get_prop_str(invoice, "[0].customer");
  tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");
  uint32_t perf_num = tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0);

  str_init(result, 0);
  str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", customer);

  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    const char* play_name = tk_object_get_prop_str(play_for_perf(getPerf(performances, i), plays), "name");

    volume_credits += volume_credits_perf(getPerf(performances, i), plays);
    total_amount += amount_for_perf(getPerf(performances, i), plays);

    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", play_name,
                      amount_for_perf(getPerf(performances, i), plays) / 100, audience);
  }

  /* 格式化输出总收费和获得的积分 */
  str_append_format(result, STATEMENT_SIZE, "Amount owed is $%.2f\n", total_amount / 100);
  str_append_format(result, STATEMENT_SIZE, "You earned %d credits\n", volume_credits);

  return result;
}

这里只展示一步到位的结果,在实际操作时,每编辑一个步骤都需要执行编译、测试、提交。

《重构》的作者指出,临时变量往往会带来麻烦,它们只在对其进行处理的代码块中有用,因此临时变量实质上是鼓励我们写又长又复杂的函数,代码往往变得晦涩难懂。因此,重构代码的过程中非常重视替换这些临时变量,有时甚至存在“将函数赋值给临时变量”的场景,比如书中 JS 代码的 format 变量,这里由于我是用 C 语言写的,所以没有这种情况。作者在重构时将他的 format 变量替换为一个明确声明的函数,并使用了优化该函数,最终结果如下:

function usd(aNumber) { 
        
  return new Intl.NumberFormat("en-US", { 
         style: "currency", currency: "USD", minimumFractionDigits: 2}).format(aNumber/100);
}

备注:在写代码的时候,好的命名非常重要。只有恰如其分地命名,才能彰显出将大函数分解成小函数的价值。有了好的名称,就不必通过阅读函数体来了解其行为。但要一次把名取好并不容易,这是一个不断优化的过程。通常我们需要花时间通读更多代码,才能发现最好的名称是什么。

2.1.2 移除观众量积分总和

接着我们来重构 volume_credits,处理这个变量更加微妙,因为它是在循环的迭代过程中累加得到的。第一步,就是应用拆分循环手法将 volume_credits 的累加过程分离出来,代码如下:

str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  double_t total_amount = 0;
  uint32_t volume_credits = 0;
  str_t* result = TKMEM_ZALLOC(str_t);
  const char* customer = tk_object_get_prop_str(invoice, "[0].customer");
  tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");
  uint32_t perf_num = tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0);

  str_init(result, 0);
  str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", customer);

  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    const char* play_name =tk_object_get_prop_str(play_for_perf(getPerf(performances, i), plays), "name");

    total_amount += amount_for_perf(getPerf(performances, i), plays);
    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", play_name,
                      amount_for_perf(getPerf(performances, i), plays) / 100, audience);
  }

  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    volume_credits += volume_credits_perf(getPerf(performances, i), plays);
  }

  /* 格式化输出总收费和获得的积分 */
  str_append_format(result, STATEMENT_SIZE, "Amount owed is $%.2f\n", total_amount / 100);
  str_append_format(result, STATEMENT_SIZE, "You earned %d credits\n", volume_credits);

  return result;
}

完成这一步,我就可以使用移动语句手法将变量声明挪动到紧邻循环的位置,代码如下:

str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  ...
  uint32_t volume_credits = 0;
  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    volume_credits += volume_credits_perf(getPerf(performances, i), plays);
  }
  ...
  return result;
}

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

static uint32_t total_volume_credits(tk_object_t* performance_list, tk_object_t* plays) { 
        
  uint32_t result = 0;
  for (uint32_t i = 0; i < tk_object_get_prop_uint32(performance_list, TK_OBJECT_PROP_SIZE, 0); i++) { 
        
    result += volume_credits_perf(getPerf(performance_list, i), plays);
  }
  return result;
}
str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  double_t total_amount = 0;
  str_t* result = TKMEM_ZALLOC(str_t);
  const char* customer = tk_object_get_prop_str(invoice, "[0].customer");
  tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");
  uint32_t perf_num = tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0);

  str_init(result, 0);
  str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", customer);

  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    const char* play_name = tk_object_get_prop_str(play_for_perf(getPerf(performances, i), plays), "name");

    total_amount += amount_for_perf(getPerf(performances, i), plays);
    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", play_name,
                      amount_for_perf(getPerf(performances, i), plays) / 100, audience);
  }

  uint32_t volume_credits = total_volume_credits(performances, plays);

  /* 格式化输出总收费和获得的积分 */
  str_append_format(result, STATEMENT_SIZE, "Amount owed is $%.2f\n", total_amount / 100);
  str_append_format(result, STATEMENT_SIZE, "You earned %d credits\n", volume_credits);

  return result;
}

完成函数提炼后,再应用内联变量手法内联 total_volume_credits 函数。

str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  double_t total_amount = 0;
  str_t* result = TKMEM_ZALLOC(str_t);
  const char* customer = tk_object_get_prop_str(invoice, "[0].customer");
  tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");
  uint32_t perf_num = tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0);

  str_init(result, 0);
  str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", customer);

  for (uint32_t i = 0; i < perf_num; i++) { 
        
    uint32_t audience = tk_object_get_prop_uint32(getPerf(performances, i), "audience", 0);
    const char* play_name = tk_object_get_prop_str(play_for_perf(getPerf(performances, i), plays), "name");

    total_amount += amount_for_perf(getPerf(performances, i), plays);
    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", play_name,
                      amount_for_perf(getPerf(performances, i), plays) / 100, audience);
  }

  /* 格式化输出总收费和获得的积分 */
  str_append_format(result, STATEMENT_SIZE, "Amount owed is $%.2f\n", total_amount / 100);
  str_append_format(result, STATEMENT_SIZE, "You earned %d credits\n", total_volume_credits(performances, plays));

  return result;
}

2.1.3 移除总收费

我们再以同样的手法移除 total_amount 变量,代码如下:

static double_t total_amount(tk_object_t* performance_list, tk_object_t* plays) { 
        
  double_t result = 0;
  for (uint32_t i = 0; i < tk_object_get_prop_uint32(performance_list, TK_OBJECT_PROP_SIZE, 0); i++) { 
        
    result += amount_for_perf(getPerf(performance_list, i), plays);
  }
  return result;
}
str_t* statement(tk_object_t* invoice, tk_object_t* plays) { 
        
  str_t* result = TKMEM_ZALLOC(str_t);
  tk_object_t* performances = tk_object_get_prop_object(invoice, "[0].performances");

  str_init(result, 0);
  str_append_format(result, STATEMENT_SIZE, "Statement for %s\n", tk_object_get_prop_str(invoice, "[0].customer"));

  for (uint32_t i = 0; i < tk_object_get_prop_uint32(performances, TK_OBJECT_PROP_SIZE, 0); i++) { 
        
    /* 格式化输出每个剧目的收费 */
    str_append_format(result, STATEMENT_SIZE, " %s: $%0.2f (%d seats)\n", 
                      tk_object_get_prop_str(play_for_perf(getPerf(performances, i), plays), "name"),
                      amount_for_perf(getPerf(performances, i), plays) / 100, 
                  

标签: 电流传感器tkc200bs

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台