4.字符串计算

未匹配的标注
  • 本系列文章为laracasts.com 的系列视频教程——Code Katas in PHP 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频,支持正版
  • Kata 是一个简短,可重复的编程挑战,可以帮助我们进行快速地编程练习。
  • 开发模型仍旧是 TDD(测试驱动开发),视频中使用的是 phpspec 进行开发,笔记中使用了 Laravel 应用,因此代码有不同。

本节说明

  • 对应第 4 小节:String Calculator

本节内容

这一节我们的练习是字符串计算。首先我们定义几条规则:

  1. StringCalculator::add('')的结果是 0;
  2. StringCalculator::add('1,2,3')的结果是 6;
  3. 参数不能包含负数;
  4. StringCalculator::add('1,2,3,1000')的结果是 6,即过滤超出范围的数字;
  5. 允许用换行符\n进行分割;

开始我们的练习:

$ php artisan make:test StringCalculatorTest --unit

然后开始第一个测试:

tests\Unit\StringCalculatorTest.php

<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\StringCalculator;

class StringCalculatorTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        $this->stringCalculator = new StringCalculator();
    }

    /** @test */
    public function it_translate_empty_string_into_zero()
    {
        $this->assertEquals(0,$this->stringCalculator->add(''));
    }
}

运行测试:
file
我们来使测试通过:

app\StringCalculator.php

<?php

namespace App;

class StringCalculator 
{
    public function add()
    {
        return 0;
    }
}

运行测试:
file
向前推进:

    .
    .
    /** @test */
    public function it_finds_the_sum_of_one_number()
    {
        $this->assertEquals(5,$this->stringCalculator->add('5'));
    }
}

运行测试:
file
修改代码:

<?php

namespace App;

class StringCalculator 
{
    public function add($number)
    {
        return (int) $number;
    }
}

再次测试:
file
继续推进,计算两个数的和:

tests\Unit\StringCalculatorTest.php

    .
    .
    /** @test */
    public function it_finds_the_sum_of_one_number()
    {
        $this->assertEquals(5,$this->stringCalculator->add('5'));
    }

    /** @test */
    public function it_finds_the_sum_of_two_numbers()
    {
        $this->assertEquals(3,$this->stringCalculator->add('1,2'));
    }
}

运行测试会失败:
file
我们来让测试通过:

<?php

namespace App;

class StringCalculator 
{
    public function add($numbers)
    {
        $numbers = explode(',',$numbers);

        return array_sum($numbers);
    }
}

运行测试:
file
继续推进:

    .
    .
    /** @test */
    public function it_finds_the_sum_of_any_numbers()
    {
        $this->assertEquals(15,$this->stringCalculator->add('1,2,3,4,5'));
    }
}

运行测试:
file
继续推进,测试参数包含负数时抛出异常:

    .
    .
    /** @test */
    public function it_disallows_negative_numbers()
    {
        $this->expectException('InvalidArgumentException');

        $this->stringCalculator->add('1,2,-3,4,5');
    }
}

运行测试:
file
查看我们的代码会发现我们没有地方来进行判断,所以我们要先进行重构。正如我们之前说过的,进行代码重构的时候,必须要保证你的测试全部通过。所以我们注释掉新添加的测试,然后进行如下重构:

<?php

namespace App;

class StringCalculator 
{
    public function add($numbers)
    {
        $numbers = explode(',',$numbers);

        $solution = 0;

        foreach($numbers as $number){
            $solution += (int)$number;
        }

        return $solution;
    }
}

运行测试:
file
然后我们取消注释,在循环中进行判断:

<?php

namespace App;

use InvalidArgumentException;

class StringCalculator 
{
    public function add($numbers)
    {
        $numbers = explode(',',$numbers);

        $solution = 0;

        foreach($numbers as $number){
            if($number < 0) throw new InvalidArgumentException;

            $solution += (int)$number;
        }

        return $solution;
    }
}

再次运行测试:
file
继续推进:

    .
    .
    /** @test */
    public function it_ignores_any_number_that_is_one_thousand_or_greater()
    {
        $this->assertEquals(3,$this->stringCalculator->add('1,2,1000'));
    }
}

运行测试:
file
让测试通过:

    .
    .
    public function add($numbers)
    {
        $numbers = explode(',',$numbers);

        $solution = 0;

        foreach($numbers as $number){
            if($number < 0) throw new InvalidArgumentException;
            if($number >= 1000) continue;

            $solution += (int)$number;
        }

        return $solution;
    }
}

运行测试:
file
继续推进,允许使用换行符进行分割:

    .
    .
    /** @test */
    public function it_allows_new_line_delimiters()
    {
        $this->assertEquals(6,$this->stringCalculator->add('1,2\n3'));
    }
}

运行测试:
file
我们加入了一个新的分割符合:\n,所以explode()函数不再适用,我们改用正则表达式匹配分割。别忘了,我们在做的事情是重构,所以我们需要保证我们的测试是全部通过的。先注释掉新添加的测试然后再进行修改:

    .
    .
    $numbers = preg_split('/\s*,\s*/',$numbers);
    .
    .

注:我们同时去除了,前后的空格

然后我们来运行之前的全部测试:
file
现在取消掉注释,再让新的测试通过:

    .
    .
    $numbers = preg_split('/\s*(,|\\\n)\s*/',$numbers);
    .
    .

运行测试:
file
好了,现在我们的开发告一段落,但是,我们仍然可以进行重构工作。因为我们的测试是全部通过的,所以我们有相当大地自信来进行重构。首先,当参数包含负数时,我们期望得到具体的信息,如下:

    .
    .
    /** @test */
    public function it_disallows_negative_numbers()
    {
        $this->expectException('InvalidArgumentException');
        $this->expectExceptionMessage('Invalid number provided:-3');

        $this->stringCalculator->add('1,2,-3,4,5');
    }
    .
    .

然后修改代码给出异常消息:

    .
    .
    public function add($numbers)
    {
        $numbers = preg_split('/\s*(,|\\\n)\s*/',$numbers);
        $solution = 0;

        foreach($numbers as $number){
            if($number < 0) throw new InvalidArgumentException("Invalid number provided:{$number}");
            if($number >= 1000) continue;

            $solution += (int)$number;
        }

        return $solution;
    }
    .
    .

运行测试:
file
接着我们对业务代码进行重构:

<?php

namespace App;

use InvalidArgumentException;

class StringCalculator 
{
    const MAX_NUMBER_ALLOWED = 1000;

    public function add($numbers)
    {
        $numbers = $this->parseNumbers($numbers);

        $solution = 0;

        foreach($numbers as $number){
            $this->guardAgainstInvalidNumber($number);

            if($number >= self::MAX_NUMBER_ALLOWED) continue;

            $solution += $number;
        }

        return $solution;
    }

    private function guardAgainstInvalidNumber($number)
    {
        if($number < 0) throw new InvalidArgumentException("Invalid number provided:{$number}");
    }

    private function parseNumbers($numbers)
    {
        return array_map('intval',preg_split('/\s*(,|\\\n)\s*/',$numbers));
    }
}

我们对三个地方进行了重构:

  1. 字符串分割为数组的逻辑我们抽取到了parseNumbers()方法中,并且转换成int
  2. 参数为负数的验证逻辑我们抽取到了guardAgainstInvalidNumber()方法中;
  3. 规定的最大值我们定义了常量MAX_NUMBER_ALLOWED来进行维护;

重构并不复杂,但是可读性大大增强了,不是吗?

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~