Kata04:重构开始

Kata04地址

第四个Kata,名字叫“Data Munging”,一看Data大家就知道了,这次是要处理数据。

任务分三步:

  • 处理weather.dat,找到温差最小的一天,第二列和第三列分别对应每天的最高温度和最低温度
  • 处理football.dat,找到进球数和丢球数之差最小的一只球队,“-”符号左侧是进球数,右侧是丢球数
  • 重构上面两步的代码

输入数据

我知道你们肯定懒得去点数据链接,所以我直接把数据贴在这里

weather.dat:

  Dy MxT   MnT   AvT   HDDay  AvDP 1HrP TPcpn WxType PDir AvSp Dir MxS SkyC MxR MnR AvSLP

   1  88    59    74          53.8       0.00 F       280  9.6 270  17  1.6  93 23 1004.5
   2  79    63    71          46.5       0.00         330  8.7 340  23  3.3  70 28 1004.5
   3  77    55    66          39.6       0.00         350  5.0 350   9  2.8  59 24 1016.8
   4  77    59    68          51.1       0.00         110  9.1 130  12  8.6  62 40 1021.1
   5  90    66    78          68.3       0.00 TFH     220  8.3 260  12  6.9  84 55 1014.4
   6  81    61    71          63.7       0.00 RFH     030  6.2 030  13  9.7  93 60 1012.7
   7  73    57    65          53.0       0.00 RF      050  9.5 050  17  5.3  90 48 1021.8
   8  75    54    65          50.0       0.00 FH      160  4.2 150  10  2.6  93 41 1026.3
   9  86    32*   59       6  61.5       0.00         240  7.6 220  12  6.0  78 46 1018.6
  10  84    64    74          57.5       0.00 F       210  6.6 050   9  3.4  84 40 1019.0
  11  91    59    75          66.3       0.00 H       250  7.1 230  12  2.5  93 45 1012.6
  12  88    73    81          68.7       0.00 RTH     250  8.1 270  21  7.9  94 51 1007.0
  13  70    59    65          55.0       0.00 H       150  3.0 150   8 10.0  83 59 1012.6
  14  61    59    60       5  55.9       0.00 RF      060  6.7 080   9 10.0  93 87 1008.6
  15  64    55    60       5  54.9       0.00 F       040  4.3 200   7  9.6  96 70 1006.1
  16  79    59    69          56.7       0.00 F       250  7.6 240  21  7.8  87 44 1007.0
  17  81    57    69          51.7       0.00 T       260  9.1 270  29* 5.2  90 34 1012.5
  18  82    52    67          52.6       0.00         230  4.0 190  12  5.0  93 34 1021.3
  19  81    61    71          58.9       0.00 H       250  5.2 230  12  5.3  87 44 1028.5
  20  84    57    71          58.9       0.00 FH      150  6.3 160  13  3.6  90 43 1032.5
  21  86    59    73          57.7       0.00 F       240  6.1 250  12  1.0  87 35 1030.7
  22  90    64    77          61.1       0.00 H       250  6.4 230   9  0.2  78 38 1026.4
  23  90    68    79          63.1       0.00 H       240  8.3 230  12  0.2  68 42 1021.3
  24  90    77    84          67.5       0.00 H       350  8.5 010  14  6.9  74 48 1018.2
  25  90    72    81          61.3       0.00         190  4.9 230   9  5.6  81 29 1019.6
  26  97*   64    81          70.4       0.00 H       050  5.1 200  12  4.0 107 45 1014.9
  27  91    72    82          69.7       0.00 RTH     250 12.1 230  17  7.1  90 47 1009.0
  28  84    68    76          65.6       0.00 RTFH    280  7.6 340  16  7.0 100 51 1011.0
  29  88    66    77          59.7       0.00         040  5.4 020   9  5.3  84 33 1020.6
  30  90    45    68          63.6       0.00 H       240  6.0 220  17  4.8 200 41 1022.7
  mo  82.9  60.5  71.7    16  58.8       0.00              6.9          5.3

football.dat:

       Team            P     W    L   D    F      A     Pts
    1. Arsenal         38    26   9   3    79  -  36    87
    2. Liverpool       38    24   8   6    67  -  30    80
    3. Manchester_U    38    24   5   9    87  -  45    77
    4. Newcastle       38    21   8   9    74  -  52    71
    5. Leeds           38    18  12   8    53  -  37    66
    6. Chelsea         38    17  13   8    66  -  38    64
    7. West_Ham        38    15   8  15    48  -  57    53
    8. Aston_Villa     38    12  14  12    46  -  47    50
    9. Tottenham       38    14   8  16    49  -  53    50
   10. Blackburn       38    12  10  16    55  -  51    46
   11. Southampton     38    12   9  17    46  -  54    45
   12. Middlesbrough   38    12   9  17    35  -  47    45
   13. Fulham          38    10  14  14    36  -  44    44
   14. Charlton        38    10  14  14    38  -  49    44
   15. Everton         38    11  10  17    45  -  57    43
   16. Bolton          38     9  13  16    44  -  62    40
   17. Sunderland      38    10  10  18    29  -  51    40
   -------------------------------------------------------
   18. Ipswich         38     9   9  20    41  -  64    36
   19. Derby           38     8   6  24    33  -  63    30
   20. Leicester       38     5  13  20    30  -  64    28

可以看到,这个题目其实很简单,只要掌握基本的读文件和split功能很轻易就能实现。

初步实现

按照题目要求,我们先完成了前两步,我用的是Python,小伙伴用的是Nodejs,上代码。

Python版:

def answer_1():
    min_1 = None
    with open("weather.dat") as f:
        for line in f.readlines():
            temp = line.strip().split()
            if line[0] == "mo":
                continue
            try:
                temp_min = abs(int(temp[1].replace("*", "")) - int(temp[2].replace("*", "")))
            except:
                continue
            if not min_1:
                min_1 = temp_min
            elif temp_min < min_1:
                min_1 = temp_min

    print min_1

def answer_2():
    min_2 = None
    with open("football.dat") as f:
        for line in f.readlines():
            temp = line.strip().split()
            if len(temp) != 10:
                continue
            temp_min = abs(int(temp[6]) - int(temp[8]))
            if not min_2:
                min_2 = temp_min
            elif temp_min < min_2:
                min_2 = temp_min
    print min_2

answer_1()
answer_2()

Nodejs版:

// 问题1
var  fs = require('fs');
fs.readFile("weather.dat", "utf8", function(err, data) {
    var i;
    var mSpread = 100;
    var spread;
    var result;
    if (err) throw err;
    lines = data.trim().split(/\n/);
    lines = lines.splice(2, 30);
    lines = lines.map(function(line) {
        line = line.trim().split(/\s+/);
        return line;
    });
    for (var i = 0; i < 30; i++) {
        spread = lines[i][1] - lines[i][2];
        console.log(spread);
        if (spread < mSpread) {
            mSpread = spread;
            result = lines[i][0];
        }
    }
    console.log("最小温差是第" + result + "天。" + "最小温差是" + mSpread);
});
// 问题2
var  fs = require('fs');
fs.readFile("football.dat", "utf8", function(err, data) {
    var i;
    var l;
    var mSpread = 100;
    var spread;
    var result;
    if (err) throw err;
    lines = data.trim().split(/\n/);
    lines = lines.splice(1, 20);
    lines = lines.map(function(line) {
        line = line.trim().split(/\s+/);
        return line;
    });
    for (i = 0, l = lines.length; i < l; i++) {
        spread = Math.abs(lines[i][6] - lines[i][8]);
        console.log(lines[i]);
        if (spread < mSpread) {
            mSpread = spread;
            result = lines[i][0];
        }
    }
    console.log("第" + result + "队。")
});
nsole.log("第" + result + "队。")
});

和我们想的一样,代码非常简单。我对Python很熟悉,两个小程序基本上10分钟搞定,小伙伴Nodejs连查带写也是很快就完成了。下面就是重构了。

重构

这个练习的目的就是重构。

我觉得重构大体上有两个层面吧,一个是具体的代码层面,一个是思想层面。前者落实的时候主要是一些方法,比如提取公共函数、修改变量名可读性、优化内存使用等等,后者主要体现在编程模式上。

编程模式的话我之前看过《大话编程模式》,很不错的一本书,推荐给大家。

纸上得来终觉浅,尤其是模式这种东西,自己不写个十遍八遍的很难体会到其中的奥妙,也就更难发现模式存在的问题。世界上没有完美的模式,学习模式的终极目标其实就是抛弃模式,不断用新模式替换旧模式,不断让模式更专注于某个领域。

回到这个Kata。

由于代码太简单了,用模式来改写就意义不大了,所以我们基本上是采用了代码层面的重构。不过即使是这么简单的代码,我和小伙伴的重构结果也是差异极大。

Python重构:

def common_answer(filename, filterfunc, generate, compare):
    result = None
    with open(filename) as f:
        for line in f.readlines():
            temp = line.strip().split()
            if filterfunc(temp):
                continue
            temp = generate(temp)
            if not result:
                result = temp
            elif compare(result, temp):
                result = temp

    return result

print common_answer("weather.dat", lambda b: not b or b[0] in ["Dy", "mo"], lambda b: abs(int(b[1].replace("*", "")) - int(b[2].replace("*", ""))), lambda a, b: a > b)
print common_answer("football.dat", lambda b: "." not in b[0], lambda b: abs(int(b[6]) - int(b[8])), lambda a, b: a > b)

Nodejs重构出了一个新文件common.js

function parseTable(tableData, startline, lineNum) {
    var lines;
    lines = tableData.trim().split(/\n/);
    lines = lines.splice(startline, lineNum);
    lines = lines.map(function(line) {
        line = line.trim().split(/\s+/);
        return line;
    });
    return lines;
}

function findMinSpread(data, col1, col2) {
    var i;
    var l;
    var mSpread = 1000;
    var result;
    for (i = 0, l = data.length; i < l; i++) {
        spread = Math.abs(data[i][col1] - data[i][col2]);
        if (spread < mSpread) {
            mSpread = spread;
            result = data[i][0];
        }
    }
    return result;
}
exports.parseTable = parseTable;
exports.findMinSpread = findMinSpread;

具体解决问题的代码如下:

// 问题1
var  fs = require('fs');
var myCommon = require('./common.js');
fs.readFile("weather.dat", "utf8", function(err, data) {
    var lines;
    if (err) throw err;
    lines = myCommon.parseTable(data, 1, 20);
    console.log("最小温差是第" + myCommon.findMinSpread(lines, 1, 2) + "天。");
});
// 问题2
var  fs = require('fs');
var myCommon = require('./common.js');
fs.readFile("football.dat", "utf8", function(err, data) {
    var lines;
    if (err) throw err;
    lines = myCommon.parseTable(data, 1, 20);
    console.log("第" + myCommon.findMinSpread(lines, 6, 8) + "队。")
});

可以看出,Python的重构比较偏向整体架构调整,将处理逻辑分成多步,每一步都由传入的函数处理,相对来说偏向函数式编程一点。而Nodejs的重构比较偏向提取公共函数,处理流程的重心仍然放在代码本身。

我觉得两种方法本质上没有孰优孰劣,我重构Python的时候更多考虑的是通用性,整体的架构更像一个框架,使用的时候传入各部分的处理函数,框架负责运行。小伙伴重构Nodejs的时候更多考虑的是易用性,整体的架构更像一个工具库,使用的时候直接调用公共函数,由用户代码负责运行。

我的思路是大一统,小伙伴的思路是各司其职。相对来说,我的思路会很大地限制使用范围,但是在范围内使用是非常方便的,小伙伴的思路可以应用在非常大的范围中,但是给用户带来的便利是有限的。

一句话,区别就是深和广,具体选择哪个还是要看使用场景以及个人习惯。

反思重构

尽管这个Kata很小,并没有用各种高端的设计模式,但是仍然给我们带来很多思考。

首先,重构是否有必要。

代码重构完,从逻辑上来说确实更加清晰,代码量更少,但是重构本质是抽象,对于阅读代码的人来说实际上会增加一定的理解难度,大家从Python例子就可以看出,重构之前读一遍代码就能读懂,重构之后需要不断比对参数和函数内容才能理解作用,并没有那么直观。

不过重构还是需要的,不仅可以使代码逻辑更加清晰,代码量更少,还可以增加代码的可维护性,可以说重构带来的副作用主要就体现在代码的理解难度上,而这恰恰就是程序员的职责所在。

其次,重构思路不一样。

上文已经详细介绍了重构思路的区别,可以看到,即使是这样一个简单的例子都可以有完全不同的重构方法,那大项目就更不用说了。如何选择重构思路其实和如何选择编程语言是一样的,关键是合适不合适,而不是思路优越不优越或者语言牛逼不牛逼。

抓住本质,不要浮在表面,否则迟早有一天你会被冲走。

你可能感兴趣的:(Kata04:重构开始)