代码改变世界

以简单功能代码示例讲解我的开发经验

2013-01-19 15:52  旦旦  阅读(1956)  评论(4编辑  收藏  举报
编码基本上每位程序员都是会的但由于每位程序员的习惯都有所不同从而产生了各式各样的编码。怎么样的代码是最好的?这好像就没有一个很好的说法从我自己几年的开发经验觉得好的代码应该具有以下几点特性:
  • 易读:命名、函数内上下文件流程(达到基本上不用注释都知道这是的是什么)
  • 易扩展:有新的需求时可以不改动(少量改动)以前的代码就可以完成
  • 易维护:用少量的时间就可以完成维护过程(这与前两个有很大的关系)
这好像是地球人都知道的但做起来又是另一回事,还是以一个数据逻辑为原型来细说。
现在一堆区间数组把相交的区间数组合并起来得出新的区间数组效果如下:
原始区间数组:{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), flag2 = this.IsBetween(end);
            if (flag1 && fllag2) 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), fllagthis.IsBetween(end);
            if (flag1 &&fllagreturn this;
            if (flag1 ) return  new Interval <T>( this._begin, end);
            if (flagreturn return new Interval <T>(begin, this._end);
            flag1 = this.Begin.IsBetween(begin, end);
            flagthis.End.IsBetween(begin, end);
            if (flag1 &&flagreturn return new Interval <T>(begin, end);
            if (flag1 ) return return new Interval <T>(begin, this._end);
            if (flagreturn 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小时。这个代码功能不怎么样但也有算是一个比较通用的东东以后应该用得上。