編碼基本上每位程序員都是會的但由於每位程序員的習慣都有所不同從而產生了各式各樣的編碼。怎么樣的代碼是最好的?這好像就沒有一個很好的說法從我自己幾年的開發經驗覺得好的代碼應該具有以下幾點特性:
- 易讀:命名、函數內上下文件流程(達到基本上不用注釋都知道這是的是什么)
- 易擴展:有新的需求時可以不改動(少量改動)以前的代碼就可以完成
- 易維護:用少量的時間就可以完成維護過程(這與前兩個有很大的關系)
現在一堆區間數組把相交的區間數組合並起來得出新的區間數組效果如下:
原始區間數組:{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小時。這個代碼功能不怎么樣但也有算是一個比較通用的東東以后應該用得上。