编码基本上每位程序员都是会的但由于每位程序员的习惯都有所不同从而产生了各式各样的编码。怎么样的代码是最好的?这好像就没有一个很好的说法从我自己几年的开发经验觉得好的代码应该具有以下几点特性:
- 易读:命名、函数内上下文件流程(达到基本上不用注释都知道这是的是什么)
- 易扩展:有新的需求时可以不改动(少量改动)以前的代码就可以完成
- 易维护:用少量的时间就可以完成维护过程(这与前两个有很大的关系)
现在一堆区间数组把相交的区间数组合并起来得出新的区间数组效果如下:
原始区间数组:{3,5}{1,5}{5, 9}{6, 8}{-9, 5}{40, 90}{5,7}{-1,1}
合并区间数组:{-9, 9}{40, 90}
|
需求分析
分析主要的功能是把这走这一需求时所产生的情况合理的整理出来。
合并时两个区间可能会产生的关系(与第二区间相等、
不相交在第二区间前、
不相交
在第二区间后
、
与第二区间的头相交
、
与第二区间的尾相交
、
包含第二区间
、
被第二区间包含),然后把相关联的区间合并在一起。
简单设计
public
struct
Interval
<T> :
IComparable
,
IComparable
<
Interval
<T>>
where
T :
IComparable
| ||||||||||||||
public
class
IntervalNode<T>
where T :
IComparable
| ||||||||||||||
区间相交情况
[
Flags
]
public
enum
JointIntervalKind
{
Equal,
///
与第二区间
相等
After,
///
不相交在第二区间后
Before,
///
不相交在第二区间前
Begin,
///
与第二区间的头相交
End,
///
与第二区间的尾相交
Include,
///
包含第二区间
UnInclude,
///
被第二区间包含
}
|
考虑扩展重用
现在很多程序员往往都没有按(软件工程)的方法,有什么功能就写什么代码很多基本上的考虑都没有。以下方法函数是在集合中取出现在集合中与指定区间相交的元素判断方法函数:
一开始直接写出来 | 整理得出什么Flag123这样子的命名还真的要名过了段时间后就怕不记得了吧。整理时果断理理。 |
public
bool
IsJoint(T begin, T end)
{
if
(begin.CompareTo(end) > 0)
throw
new
Exception
(
"开始数值不能大于结束数值"
);
bool
flag1 =
this
.IsBetween(begin), flag
2 =
this
.IsBetween(end);
if
(flag
1 && fllag
2)
return
true
;
if
(falg1)
return
true
;
if
(flag
2
)
return
true
;
falg1
=
this
.Begin.IsBetween(begin, end);
falg2
=
this
.End.IsBetween(begin, end);
if
(falg1
&&flag
2
)
return
true
;
if
(falg1
)
return
true
;
if
(flag
2
)
return
true
;
return
false
;
}
|
public
JointIntervalKind GetJoint(T begin, T end)
{
if (begin.CompareTo(end) > 0)
throw
new
ArgumentOutOfRangeException(
"begin" ,
"开始数值不能大于结束数值" );
if (
this .Begin.CompareTo(begin) == 0 &&
this.End.CompareTo(end) == 0)
return
JointIntervalKind .Equal;
bool flagIncludeTBegin =
this .IsBetween(begin);
bool flagIncludeTEnd =
this .IsBetween(end);
bool flagBeginInT =
this .Begin.IsBetween(begin, end);
bool flagEndInT =
this .End.IsBetween(begin, end);
if (flagIncludeTBegin &&flagIncludeTEnd )
return
JointIntervalKind .Include;
if (flagBeginInT && flagEndInT)
return
JointIntervalKind .UnInclude;
if (flagIncludeTBegin ||flagBeginInT )
return
JointIntervalKind .Begin;
if (flagIncludeTEnd ||flagEndInT )
return
JointIntervalKind.End;
return
this .Begin.CompareTo(begin) > 0 ?
JointIntervalKind.After :
JointIntervalKind .Before;
}
|
上两段代码中的方法所表达的意思都是一样的。往往很多程序员都是以第一种方法处理上面的业务逻辑反正以上面要知道是与否而以。而第二种的做法就在设计时考虑到区间相交的几种不同情况。而以后判断变量就是不用True or False 这样的子的变化还是可以让人接受的。但样子处理好像对现有的代码并没有明显提高或帮助。不过在中途写合并时两个区间时又会要判断是否在区间内。以下是两个区间的合并:
public
Interval <T> Merge(T begin, T end)
{
if (begin.CompareTo(end) > 0)
throw
new
Exception(
"开始数值不能大于结束数值" );
flag1 =
this .IsBetween(begin), fllag
2 =
this.IsBetween(end);
if (flag1 &&fllag
2 )
return
this;
if (flag1 )
return
new
Interval <T>(
this._begin, end);
if (flag
2 )
return
return
new
Interval <T>(begin,
this._end);
flag1 =
this.Begin.IsBetween(begin, end);
flag
2 =
this.End.IsBetween(begin, end);
if (flag1 &&flag
2 )
return
return
new
Interval <T>(begin, end);
if (flag1 )
return
return
new
Interval <T>(begin,
this._end);
if (flag
2 )
return
new
Interval <T>(
this._begin, end);
throw
new
Exception(
"区间不相交,无法相加" );
}
|
public
Interval <T> Merge(T begin, T end)
{
var kind = GetJoint(begin, end);
switch (kind)
{
case
JointIntervalKind .Begin:
return
new
Interval <T>(
this._begin, end);
case
JointIntervalKind .End:
return
new
Interval <T>(begin,
this._end);
case
JointIntervalKind .Equal:
case
JointIntervalKind .Include:
return
this ;
case
JointIntervalKind .UnInclude:
return
new
Interval <T>(begin, end);
default :
throw
new
Exception(
"区间不相交,无法相加" );
}
}
|
左边的问题是不能重用上段代码中两个区间之间的关系。要是上段代码出现了问题这段代码也是修改相似的地方逻辑简单还好说要是复杂点的逻辑就死惨了。但也有不少人会写出这样子的代码。 | 右边代码是发现这段代码与上段代码的区间关系很接近而且逻辑还相对有点复杂的情况。果断的修改上段区间关系的代码让合并区间也能调用上段代码。于是添加了枚举类型合并时先调用区间关系后就可以通过Switch来判断。 |
从这示例说明如果会产生多种结果的情况如果你采用的是一刀切非黑即白的观念来处理。那会产生很多不可控情况如:维护(改了判断忘记改合并从而导致Bug的产生)、添加新功能(还要用到判断时是直接复制后再改代码?还是……)
代码整理
为了实现功能而一次过的代码 | 实现了功能并通过单元测试后从而整理的代码 |
public
void Add(
Interval<T> value)
{
Interval <T>[] betweens = GetJointInterval(value);
if (betweens==
null| |betweens.Length==0)
{
_region.Add(value);
return;
}
int index = -1;
Interval<T> newitem;
if (betweens.Length == 1)
{
var item = betweens[0];
newitem = item + value;
if (Equals(item, newitem))
return ;
index =
this._region.IndexOf(item);
this._region.RemoveAt(index);
this._region.Insert(index, newitem);
return;
}
var interval1 = source.OrderBy(n => n.Begin).First();
var interval2 = source.OrderByDescending(n => n.End).First();
T tempbegin = value.Begin.CompareTo(interval1.Begin) == -1 ? value.Begin : interval1.Begin;
T tempend = value.End.CompareTo(interval2.End) == 1 ? value.End : interval2.End;
newitem =
new
Interval <T>(tempbegin, tempend);
foreach (
var interval
in betweens)
{
var tmpindex =
this._region.IndexOf(interval);
if (tmpindex == -1)
continue ;
if (index < tmpindex) index = tmpindex;
this._region.RemoveAt(tmpindex);
}
this._region.Insert(index, newitem);
}
|
public
void Add(
Interval<T> interval)
{
Interval <T>[] betweens = GetJointInterval(interval);
int count = betweens.GetCount();
//扩展函数
switch (count)
{
case 0: _region.Add(interval);
break ;
case 1: ReplaceRegion(interval, betweens.First());
break;
default : ReplaceRegion(interval, betweens);
break;
}
}
///
<summary>
///
通过相交的数组创建新的区间,并替换原来的数据
///
</summary>
///
<param name="interval">
新
</param>
///
<param name="source">
源
</param>
private
void ReplaceRegion(
Interval<T> interval,
Interval <T> source)
{
Interval <T> newitem = source + interval;
if (Equals(source, newitem))
return ;
this ._region.Remove(source);
this ._region.Add(newitem);
}
///
<summary>
///
通过相交的数组创建新的区间,并替换原来的数据
///
</summary>
///
<param name="interval"></param>
///
<param name="source">
源
</param>
private
void ReplaceRegion(
Interval<T> interval,
Interval <T>[] source)
{
var min = source.OrderBy(n => n.Begin).First();
var max = source.OrderByDescending(n => n.End).First();
var newitem = interval.Merge(min, max);
foreach (
var tmpindex
in source)
this ._region.Remove(tmpindex);
this ._region.Add(newitem);
}
|
- 一个函数内的行数最好不要超过15~20行。
- 能把相关几行的代码可整理成私有方法函数就尽量整理,那样子会提高更好的易读性。
- 操作子元素的方法能公开方法可归入子元素方法如(Interval<T> Merge(Interval<T> min, Interval <T> max);)
- 走分支时不要用if采用Switch
重载运算符
由于
Interval
<T>设计成可操作的值类型,考虑到代码的直观、易读性、方便操作可以重载运算符
类型 | 函数 | 示例 |
算术 |
public
static
Interval
<T>
operator
+(
Interval
<T> x,
Interval
<T> y)
| x+y(合并) |
判断 |
public
static
bool
operator
true
(
Interval
<T> x)
public
static
bool
operator
false
(
Interval
<T> x)
public
static
bool
operator
!(
Interval
<T> x)
public
static
bool
operator
>(
Interval
<T> x,
Interval
<T> y)
public
static
bool
operator
<(
Interval
<T> x,
Interval
<T> y)
public
static
bool
operator
>=(
Interval
<T> x,
Interval
<T> y)
public
static
bool
operator
<=(
Interval
<T> x,
Interval
<T> y)
| if (x){}
if (!x){}
x>y
x<y x>=y x<=y |
转换 |
public
static
implicit
operator
T[](
Interval
<T> value)
public
static
implicit
operator
Interval
<T>(T value)
public
static
implicit
operator
Interval
<T>(
Tuple
<T, T> value)
public
static
implicit
operator
Tuple
<T, T>(
Interval
<T> value)
| int[] arr=x;
Interval
<
int
> x=1;
Interval
<
int
> x =
new
Tuple
<
int
,
int
>(3,5)
Tuple
<
int
,
int
> tuple=x;
|
单元测试
只要区间的元素是固定的数量那每次元素无序的添加最后所得的结果一定一致。测试往往是最难的特别是这种而单元测试工具是最好的选择可以使用自动化测试来测试排列组合计算出所有元素加添的情况。按一开始自己定的好顺序一来手功测试一点错都没有还真的没想到通过自动测试无序添加测试连续改了接收2位数的次数才把Bug修改好。
总结
一开始设计时区间元素(两个基本属性、是否在区间内、与另一个区间合并的函数)更多的方法是在写代码时细化的,连区间元素自身能进行比较也是在集合中要想排序时才自到的,而集合想到要排序是在无序测试时发现结果是对的但整理后的两个区间的位置错了从而把
List
<T>改成
SortedSet
<T>。在区间关系中一开始也是用Bool直接了当,在合并区间时发现生了问题才采用了枚举型。这个功能代码写出效果就1个小时就拍拉拍拉写完了更多的时候是在整理和完善后跑单元测试再加上写这文章时也会突然间想到新功能又去改改测测就用了2天的时间。写个代码简单快捷但要写个好的代码还真的要用上不少时间当然不是上班的8小时两天是晚上的3-4小时。这个代码功能不怎么样但也有算是一个比较通用的东东以后应该用得上。