sprint3:
(1)宽的取值范围为(0,100],高的取值范围为(0,75)。当值不在这个范围内,则认为矩形非法;
(2)非法的矩形的面积和周长为0;
按照需求设计出测试用例:
TEST("should calc rectangle width & height range succ")
{
Rectangle rec(100 , 75);
ASSERT_THAT(rec.getWidth(), eq(100));
ASSERT_THAT(rec.getHeight(), eq(0));
ASSERT_THAT(rec.area(), eq(0));
ASSERT_THAT(rec.perimeter(), eq(0));
}
按照需求快速实现:
namespace
{
const double MIN_WIDTH = 0;
const double MAX_WIDTH = 100;
const double MIN_HEIGHT = 0;
const double MAX_HEIGHT = 75;
}
Rectangle::Rectangle(double width, double height)
{
double m_width_result = processPrecision(width, 2, 0);
if(m_width_result > MIN_WIDTH and m_width_result <= MAX_WIDTH) m_width = m_width_result;
else m_width = 0;
double m_height_result = processPrecision(height, 2, 0);
if(m_height_result > MIN_HEIGHT and m_height_result < MAX_HEIGHT) m_height = m_height_result;
else m_height = 0;
}
double Rectangle::area() const
{
return processPrecision(getHeight() * getWidth(), 2, 0.5);
}
double Rectangle::perimeter() const
{
if(getHeight() == 0 or getWidth() == 0)
return 0;
return 2 * (getHeight() + getWidth());
}
refactor:去除重复
从上述代码中可以看出,计算长方形长、宽的逻辑基本一致:
如果长(宽)在取值范围内,那么则取对应的值为最终结果,否在最终结果为0。
唯一存在差异的地方是长方形的长、宽对合法取值范围的定义是不一样的。
我们这里可以提取出一个inRange的概念,这是个相对稳定的概念,而getResult依赖于这个稳定的概念,这样下来代码是稳定的。Width
和Height
对于inRange这个概念的实现都有自己的定义。
namespace
{
struct Width
{
Width(double value):value(value){}
bool inRange() const
{
return value > MIN_WIDTH and value <= MAX_WIDTH;
}
private:
double value;
};
struct Height
{
Height(double value):value(value){}
bool inRange() const
{
return value > MIN_HEIGHT and value < MAX_HEIGHT;
}
private:
double value;
};
template<typename Value>
double getResult(double value)
{
// 对Value类的接口存在隐式约束:必须实现inRange,否则编译失败。
return Value(value).inRange() ? processPrecision(value, 2, 0): 0;
}
}
Rectangle::Rectangle(double width, double height)
{
m_width = getResult<Width>(width);
m_height = getResult<Height>(height);
}
refactor:分离精度算法
精度算法是比较常用的,如果将其分离出去,能够提高函数的可重用性:
//util.h
#ifndef H44DCD35C_F5D5_4801_8E16_AF7DB6641C5A
#define H44DCD35C_F5D5_4801_8E16_AF7DB6641C5A
double floor(double value, const unsigned int precision);
double round(double value, const unsigned int precison);
#endif /* H44DCD35C_F5D5_4801_8E16_AF7DB6641C5A */
//util.cpp
#include <cmath>
#include "util.h"
namespace
{
double processPrecision(double value, const unsigned int precision, double compensation)
{
double factor = ::pow(10, precision);
return ::floor(value * factor + compensation)/factor;
}
}
double floor(double value, const unsigned int precision)
{
return processPrecision(value, precision, 0);
}
double round(double value, const unsigned int precision)
{
return processPrecision(value, precision, 0.5);
}
// Rectangle.cpp 对应调用的地方修改为:
template<typename Value>
double getResult(double value)
{
return Value(value).inRange() ? floor(value, 2): 0;
}
double Rectangle::area() const
{
return round(getHeight() * getWidth(), 2);
}
这里除了对精度算法进行了分离,还对processPrecision(value, precision, 0)
及processPrecision(value, precision, 0.5)
进行了封装,这样做的好处有:
-
floor
和round
较好地表达了向下取整、四舍五入的语义。 - 降低客户端调用的心智包袱。
杜绝潜规则:
在上节,我们将非法的长度、宽度的值设置为0,需求里也没有具体提及到可以这样处理。所以我们不应该投机取巧(不要将非法长度设为0),把偶然当必然(非本质的关系不稳定)。我们做如下修改:
namespace
{
struct Width
{
Width(double value):value(value){}
bool isVaild() const
{
return value > MIN_WIDTH and value <= MAX_WIDTH;
}
private:
double value;
};
struct Height
{
Height(double value):value(value){}
bool isVaild() const
{
return value > MIN_HEIGHT and value < MAX_HEIGHT;
}
private:
double value;
};
}
Rectangle::Rectangle(double width, double height):
m_width(floor(width, 2)),m_height(floor(height, 2)){}
double Rectangle::area() const
{
if(not isVaild())
return 0;
return round(getHeight() * getWidth(), 2);
}
double Rectangle::perimeter() const
{
if(not isVaild())
return 0;
return 2 * (getHeight() + getWidth());
}
bool Rectangle::isVaild() const
{
return Width(m_width).isVaild() and Height(m_height).isVaild();
}
网友评论