• 从Klocwork中.net检测项说说代码书写


          前些日子对公司所有.net产品使用Klocwork进行了静态代码分析。由于Klocwork目前没有发现有破解版,虽然可用于C++、Java、C#等项目,但因价格不菲,其使用细节我就不多讲。通过这次对代码的静态分析我们还是发现了一些问题,有些可能是我们本身不太认真直接造成的;还有一些则可能是由于代码的在长期维护或是后期的不断重构中,间接的造成了相关问题。下面我们就一些比较常见的现象进行罗列。

    [其中有一些项与VS自带的代码分析相同,代码在VS2010+.net3.5+Win7 环境中测试通]

    一、值类型

    1、 浮点数据比较

    double x1 = 0.1;

    float x2 = 0.1f;

    if (x1 == x2)  //浮点数的比较,不能直接等于或不等于,此处不相等。

    {

    }

    浮点数的比较,有他特定的规则,即:误差在规定的范围内。但在现实中,我们总是很懒,容易简单的写成 if (a == b)。

    2、 空值判断

    int i = 0;

    //…

     if (i == null)  //值类型,此处永远不为空。即此处永远为false。

    {

    }

    3、 获取Object.ReferenceEquals

    object obj = new object();

    int i = 5;

    bool res = Object.ReferenceEquals(i, obj);    //这样的判断永远为false,无意义。

    4、 String类型“+”的问题

    string res = string.Empty;

    int count = 100;

    for (int i = 0; i < count; i++)    //foeach 中也适应。

    {

        res += "Hello";                  //效率低,建议改用StringBuilder进行操作。

    }

    5、 整型数据除后还是整形,精度丢失

    int a = 11;

    long b = 5;             //int b=5;  结果也一样。

    decimal d = a / b;   //想要的结果为2.5,其实d值为2。

    float f = a / b;       //想要的结果为2.5,其实f值为2.0。

    二、无意义的判断

    1、 do操作

    int x = 0;

    do

    {

        x++;

         //...

    } while (3 < 10);  // 条件是常数,永远为true,且表达式无意义、可读性差。

    //也适应于类似:int m = 3; int n = 10;  while (m < n)的情况,但m、n自始至终都没有改变过。

     

    //建议改为类似如下方式。

    do

    {

        x++;

        //...

    } while (true);

     

    2、 if操作

    if (3 < 10)  //无意义,永远为true。

    {

    }

     

    3、 swich操作

    switch (10 - 3)  //无意义,永远匹配7

    {

        case 7:

             break;

        //……

    }

     

    4、 ?操作

    return ((10 - 3) > 1) ? 0 : 1;   //无意义,永远返回0

     

    5、 While操作

    while (false)   //此处while内的操作永不执行,无意义。也适应于类似While (10 < 3)

    {

         // ...

    }

    三、空指针

    预先一段代码,为方便说明:

    /// <summary>
        /// 示例类.只为说明配置项,无具体代表意义
        /// </summary>
        public class Example
        {
            public Example()
            {
                Init();
            }
    
            protected virtual void Init()
            {
                Debug.WriteLine("Init Example");
            }
    
            #region other
    
            /// <summary>
            /// 随机创建一个Example对象,可能为实例,也可能为null
            /// </summary>
            /// <returns>返回一个对象,或返回null</returns>
            public static Example Factory()
            {            
                Random random = new Random();
                int randKey = random.Next(0, 2);
    
                if (randKey == 0)
                {
                    return null;
                }
                else
                {
                    return new Example();
                }
            }
    
            /// <summary>
            /// 随机产生一个标志,要能为true也可能为false.
            /// </summary>
            /// <returns></returns>
            public static bool GetFlag()
            {
                 Random random = new Random();
                int randKey = random.Next(0, 2);
    
                if (randKey == 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
    
            /// <summary>
            /// 如果example为null,那么必定引发异常
            /// </summary>
            /// <param name="example"></param>
            /// <returns></returns>
            public static bool CheckName(Example example)
            {
                if (string.IsNullOrEmpty(example.Name))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
    
            /// <summary>
            /// 如果example为null,可以引发异常
            /// </summary>
            /// <param name="example"></param>
            /// <param name="flag"></param>
            /// <returns></returns>
            public static bool CheckData(Example example, bool flag)
            {
                if (!flag)
                {
                    return false;
                }
    
                if (example.Data < 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }
    
            }
    
           
            public string Name
            {
                get;
                set;
            }
    
    
            public int Data
            {
                get;
                set;
            }
    
            #endregion
        }

    1、 有机会调用可能为null的对象

    Example example = Example.Factory();

    bool flag = Example.GetFlag();

    if (flag)

    {

        Example.CheckName(example);      //如果exaple为null,且flag为true,则会引发异常

     

        //以下代码是CheckName内部

        //if (string.IsNullOrEmpty(example.Name))   //如果传入的为null对象,则exaple.Name必引发异常

        //{

        //    return true;

        //}

        //else

        //{

        //    return false;

        //}

    }

     

    2、 调用可能为null的对象

    Example example = Example.Factory();

    Example.CheckName(example);        //如果exaple为null,则会引发异常

     

    //以下代码是CheckName内部

    //if (string.IsNullOrEmpty(example.Name))   //如果传入的为null对象,则exaple.Name必引发异常

    //{

    //    return true;

    //}

    //else

    //{

    //    return false;

    //}

     

    3、 还有许多关于传入空引用或可能传入空应用,在此也不详细说明,大同小异。重点想与大家探讨 一下如下疑惑:

    Example example = Example.Factory();

     

    //方案1:如果返回的结果可能为空,那么就忽必对其为空时进行判断

    if (example != null)

    {

        Example.CheckName(example); 

    }

     

    //方案2:对CheckName(example)函数内部进行有效性判断

    if (example == null)

    {

        return true;  //或者返回其它

    }

     

    if (string.IsNullOrEmpty(example.Name))

    {

        return true;

    }

    else

    {

        return false;

    }

     

    疑惑:

    ①、我个人觉得,如果方案1和方案2中的代码都有进有效性判断应该是比较严谨的。

    ②、但是人总是有惰性的,比如方案2中,每写一个函数都得先对其输入的对象参数是否为null进行判断,总觉有点繁琐。

    ③、就我个人在编程时,对方案1的实施我还是比较到位,因为可能为空的对象我还是尽量进行判断。 因为不敢肯定别人提供的Example.CheckName(example)是否进行了有效性判断。在对方案2的实施确较差,除非这些功能是提供给别人使用的,如果是自己用却少有判断,看了这个后,我是是否该改一改了?

    ④、也不知大家是否有其它方案或习惯?请传授一下。

    四、变量被覆盖问题

    如果一个类有个成员变量为:

    private int price;

    1、  变量被参数覆盖

    public void InitPrice(int price)

    {

         this.price = price;

    }

     

    2、 变量被临时变量覆盖

    public void DoSomeThing()

    {

          int price = 0;

          //....

          this.price = price;

    }

    3、 成员变量如此,属性也一样。不过由于C#常用Pascal命名方法所以重名较少。

    疑惑:

    ①、由于采用Passcal命名方式,在考虑简洁、可读性强的情况下很容易出现重名的现象,至少我的代码中就有样的情况。有时我也会采用类似“currentPrice”来作为成员变量,但终究不能适应所有场合。

    ②、每当这个时候就会想起匈牙利命名方法来个“m_price”。但是在很多公司,至少我这里是这样,命名方式不能混搭,所以有时就有点为难了。

    ③、其实在被覆盖后,不经意间就会把“this.”给忘了,特别在复制粘贴时更甚,比如从某未被覆盖处拷贝“price = GetMaxPrice();”到被覆盖处,这样就造成了错误或未达到效果,这也是提示“警告”的原因吧。

    ④、不知大家会不会有这方面的考虑,有无更好的解决方案?

    五、强制类型转换

    1、 强制转换类

    在C#中由于有is和as两个关键字,相对来说如下代码较少发生。

    Example example = null;

    object obj = new object();

    example = (Example)obj;        //类型不匹配会引发异常。

    SimpleExample simpleExample = (SimpleExample)example;

     

    2、 循环中未经判断直接类型转换

    ArrayList list = new ArrayList();

    list.Add(1);

    list.Add(2);

    list.Add(3);

     

    int value = 0;

    foreach (int i in list)   //也许list是我们自己构造的,所以我们并不担心,但并不严谨

    {

        value += i;

    }

     

    //某一天,有人重构

    list.Add("全部");

    //…

    foreach (int i in list)    //必产生异常

    {

    }

     

    注:虽然ArrayList现在用的少,但此问题确是存在,比如常见的有如“下拉框”中的项等。

     

    3、 Landa表达式中

    List<Example> exampleList = new List<Example>();

    //...

    var temp = exampleList.Where(p => p.Name.Length > 5).ToList();    //此处警告

     

    疑惑:

        本人不解未发现异样?(不会是误报吧?)

    六、其它:

    1、 构造函数中调用虚函数

        由于子类在初始化时,会调用基类的初始化函数。然而由于多态的原因,在调用基类初始化函数时,调用的那个虚函数却又是子类的函数。从而给人以混乱的感觉,也可能达不到预期的效果。我所在的公司《C#编程规范》中明确要求构造函数不允许调用虚函数。

        来看个示例吧,代码如下:

    /// <summary>
        /// 示例类.只为说明配置项,无具体代表意义
        /// </summary>
        public class Example
        {
            public Example()
            {
                Init();
            }
    
            protected virtual void Init()
            {
                Console.WriteLine("Init Example");
            }
          
        }
    
        /// <summary>
        /// 子类
        /// </summary>
        public class SimpleExample : Example
        {
            public SimpleExample()
            {
                Init();
            }
    
            protected override void Init()
            {
                Console.WriteLine("Init SimpleExample");
            }
        }

     SimpleExample simpleExample = new SimpleExample();

    输出结果了并非意想的:

    Init Example

    Init SimpleExample

    而是:

    为什么会这样,直接上图吧:

    但F11后“奇迹”出现了,感觉也混乱了!

     

    2、 Catch中无操作

        当Catch中无操作时,会发出警告。园子中精华区有个“throw和throw ex的区别”讲得不错,可解决此问题,有兴趣的朋友可以看看。当然在软件系统中如何throw异常,在对象设计及函数设计时如何throw异常信息(主要是信息)也可能各不尽然,限于篇幅在此不做详述,以后归纳一下再与大家交流。

     

    3、 资源释放

    //1、非托管资源

    string fileNaem = "C:\\1.txt";

    StreamReader reader = new StreamReader(fileNaem);

    String result = reader.ReadToEnd();           //如果此处抛出异常,那么Close不会调用,文件被长时间点用

     reader.Close();

     //2、托管资源

    Form testForm = new Form();

    testForm.ShowDialog();        //如果Form中没有非托管资源,应该可以回收。建议也使用using

    个人观点:

    ①、对于非托管资源,如果出现异常不能正常释放,那么就会长时间占用资源,造成不良后果。但是对于托管资源,可能影响不大。

    ②、对实现了IDisposable的类,建议使用using来管理。

    ③、对于其它资源,可以通过try- finally来管理,即在finally中对资源进行释放。

  • 相关阅读:
    一个模式的四个基本要素
    【原】基础篇:第一篇,本节主要向大家介绍Ext的最基础的话题
    一些基本的设计模式
    沈老板位置
    977
    设计模式的原则
    hash算法的介绍 【清晰易懂】
    字符编码知识:Unicode、UTF8、ASCII、GB2312等编码之间是如何转换的?
    字符编码知识:Unicode、UTF8、ASCII、GB2312等编码之间是如何转换的?
    vbs生成域账号
  • 原文地址:https://www.cnblogs.com/showjan/p/2735438.html
Copyright © 2020-2023  润新知