以簡單功能代碼示例講解我的開發經驗


編碼基本上每位程序員都是會的但由於每位程序員的習慣都有所不同從而產生了各式各樣的編碼。怎么樣的代碼是最好的?這好像就沒有一個很好的說法從我自己幾年的開發經驗覺得好的代碼應該具有以下幾點特性:
  • 易讀:命名、函數內上下文件流程(達到基本上不用注釋都知道這是的是什么)
  • 易擴展:有新的需求時可以不改動(少量改動)以前的代碼就可以完成
  • 易維護:用少量的時間就可以完成維護過程(這與前兩個有很大的關系)
這好像是地球人都知道的但做起來又是另一回事,還是以一個數據邏輯為原型來細說。
現在一堆區間數組把相交的區間數組合並起來得出新的區間數組效果如下:
原始區間數組:{3,5}{1,5}{5, 9}{6, 8}{-9, 5}{40, 90}{5,7}{-1,1}
合並區間數組:{-9, 9}{40, 90}
需求分析
     分析主要的功能是把這走這一需求時所產生的情況合理的整理出來。
     合並時兩個區間可能會產生的關系(與第二區間相等、 不相交在第二區間前、 不相交 在第二區間后 與第二區間的頭相交   與第二區間的尾相交 包含第二區間 被第二區間包含),然后把相關聯的區間合並在一起。
簡單設計
  1. 區間以操作運算為主適用值類型(struct )
  2. 區間內的值一般是可比較的值並不一定為Int32整形定義泛型T:IComparable
  3. 區間元素自己也是能進行比較的引用接口IComparable , IComparable <in T>
public   struct  Interval <T> :  IComparable  ,  IComparable  <  Interval <T>>  where  T :  IComparable
名稱 說明
public T Begin{   get ;}
開始值
public T End{   get ;}
結束值
public  bool IsBetween(T value);
指定值是否在區間內
public  JointIntervalKind GetJoint(T begin, T end);
public  JointIntervalKind GetJoint( Interval<T> value);
與指定區間的相交情況
public  Interval <T> Merge( Interval<T> x);
public  Interval <T> Merge(T begin, T end);
public  Interval <T> Merge( Interval<T> min,  Interval <T> max);
與指定區間合並
public  int CompareTo( object other);
public  int CompareTo( Interval<T> other);
實現接口功能:與指定區間比較
 
  1. 區間集合容器
  2. 因子元素泛型,集合容器也定義相同的結構T:IComparable
  3. 因區間元素能進行比較,新加入一個元素區間后最好能自動排好序集合容器可采用SortedSet <T>
public  class  IntervalNode<T>  where T :  IComparable
名稱 說明
public  Interval <T>[] Region{   get ;}
合並的區間數組
public  void Add(T value);
public  void Add(T begin, T end);
public  void Add( Interval<T> interval);
public  void Add( IEnumerableInterval <T>> collection);
向集合添加區間
public  Interval <T>[] GetJointInterval(T begin, T end)
public  Interval <T>[] GetJointInterval( Interval<T> value);
取出相交的區間
public  bool HasBetween(T value);
當前值是否在區間內
public Interval <T>[] GetBetween(T value);
取出指定值所在的區間
private  readonly  SortedSet< Interval <T>> _region;
表示按排序順序保持的對象的集合
 
區間相交情況
[  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 )  return  true ;
            falg1  =  this .Begin.IsBetween(begin, end);
            falg2  =  this .End.IsBetween(begin, end);
             if  (falg1  &&flag )  return  true ;
             if  (falg1  )  return  true ;
             if  (flag )  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 this.IsBetween(end);
             if (flag1 &&fllag return  this;
             if (flag1 )  return   new  Interval <T>(  this._begin, end);
             if (flag return  return  new  Interval <T>(begin,  this._end);
            flag1 =  this.Begin.IsBetween(begin, end);
            flag this.End.IsBetween(begin, end);
             if (flag1 &&flag return  return  new  Interval <T>(begin, end);
             if (flag1 )  return  return  new  Interval <T>(begin,  this._end);
             if (flag 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);
        }
 
     
左邊代碼段我就不多數了主要是講講右邊代碼的整理規則。
  1. 一個函數內的行數最好不要超過15~20行。
  2. 能把相關幾行的代碼可整理成私有方法函數就盡量整理,那樣子會提高更好的易讀性。
  3. 操作子元素的方法能公開方法可歸入子元素方法如(Interval<T> Merge(Interval<T> min, Interval <T> max);)
  4. 走分支時不要用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小時。這個代碼功能不怎么樣但也有算是一個比較通用的東東以后應該用得上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM