Skip to content

第十章 抽取不相关的子问题

TIP

所谓工程学就是把大问题拆分为小问题再把这些问题的解决方案放回到一起。把这条原则应用于代码会使代码更健壮并且更容易读。

本章的建议是“积极地发现并抽取出不想关的子逻辑”

介绍性的例子

下面这段代码的目标是“找到举例给定点最佳的位置”

js
var findClosestLoction = function (lat, lng, array){
  var cloest;
  var cloest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i += 1) {
    var lat1_rad = radians(lat);
    var lng1_rad = radians(lng);
    var lat2_rad = radians(array[i].latitude);
    var lng2_rad = radians(array[i].longtude);
    
    var dist = Math.acos(
      Math.cos(lng2_rad - lng1_rad) *
      Math.cos(lat1_rad) * Math.cos(lat2_rad) + 
      Math.sin(lat1_rad) * Math.sin(lat2_rad)
    )

    if (dist < closest_dist){
      closest_dist = dist;
      cloest= array[i];
    }
  }
  return cloest;
}

循环中大部分代码都旨在解决一个不相关的子问题:“计算两个经纬坐标点之间的球面距离”,我们将其抽取出来。

js
var spherical_distance = function (lat1, lng1, lat2, lng2) {
  var lat1_rad = radians(lat1);
  var lng2_rad = radians(lng2);
  var lat1_rad = radians(lat2);
  var lng2_rad = radians(lng2);
  return Math.acos(
      Math.cos(lng2_rad - lng1_rad) *
      Math.cos(lat1_rad) * Math.cos(lat2_rad) + 
      Math.sin(lat1_rad) * Math.sin(lat2_rad)
    )
}

现在代码变成了:

js
var findClosestLoction = function (lat, lng, array){
  var cloest;
  var cloest_dist = Number.MAX_VALUE;
  for (var i = 0; i < array.length; i += 1) {
    var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longtude)
    if (dist < closest_dist){
      closest_dist = dist;
      cloest= array[i];
    }
  }
  return cloest;
}

这段代码的可读性好很多,因为读者可以关注高层次目标,而不必因为复杂的几何公式分型。

纯工具代码

有一组核心任务大多数程序都会做,例如操作字符串、使用哈希表以及读写文件等。

通常这些“基本”工具是由编程语言内置的库来实现的。例如想要读取文件的内容,在 python 中可以用 open('filename').read()。

但有时我们也要自己来填补这些空白。例如在 c++ 中就没有简单的方法来读取整个文件,我们不可避免的要写这样的代码:

cpp
ifstream file(file_name);

file.seekg(0, ios:end);
const int file_size = file.tellg();
char* flle_buf = new char [file_size];

file.seekg(0, ios:beg)
file.read(file_buf, file_size);
file.close();

这是一个不相关子问题的经典例子,应该把它抽取到一个新的函数中,比如 ReadFileToString();

其他多用途代码

当调试 JavaScript 代码时,程序员经常使用 alert 来弹出消消息框,把一些信息展示给他们看,这是 Web 版本的 printf 调试。

创建大量通用代码

ReadFileToString 和 formatPretty 这两个函数时不相关子问题的好例子。它们是如此的基本而广泛适用,所以很可能在多个项目中重用。

项目专有的功能

简化已有接口

按需重塑接口

过犹不及

总结

大部分代码都是一般代码。通过建立一大组库和辅助函数来解决一般问题,剩下的只是让你的程序与众不同的核心程序。

这个技巧有帮助的原因是它使程序员关注小而定义良好的问题,这些问题已经同项目的其他部分脱离。其结果是,对于这些子问题的解决方案倾向于更加完善的正确。你也可以在以后重用它们。