精华内容
下载资源
问答
  • 用 Groovy 减少代码冗余

    千次阅读 2008-12-14 10:35:00
    Groovy 简洁的语法将开发人员从那种需要进行代码编译但却无助于表达 什么 是程序真正想要实现的典型的 Java™ 结构中解放了出来。在实战 Groovy 系列的这一复兴篇中,Groovy 开发人员兼特约专栏作家 J. Scott Hickey...
     
    
    Groovy 简洁的语法将开发人员从那种需要进行代码编译但却无助于表达 什么 是程序真正想要实现的典型的 Java™ 结构中解放了出来。在 实战 Groovy 系列的这一复兴篇中,Groovy 开发人员兼特约专栏作家 J. Scott Hickey 带您进行一系列对常规 Java 代码和 Groovy 代码的比较,展示这门令人兴奋的语言如何将您解放出来,让您能够专注于编码的重要方面。

    通常,程序员们转而选择诸如 Groovy 之类的编程语言,是为了构建快速的实用程序,快速编写测试代码,甚至创建构成大型的 Java 应用程序的组件,而 Groovy 先天具有这样一种能力,它能够减少传统的基于 Java 系统所固有的许多冗余并降低其复杂度。Groovy 简洁而灵活的语法将开发人员从那种需要进行代码编译却无助于表达什么 是程序真正想要实现的典型的 Java 结构中解放出来。不仅如此,Groovy 轻松的类型通过减少一些接口和超类使代码不再复杂,这些接口和超类都是常规 Java 应用程序用以支持不同具体类型间的通用行为所需的。

    为了举例说明 Groovy 如何减少 Java 应用程序所涉及的无用数据,我将使用 Bruce Tate 和 Justin Ghetland 的 Spring: A Developer's Notebook(参见 参考资料) 中的样例代码,该书介绍了如何使用 Spring 进行控制反转。每当回顾一个 Java 样例,我都会将其与实现相同功能的相应的 Groovy 源代码进行比较,您将很快发现 Groovy 通过减少 Java 编程的不同方面(冗余且不必要地传递了应用程序的行为)而使应用程序代码变得多么地清晰。

    Groovy 之声

    在 Bruce 和 Justin 这本书的第一章中,创建了一个简单的自行车商店应用程序,其中包含有四个类。首先,我将向您展示一个简单的名为 Bike 的 JavaBean 类,该类代表了一辆库存的自行车。然后,我会考查自行车商店的类型,名为 RentABike。它包含了一个 Bike 集。还有一个命名为 CommandLineView 的用于显示自行车列表的类,该类依赖于 RentABike 类型。最后,有一个用于集成这些部分以创建工作应用程序的类,该类利用 Spring 来传递完整地配置了 RentABike 类型的 CommandLineView 类 —— 免去了复杂的硬编码。

    停用 JavaBean!

    清单 1 中一个代表自行车的类在常规 Java 代码中被实现为一个简单的 JavaBean,它是 Java 开发人员可能已经编写好的成百上千的类的一个典型。通常来说,JavaBean 并没有什么特殊之处 —— 其属性被声明为 private,且可通过 public getter 和 setter 对其进行访问。


    清单 1. Java 代码中的 Bike JavaBean
        
    import java.math.BigDecimal;

    public class Bike {
    private String manufacturer;
    private String model;
    private int frame;
    private String serialNo;
    private double weight;
    private String status;
    private BigDecimal cost;

    public Bike(String manufacturer, String model, int frame,
    String serialNo, double weight, String status) {
    this.manufacturer = manufacturer;
    this.model = model;
    this.frame = frame;
    this.serialNo = serialNo;
    this.weight = weight;
    this.status = status;
    }

    public String toString() {
    return "com.springbook.Bike : " +
    "manufacturer -- " + manufacturer +
    "/n: model -- " + model +
    "/n: frame -- " + frame +
    "/n: serialNo -- " + serialNo +
    "/n: weight -- " + weight +
    "/n: status -- " + status +
    "./n"; }

    public String getManufacturer() { return manufacturer; }

    public void setManufacturer(String manufacturer) {
    this.manufacturer = manufacturer;
    }

    public String getModel() { return model; }

    public void setModel(String model) { this.model = model; }

    public int getFrame() { return frame; }

    public void setFrame(int frame) { this.frame = frame; }

    public String getSerialNo() { return serialNo; }

    public void setSerialNo(String serialNo) { this.serialNo = serialNo; }

    public double getWeight() { return weight; }

    public void setWeight(double weight) { this.weight = weight; }

    public String getStatus() { return status; }

    public void setStatus(String status) { this.status = status; }

    public BigDecimal getCost() { return cost; }

    public void setCost(BigDecimal cost) {
    this.cost = cost.setScale(3,BigDecimal.ROUND_HALF_UP);
    }

    }

    清单 1 是一个只有一个构造方法和六个属性的小例子,但其代码却填满了浏览器的整个页面!清单 2 显示了在 Groovy 中定义的相同的 JavaBean:


    清单 2. Bike GroovyBean
      
    class Bike {

    String manufacturer
    String model
    Integer frame
    String serialNo
    Double weight
    String status
    BigDecimal cost

    public void setCost(BigDecimal newCost) {
    cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP)
    }


    public String toString() {
    return """Bike:
    manufacturer -- ${manufacturer}
    model -- ${model}
    frame -- ${frame}
    serialNo -- ${serialNo}
    """
    }
    }

    您认为哪一个没那么多冗余呢?

    Groovy 版的代码要少很多很多,这是因为 Groovy 的默认属性语义用 public 访问器和存取器自动定义了 private 域。例如,上述 model 属性现在有了自动定义的 getModel() 方法和 setModel() 方法。可以看到,这项技术的好处是,不必在一种类型中按照属性手工定义两个方法!这也解释了 Groovy 中的一条反复强调的定律:使普通的编码规则变得简单

    Groovy 更新了的属性语法
    最新版的 Groovy, JSR-06 简化了 Groovy 的属性语法。在此版本之前,属性要在变量声明前跟一个特定的 @Property 符号。但这个符号现在已经不再需要。在 JSR-06 中,只需指定一个类型和一个变量名,就会自动生成一个有着 public getter 和 setter 的 private 属性。也可以使用 def 关键字来代替特定的类型。

    另外,在 Groovy 中,类的默认函数表达得更为简洁,而在常规 Java 代码(如 清单 1)中该函数必须显式编码。当需要用构造函数或 getter 或 setter 来完成一些特殊任务时,Groovy 真的很出色,因为只需瞥一眼代码,其精彩的行为就会立即变得十分明显。例如,在 清单 2 中很容易看出,setCost() 方法会将 cost 属性换算为三个十进制的位。

    将这段不太显眼的 Groovy 代码同 清单 1 中的 Java 源代码进行比较。第一次阅读这段代码时,您注意到 setCost() 方法中嵌入了特殊的函数了吗?除非仔细观察,否则太容易看漏了!

    Groovy 测试

    清单 3 中 Bike 类的测试用例展示了如何使用自动生成的访问器。同时,出于进一步简化通用编程任务的考虑,测试用例也使用了更为简便的 Groovy 点属性名 标记来访问属性。相应地,能够通过 getModel() 方法或更为简洁的 b.model 形式来引用 model 属性。


    清单 3. Groovy 中的 Bike 测试用例
      
    class BikeTest extends GroovyTestCase {
    void testBike() {
    // Groovy way to initialize a new object
    def b = new Bike(manufacturer:"Shimano", model:"Roadmaster")

    // explicitly call the default accessors
    assert b.getManufacturer() == "Shimano"
    assert b.getModel() == "Roadmaster"

    // Groovier way to invoke accessors
    assert b.model == "Roadmaster"
    assert b.manufacturer == "Shimano"
    }
    }

    也注意到在上述 Groovy 例子中,不必定义一个如 清单 1 中定义的 Java 构造函数那样的能够接受全部六个属性的构造函数。同时,也不必要创建另一个 只含两个参数的构造函数来支持测试用例 —— 在对象创建过程中设置对象属性的 Groovy 的语义不需要这种冗余、烦人的构造函数(它们综合有多个参数但其作用却只是初始化变量)。

    Groovy 和 IDE
    像 Eclipse 那样的 IDE 使得自动创建 getter 和 setter 变得十分简单。这些工具也便利了从具体类中提取接口以利于对其进行重构。但我敢说,读那段代码的次数要比写它的次数多很多。 不幸的是,开发人员仍需要通过已经生成的代码和源文件来费力地分辨程序真正在实现什么。用这个高质量的 Java 工具究竟能不能节省创建 Groovy 源代码的敲键次数尚有争议,但一看即明的是 Groovy 代码更为简洁、更易于解释,且没那么复杂。在实际的有着数百个类和数千行代码的应用程序,这种优势就更为突出。

    降低的复杂性

    在前面的部分中,Bike GroovyBean 利用了 Groovy 的属性和构造语义以减少源代码中的冗余。在这一部分中,Groovy 版的自行车商店也将受益于额外的冗余减少特性,如针对多态的 duck-typing、集合类的改进及操作符重载。

    Grooving 中使用多态

    在 Java 自行车应用程序中,名为 RentABike 的接口是用来定义由自行车商店支持的 public 方法的。正如在清单 4 中说明的那样,RentABike 定义了一些简单的方法,这些方法用来返回商店中单个的 Bike 或所有 Bike 的列表。


    清单 4. 由 Java 定义的 RentABike 接口
      
    import java.util.List;

    public interface RentABike {
    List getBikes();
    Bike getBike(String serialNo);
    void setStoreName(String name);
    String getStoreName();
    }

    此接口允许多态行为并在下面两种重要情况下提供了灵活性。其一,如果要决定将实现由 ArrayList 转变为数据库形式,余下的应用程序与该变化是隔绝的。其二,使用接口为单元测试提供灵活性。例如,如果要决定为应用程序使用数据库,可以轻易地创建一个该类型的模拟实现,而且它不依赖于实时数据库。

    清单 5 是 RentABike 接口的 Java 实现,它使用 ArrayList 来存储多个 Bike 类:


    清单 5. RentABike 接口的 Java 实现
        
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Iterator;

    public class ArrayListRentABike implements RentABike {
    private String storeName;
    final List bikes = new ArrayList();

    public void setStoreName(String name) {
    this.storeName = name;
    }

    public String getStoreName() {
    return storeName;
    }

    public ArrayListRentABike(String storeName) {
    this.storeName = storeName;
    bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));
    bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222",12, "Excellent"));
    bikes.add(new Bike("Trek","6000", 19, "33333", 12.4,"Fair"));
    }

    public String toString() { return "com.springbook.RentABike: " + storeName; }

    public List getBikes() { return bikes; }

    public Bike getBike(String serialNo) {
    Iterator iter = bikes.iterator();
    while(iter.hasNext()) {
    Bike bike = (Bike)iter.next();
    if(serialNo.equals(bike.getSerialNo())) return bike;

    }
    return null;
    }
    }

    现在将 清单 45 中的 Java 代码同 清单 6 中的 Groovy 代码进行比较。Groovy 版的代码很灵巧地避免了对 RentABike 接口的需求。


    清单 6. Groovy 的 ArrayListRentABike 实现
      
    public class ArrayListRentABike {
    String storeName
    List bikes = []
    public ArrayListRentABike(){
    // add new instances of Bike using Groovy's initializer syntax
    bikes << new Bike(manufacturer:"Shimano", model:"Roadmaster",
    frame: 20, serialNo:"11111", weight:15, status:"Fair")
    bikes << new Bike(manufacturer:"Cannondale", model:"F2000",
    frame: 18, serialNo:"22222", weight:12, status:"Excellent")
    bikes << new Bike(manufacturer:"Trek", model:"6000",
    frame: 19, serialNo:"33333", weight:12.4, status:"Fair")
    }

    // Groovy returns the last value if no return statement is specified
    public String toString() { "Store Name:=" + storeName }

    // Find a bike by the serial number
    def getBike(serialNo) { bikes.find{it.serialNo == serialNo} }

    }

    Groovy 像其他动态语言(如 Smalltalk 或 Ruby)一样支持具有 “duck typing” 的多态 —— 在运行时,如果一个对象表现得像个 duck ,它就会被视为 duck ,从而支持 接口的多态。有了 Groovy ArrayListRentABike 实现,不但减少了成行的代码,而且由于少创建和维护一个模块,复杂性也降低了。那是非常重要的冗余减少!

    除了 duck typing,清单 6 中的默认属性语法还简单地定义了两个普通属性,storeNamebikes,如同拥有了 getter 和 setter 一样。这样做的好处和在 清单 12 中比较 JavaBean-GroovyBean 时所说明的好处是一样的。尤其是,清单 6 还阐明了另一个用以减少代码冗余的 Groovy 特性 —— 操作符重载。请注意如何使用 << 操作符来代替 add() 方法。通过减少一层嵌套的括号使代码的可读性得以改善。这也是 Groovy 众多通过减少冗余而改善代码可读性的特性中的一种。

    Groovy 方式下的和谐集合
    用诸如 eachfind 的方法来使用闭包简化了最常见的任务集,如循环和查找。将 清单 5 中 Java 版本的 getBike()清单 6 中 Groovy 版的进行比较。在 Groovy 中,很明显是通过其序列号来寻找 Bike。而在 Java 版中,定义了一个 Iterator 并计算列表中下一个条目,这很多余,且不利于理解该应用程序真正要实现的功能,即寻找一辆自行车。

    透明的代码

    Groovy 中的 duck-typing 和属性语义通过减少代码行数来减少冗余;然而,也可以通过增加透明度来减少冗余。在 清单 6 中,请注意在 ArrayListRentABike 构造函数中创建新 Bike 对象的方式。Groovy 名称和值的初始化语法比 Java 版的略微详细,但这些额外的代码却使整个代码更为透明 —— 将这一点与 清单 5 中 Java 版的进行比较,哪个属性被初始化为哪个值会立即明显 起来。不回过头来看 Bike JavaBean 源代码,您能记起哪个参数是 frame,哪个是 new Bike("Shimano"、 "Roadmaster"、20、 "11111"、15、 "Fair")weight 吗?尽管我刚写过,但我还是记不起来!








    一个更小的、更加 Groovy 化的自行车商店视图

    到目前为止,我将 Bike 和自行车商店类型在 Java 和 Groovy 下进行了比较。现在,到了更近距离地看一下自行车商店的视图 的时候了。在清单 7 中,该视图类具有一个 rentaBike 属性,该属性引用 RentABike 接口并在行动上说明 Java 版的多态。由于 Java 要求所有类属性都必须是声明过的类型,而不是针对某个特定的实现进行编码,我向一个接口编程,该接口使这个类跟 RentABike 实现的改变分隔开来。这是很好的、扎实的 Java 编程实践。


    清单 7. Java 版的自行车商店视图
      
    public class CommandLineView {
    private RentABike rentaBike;

    public CommandLineView() {}

    public void setRentaBike(RentABike rentaBike) {
    this.rentaBike = rentaBike;
    }

    public RentABike getRentaBike() { return this.rentaBike; }

    public void printAllBikes() {
    System.out.println(rentaBike.toString());
    Iterator iter = rentaBike.getBikes().iterator();
    while(iter.hasNext()) {
    Bike bike = (Bike)iter.next();
    System.out.println(bike.toString());
    }
    }
    }

    将清单 7 中的 Java 视图与清单 8 中的 Groovy 视图进行比较,请注意我声明了带 def 关键字的 rentaBike。这是 duck-typing 的实践,与 Java 版的很像。我正在实践好的软件设计,这是因为我还没有将视图和特定的实现耦合起来。但我也能够 定义接口就实现解耦。


    清单 8. Groovy 的 CommandLineView
      
    public class CommandLineView {
    def rentaBike // no interface or concrete type required, duck typing in action

    def printAllBikes() {
    println rentaBike
    rentaBike.bikes.each{ println it} // no iterators or casting
    }
    }

    Bike 和自行车商店类型一样,Groovy 的 CommandLineView 没有了为 RentABike 属性所显式编写 的 getter 或 setter 的冗余。同样,在 printAllBikes() 方法中,通过使用 each 来打印在集合里找到的每辆自行车,我再一次利用了 Groovy 强大的集合功能的改进。







    使用 Spring 进行组装

    在前面的部分中,已经介绍了 Groovy 相比 Java 是如何定义自行车、自行车商店和自行车商店视图的。现在该介绍如何将整个应用程序组装起来并在命令行视图中使用 Spring 来显示库存自行车列表了。

    工厂的工厂

    在 Java 编程中,一旦定义了一个接口,就可以使用工厂模式将创建真实的实现类的责任委派给一个对象工厂。使用 Spring 作为一个工厂极大地减少了冗余,并在 Groovy 和 Java 中都能够使用,在最终的代码样例中,Spring 负责在 Java 和 Groovy 中创建一个 CommandLineView 类型的实例。

    在清单 9 中,配置 Spring 是为了在返回一个 CommandLineView 实例前,创建并将自行车商店的 ArrayList 实现注入 CommandLineView 中。这意味着,不需要引用在 清单 78 的 Java 或是 Groovy 版的命令行视图中的 ArrayList 实现。在 Java 版中,被注入的类通常都会引用一个接口而不是实现。在 Groovy 中,由于使用 def 关键字,而允许利用 duck-typing。无论在哪个实例中,配置 Spring 的目的都是为了将自行车商店视图的实例和自行车商店类型的实例完整地配置起来


    清单 9. Spring 配置文件
            
    <beans>
    <bean id="rentaBike" class="ArrayListRentABike">
    <property name="storeName"><value>"Bruce's Bikes (spring bean)"</value></property>
    </bean>
    <bean id="commandLineView" class="CommandLineView">
    <property name="rentaBike"><ref bean="rentaBike"/></property>
    </bean>
    </beans>

    在清单 10 和 11 中,自行车商店组装类型用清单 9 中的配置文件创建了一个 Spring 的 ClassPathXmlApplicationContext 实例,然后,请求自行车商店视图的实例:


    清单 10. Java 版本下调用 Spring 创建自行车商店视图
      
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class RentABikeAssembler {
    public static final void main(String[] args) {
    // Create a Spring application context object
    ClassPathXmlApplicationContext ctx =
    new ClassPathXmlApplicationContext("RentABike-context.xml");

    // retrieve an object from Spring and cast to a specific type
    CommandLineView clv = (CommandLineView)ctx.getBean("commandLineView");

    clv.printAllBikes();
    }
    }

    请注意Java 版的清单 10 中,用以请求一个命令行视图实例的对 Spring 的调用要求向一个支持 printAllBikes() 方法的对象类型强制转换。在本例中,由 Spring 导出的对象将被强制转换为 CommandLineView

    有了 Groovy 及其对 duck-typing 的支持,将不再需要强制转换。只需确保由 Spring 返回的类能够对合适的方法调用(printAllBikes())作出响应。


    清单 11. Groovy 版的 Spring 组合件
      
    import org.springframework.context.support.ClassPathXmlApplicationContext

    class RentABikeAssembler {
    public static final void main(String[] args) {
    // Create a Spring application context object
    def ctx = new ClassPathXmlApplicationContext("RentABike-context.xml")

    //Ask Spring for an instance of CommandLineView, with a
    //Bike store implementation set by Spring
    def clv = ctx.getBean("commandLineView")

    //duck typing again
    clv.printAllBikes()
    }
    }

    正如在清单 11 中看到的那样,在 Groovy 中,duck-typing 对减少冗余的贡献不仅体现在无需声明接口即可支持由 Spring 自动配置对象,其贡献还体现在简化了对完全配置的 bean 的使用(一旦它从 Spring 容器中返回)。







    与 Groovy 相协调

    至此,希望我已经阐明了 Groovy 的强大功能及其如何能如此深远地改变源代码的性质。与上述 Java 样例相比,Groovy 代码更简短也更易理解。任何人,无论是经验丰富的 Java 架构师还是非 Java 程序员,都能轻易地掌握 Groovy 代码的意图。Groovy 及其对动态类型的支持减少了要管理的文件。总之,使用 Groovy 减少了在典型的 Java 程序中所常见的大量冗余。这实在是福音啊!






    展开全文
  • 冗余代码检测与分析

    2017-10-09 18:03:00
    \\t为了检测冗余代码,作者开发了一个工具,使用Roslyn创建C#源码抽象语法树。作者使用包括Roslyn和MSBuild在内的多个GitHub项目对这个工具进行了训练;\\t检测到冗余代码可以手动删除或添加...
    \

    本文要点

    \\
    • 代码冗余的原因多种多样,从未使用的变量到未完成的变更,再到废弃的代码;\\t
    • 冗余代码会产生一系列的影响,包括源代码臃肿、可靠性及可维护性降低。在某些情况下,死代码也会影响性能;\\t
    • 为了检测冗余代码,作者开发了一个工具,使用Roslyn创建C#源码抽象语法树。作者使用包括Roslyn和MSBuild在内的多个GitHub项目对这个工具进行了训练;\\t
    • 检测到冗余代码可以手动删除或添加注释,也可以使用一种自动化工具。使用特性分支有助于防止冗余代码检入主代码分支。\
    \\

    导言

    \\

    前段时间,我开发了一款工具,分析源代码中的依赖关系。它使用Roslyn创建C#源代码抽象语法树使用libclang创建C++源代码抽象语法树。为了验证它是否可以取得预期效果,我接下来实现了识别未使用方法的功能。结果显示,C#代码解析比C++代码解析准确得多,因此,我选择把重点放在C#分析器的进一步开发和其他人开发的更复杂的C#代码上。

    \\

    起初,该工具会标记出冗余方法所在的行,在弄清楚问题范围之后,我实现了自动删除那些行的选项。一个典型的分析过程会多次执行这个工具,尽可能地修剪源代码树。接下来是多个变更还原循环,以便可以成功地构建并通过测试。失败的原因是工具行为异常或者已知的局限性,例如,反射或代码契约。

    \\

    我选择了多个自己用过而又想回馈的C#项目,用它们的GitHub库训练了这个工具。最后,我向社区提交了pull request,请求他们讨论我在自己的分支里做的变更。由于这个工具很苛刻,而我又是第一次在网上与人交流,不懂技巧,所以希望我没有冒犯太多的人。在向社区做贡献及参与后续讨论的过程中,我对问题的理解更深入了,本文旨在将我的所得回馈给更广泛的社区。

    \\

    分析过的GitHub项目

    \\

    由JB Evain编写的Mono.Cecil可以将.NET代码反编译成C#。根据建议,只有36行代码需要删除,经过审核,JB选择单独添加部分变更,而不是合并分支。

    \\

    Automatic Graph Layout是微软官方的一个项目,由Lev Nachmanson、Sergey Pupyrev、Tim Dwyer、Ted Hart和Roman Prutkin开发,用于绘制图和有向图,Visual Studio也用它显示各种交互图。Pull request要求删除4674行代码,其中有一些和SilverLight有关(已于2015年宣布停用)。不经过修改或讨论,分支就被合并了进去。

    \\

    Roslyn是一个现代化的C#编译器,由.NET基金会的一个团队负责维护。在这个例子下,Pull request要求删除18364行代码,这引发了有益的讨论,并产生了下面讨论的大多数分类。显然,这个分支太大了,无法合并,取而代之,多个单独的议题被提了出来。

    \\

    MSBuild是微软官方的一个项目,Visual Studio的用户应该比较熟悉。根据分析,我提交了删除3722行代码的pull request,遗憾的是,其团队当时没有余力审核我提出的变更建议。

    \\

    最后分析的是.NET Core基础库里的System.XML程序集。这些库由.NET基金会负责维护,为了删除死代码,其团队发布了一条问题追踪信息。该问题的解决方法是逐个修剪程序集(通常被称为死代码消除),然后比较未修剪程序集和已修剪程序集之间的差异,从而确定哪些编译代码被删除了。通过这些差异可以知道哪些源代码被删除了,这项工作通常是由志愿者社区承担。

    \\

    鉴于只用该工具抽样分析了五个项目,得出任何结论都是不明智的,尤其是在缺少可靠的统计数据的情况下:

    \\
     Mono.CecilMSAGLRoslynMSBuildCoreFX (System.XML)
    已删除364674183643722427
    初次提交时间2010/04/122015/02/222014/03/182015/03/122014/11/08
    作者(主要)39 (1)25 (4)285 (31)90 (6)526 (29)

    鉴于其悠久的历史,MSBuild的初次提交时间值得注意。仅数一下作者的数量而不评估他们的贡献几乎可以肯定是没有意义的,因此我大致估计了主要的贡献者。说到这里,我推测:

    \\
    • 新开发的项目比成熟项目冗余代码多 \\t
      • 编写代码时假定将来会需要\\t\t
      • 测试少,相应的,发现的Bug就少\\t
      \\t
    • 团队越大制造的冗余代码越多 \\t
      • 沟通成本会随团队规模的扩大而呈几何级数上升,参见《人月神话》\\t
      \

    测试结果分类

    \\

    该工具测试冗余方法,和任何测试一样,分析有成功和失败之分。

    \\

    真负:

    \\
    • 代码有用\

    假负:

    \\
    • 测试代码 \\t
      • 测试的功能只有测试使用。这可能是TDDBDD过程的产物,在这种情况下,它可能用来追溯不需要或者未满足的需求。\\t
      \\t
    • 死代码 \\t
      • 虽然被调用了,但代码什么都没做,这种调用可以删除。\\t\t
      • 可以是重复代码。\\t\t
      • 注意,可以认为已废弃代码距离死代码只差一步。\\t
      \

    真正:

    \\
    • 故意放弃的开发 \\t
      • 方法已经添加,但环境变了,这些方法不会再使用了\\t
      \\t
    • 未完成的变更 \\t
      • 代码已变更,有些方法所有引用都删除了,但后续提交时没有将其考虑在内。这种代码有时候称为oxbow代码。它可能是重构的意外产物。\\t
      \\t
    • 未使用的变量 \\t
      • 但愿IDE会生成编译器警告和/或报告。\\t
      \

    假正:

    \\
    • 工具缺陷 \\t
      • 该工具没有检测使用反射代码契约的代码,它依赖构建或测试过程的错误来检测这些代码。\\t
      \\t
    • 回归 \\t
      • 在开发过程中,经过修改,一个方法不再调用标识为死亡的方法,此时,这种情况就会出现。显然,没有测试识别这种回归。\\t
      \\t
    • 缺少条件编译 \\t
      • 通常,这是指缺少#if DEBUG保护。Roslyn已经用于Release构建,这个过程会识别相应二进制文件中的代码膨胀。\\t
      \\t
    • 无意放弃的开发 \\t
      • 这是在特定的测试数据下发现的,但没有相应的测试。通常来说,我希望这是一个暂时的问题,随着代码增加会自然消失。\\t
      \\t
    • 在子系统中未使用 \\t
      • 分析低效的代码,找出使用该方法的地方。虽然反射也会导致这个问题,但这主要出现在公共方法分析时。一个常见的例子是执行测试辅助功能的时候没有相应的测试代码,不过,那会抛出一个设计问题,定位到测试辅助代码。\\t
      \\t
    • 仅限调试器使用的方法 \\t
      • 在附加了调试器时(可能是Release构建),有些项目会有方法打印出状态。这些方法应该以某种方式标记出来,那样就不用分析它们了。\\t
      \

    需要注意,工具失败不一定是因为推理不足。其中有些情况是其他工具的处理范畴,如编译器警告应该标记部分问题,重复代码检测器也有用。

    \\

    冗余代码的影响

    \\

    假负和真正都可以视为YAGNI的实例,这就需要我们注意以下事项:

    \\
    • \\t

      臃肿的源代码

      \\\t
      • 以前,我遇到过文本编辑器无法打开大文件的情况。\\t\t
      • 开发人员的认知受限于大脑中可以同时容纳的独立实体的数量,通常认为是7个,因此,删除方法可以提升推理能力。\\t\t
      • 所有开发人员用来阅读和理解源代码的时间很可能都是浪费。\\t\t
      • 现代IDE通常都是从源代码生成抽象语法树,因此,冗余代码降低了它们的速度。\\t
      \\t
    • \\t

      臃肿的可执行文件

      \\\t
      • 在手机上,这尤其是个问题,为了释放存储,升级会删除应用,最终,当功能受到过度损害,就不得不升级设备了。\\t
      \\t
    • \\t

      臃肿的运行时

      \\\t
      • 为黑客提供了更大的攻击面,让他们发现安全问题。\\t\t
      • 部分面向嵌入式设备的应用程序会在启动时分配所有的动态内存,因此,冗余代码会减少可用堆的大小。\\t
      \\t
    • \\t

      降低性能

      \\\t
      • 在特定的情况下,死代码执行会浪费处理器周期。\\t
      \\t
    • \\t

      降低可靠性

      \\\t
      • 在特定的情况下,死代码执行可能导致崩溃。\\t
      \\t
    • \\t

      降低可维护性

      \\\t
      • 如果执行到冗余代码时停下了,那么行为将不可预测,如Knight Capital损失了4.4亿美元。\\t\t
      • 代码覆盖率可能会因假负而升高,因假正而降低。\\t\t
      • 不必要的测试会降低测试套件的执行速度。\\t\t
      • 静态分析工具不得不处理更多的代码,并因此效率降低。\\t\t
      • 软件的设计/架构看起来比实际情况复杂。考虑到冗余代码会导致各种各样的问题,最好是将其视为代码异味。\\t
      \

    管理已有的冗余代码

    \\

    处理冗余代码有四种方法:

    \\
    • \\t

      忽略

      \\\t
      • 虽然会增加维护成本,但至少可以通过编译。\\t\t
      • 不推荐。\\t
      \\t
    • \\t

      自动从可执行文件中移除

      \\\t
      • 编译型语言在链接器中使用任意一种死代码移除选项移除。\\t\t
      • 动态语言使用Tree Shaking在运行时移除。\\t\t
      • 也不推荐。\\t
      \\t
    • \\t

      屏蔽

      \\\t
      • 注释掉或者使用预编译指令。\\t\t
      • 使用IDE提供的代码/注释折叠避免看到它。\\t
      \\t
    • \\t

      删除

      \\\t
      • 借助源代码控制保留旧版本供参考,可以随时查看。\\t\t
      • 或者你可能永远不再需要它。\\t
      \

    如果要变更源代码,任何变更都需要由熟悉这段代码的人审核,务必保证代码真冗余。

    \\

    管理新开发项目

    \\

    假如目标是在开发过程中不引入新的冗余代码,需要制定什么策略?如果允许部分提交,代码库中可能就会增加暂时冗余的代码。这种情况可以通过特性分支来缓解,只有当测试已覆盖且源代码通过了静态分析才合并进主分支。特性分支的另外一个好处就是在放弃开发后可以将分支保持在未合并状态。

    \\

    关于作者

    \\

    Zebedee Mason 是一名有着25年CAD/CAM/CAE行业经验的数值分析员,他最近搬到了SLAM。多年来,他一直从事与遗留代码库(其中有些在他还是个孩子的时候就已经创建)相关的工作,把许多学术代码改写成商业软件的组件,他甚至还重写了一些原始算法(商业秘密,无法确认)。这种背景使他对软件工具非常了解,他也编写了若干提高生产力的工具,即使以前的开发人员和IT管理人员做出了有用的选择。

    \\

    查看英文原文:Detecting and Analyzing Redundant Code

    展开全文
  • 在我们打包之后,有很多模块导出的代码,我们可能根本没有用到过,这些冗余代码也跟着一起打包,这不是赤裸裸的浪费带宽嘛。还有我们使用的框架,里面所有的模块我们都用到了?不见得吧。现在,又出现了tree-

    我们知道,前端页面越来越多,导致我们的js,css文件越来越多。

    在请求的时候,就会占有很多带宽。

    前端er们就发明了打包工具。

    把所有的js文件打包成一个文件,这样减少了http的请求数。

    但是,协作开发模式。在我们打包之后,有很多模块导出的代码,我们可能根本没有用到过,这些冗余代码也跟着一起打包,这不是赤裸裸的浪费带宽嘛。

    还有我们使用的框架,里面所有的模块我们都用到了?不见得吧。

    现在,又出现了tree-shaking技术。

    在打包之前,消除没有被调用过的冗余代码。

    前端开发再也不用会臃肿喽。

    1.rollup.js

    这只框架就可以做到。
    详细了解点这里吧:
    http://www.cnblogs.com/vajoy/p/5518442.html

    2.HTTP/2 vs 打包

    http/1 每个请求占用1个tcp连接。

    http/2 所有请求一起占一个连接,完全多路复用。大家并行一起下载,还不用产生多余的tcp连接,是不是就不需要打包了?加载多个模块不再是一个严重的性能问题。一些人认为模块打包不再需要了。这当然是有可能的。

    展开全文
  • Coverity代码静态检测工具介绍

    千次阅读 2019-08-09 09:51:20
    Coverity代码静态检测工具介绍 ...

    使用了Coverity代码静态检测工具。功能很强大,超乎我的期望。主要功能如下:

    1. 列出不会被执行到的代码
    2. 列出没被初始化的类成员变量
    3. 列出没有被捕获的异常
    4. 列出没有给出返回值的return语句
    5. 某个函数虽然有返回值,但调用该函数的地方没有用到它的返回值,这也会被列出来
    6. 列出没有被回收的new出来的对象
    7. 列出没有被关闭的句柄
    8. 精确定位到代码行,并提供逐层展开函数的功能
    9. 列出可能的数值类型溢出。例如,无符号int数做 ++ 操作,可能导致int溢出,都会被检测到。
    10. 什么地方该用&位运算,而不应该用|位运算,都能定位出来并作出建议
    11. ostream在一个函数中被修改了格式,但退出该函数之后没有将ostream恢复成先前的格式,也会被检测到
    12. ……

    貌似程序中的所有可能分支,所有new出来的对象何时被销毁,所有可能的异常是否被代码捕获,它都能扫描到。真不简单啊。

    我用到的Coverity是部署在企业内部的,通过https页面的方式向程序员报告扫描的安全隐患。

    具体的安全隐患页面很专业。但有个缺点,点击一个security issue list中的一个item进入子页面,而后再点浏览器的返回按钮后,不能返回先前的页面,而是返回所有security issue list的第一个list页面,在这点上,用户体验很糟糕,因为本来出来的列表就很长了,看的很晕了,返回页面又不是先前的页面,导致很多时间都花费在重新找寻先前的list页面上了。

     

    解决办法:

    1. 点击扫描列表上的"File"列标签,将扫描出来的列表按照文件排序,然后点击右上角的某个按钮,导出成.csv格式文件。
    2. 用Excel打开.csv文件,添加一列,记录状态,比如:Done, In progress, Cannot decide 等等。
    3. 按照.csv文件条目的顺序自上而下挨个处理。每做完一个条目,就更新对应的.csv文件中对应行的新加列。
    4. 注意,每当要处理下一个条目时,从.csv文件中copy出该条目的ID号,粘贴到Coverity页面的右上角的搜索栏中,搜索,并进入具体页面。这样做,可以免除上面提到的“因条目过多导致前进后退时迷失方向”的问题。

     

    按照这样的方法,处理完所有的条目,会得到一个完整的条目状态列表。如果有未解决的条目的话,接下来就只关注这些条目。不会出现条目遗漏,也不会因条目过多而迷失方向。即安全又节省时间。

     

    Coverity多次检测同样的代码(两次之间,被检测的代码有改动),不会覆盖先前的报告,即第一次检测得到的report item number还是有效的,不会被新的report item覆盖,搜索它仍然可以查看先前检测出来的问题。

     

    其实Coverity检测也是有缺陷的,毕竟它只是个辅助工具,不能完全达到人的水平,有时报的问题其实不是问题。例如,它可能报告有个指针pNext在没有在被赋值或被赋值为NULL的情况下做pNext++操作存在风险,其实这个pNext指针在pNext=NULL下面的一个while循环中被赋值了,于是,这个报告可以被忽略。像这样的误报,目前还没找到方法有效地过滤掉。

     

    附:

    CERT C Secure Coding Standard

    CERT C++ Secure Coding Standard

    Coverity C++ checkers of CERT C++ Secure Coding Standard

     

    ===================================

     

    转自:http://www.broadskytech.com/tabid/90/ArticleID/117/Default.aspx

     

     

     

     

    产品名称:Coverity源代码缺陷分析工具
    产品型号:Coverity

     

    Coverity公司是由一流的斯坦福大学的科学家于2002年成立的,产品核心技术是1998年至2002年在斯坦福大学计算机系统实验室开发的,用于解决一个计算机科学领域最困难的问题,在2003年发布了第一个能够帮助Linux、FreeBSD等开源项目检测大量关键缺陷的系统,Coverity是唯一位列IDC前10名软件质量工具供应商的静态分析工具厂商,被第三方权威调查机构VDC评为静态源代码分析领域的领导者,市场占有率处于绝对领先地位。

    美国Coverity公司提供最先进的和可配置的用于检测软件缺陷和安全隐患的静态源代码分析解决方案,Coverity将基于布尔可满足性验证技术应用于源代码分析引擎,分析引擎利用其专利的软件DNA图谱技术和meta-compilation技术,综合分析源代码、编译构建系统和操作系统等可能使软件产生的缺陷,Coverity是第一个能够快速、准确分析当今的大规模(几百万、甚至几千万行的代码)、高复杂度代码的工具,Coverity解决了影响源代码分析有效性的很多关键问题:构建集成、编译兼容性、高误报率、有效的错误根源分析等。

    产品功能

    Coverity Static Analysis (也称Prevent)是检测和解决C、C++、Java和C#源代码中最严重的缺陷的领先的自动化方法。通过对您的构建环境、源代码和开发过程给出一个完整的分析,Prevent建立了获得高质量软件的标准。

    静态源代码分析允许我们在软件开发生命周期的早期阶段发现和修复缺陷,节省数以百万计的相关成本。Prevent是业界标准,因为只有Coverity理解和掌握静态源代码分析技术所具有的严格的要求。

    l Coverity了解构建系统——Prevent通过在操作系统流程这个层次监测您的构建系统来获得每一个操作的清晰视图,展现您的软件。

    l Coverity了解源代码——Prevent 检测比当今市场上任何其他工具都更多的源代码信息;代码的结构、含义和意图都被用来揭示其中的严重的错误和不安全性。

    l Coverity了解开发者——Prevent 通过一个客户化的工作流、功能强大的分析器和易于使用的工具,能够使缺陷在几分钟内被定位,解决缺陷。

     

    平台支持:

    目标机平台:PowerPC,ARM,MIPS,x86,SPARC,XScale,SH,Codefire,SH,ST 20,8051,

    TI DSP C3000/C6000/C55x/C54x, Motorola 68HC05/68HC11, Freescale 68HC08/HCS08/68HC12/HCS12X, Renesas M16C/H8/M32C,

    C51/C166/C251等;

    嵌入式操作系统:VxWorks,Embedded Linux,QNX,RTEMS,ucOS,WinCE,Windows Embedded,PalmOS,Symbian, pSOS、Nucleus、ThreadX, INTEGRITY、OSE,UCLinux,国产OS等等;

    主机平台:Apple Mac OS X 10.4,Cygwin,FreeBSD,HPUX,Linux,Mac OS X , NetBSD(2.0) ,Solaris Sparc ,Solaris X86,Windows等;

    支持的编译器:ARM ADS/RVCT,Freescale Codewarrior ,GNU C/C++ ,Green Hills

    HP aCC,i-Tech PICC,IAR,Intel C/C++ ,Marvell MSA ,Microsoft Visual C++,QNX ,Renesas ,Sun C/C++ ,TI Code Composer ,Wind River,

    支持任何其他的ANSI C兼容的编译器.

    产品特点及优势

    Coverity产品专长于准确的查找最严重和最难检测的缺陷,具有以下优势:

    精确性——Coverity Prevent的特别之处在于查找精确,具有业界最低的误报率(小于15%)。当许多其他的源代码分析产品以很高的误报率使得其不可用时,Coverity的虚假路径裁剪、统计分析和其它创新减少了误报的产生。附加的配置和微调能够进一步减少误报率。

    分析的深度——Coverity Prevent提供过程间数据流分析和统计分析,评审整个程序的交互和所有的可能的路径。Coverity Prevent检查企业特定的APIs和标志使用中的不一致性,精确的检测QA、安全评审和现场会发生的缺陷。

    分析的广度——Coverity Prevent采用查找最严重的错误的最先进的技术,提高您的产品的质量和安全性。Coverity Prevent可以检测严重的问题,例如系统崩溃、内存泄漏、内存错误、不确定行为、并发缺陷和安全性问题。

    低拥有成本——Coverity Prevent无缝的与您已有的环境集成,能够快速部署和配置。

    可扩展——Coverity Prevent使您能够在我们的强大的分析引擎上创建定制的检查器,来满足您的开发组织和代码的特定需求。您可以设置定制的规则,来强化编码标准或者检测代码中的错误。

    强大的可配置性——Coverity Prevent能够快速的分析您的代码,不需要写测试用例。通常百万行的代码仅需要几个小时,其他的工具需要几天,甚至几个星期来分析大规模代码。

    广泛的平台支持——Coverity Prevent支持最广泛的编译器和硬件平台,可最大化的满足用户多平台的测试要求。

     

    以 C/C++为例

    Prevent 分析引擎使得缺陷检测检查器拥有源代码最精确的描述。

    Prevent 针对C/C++的分析引擎

    引擎功能
    路径流程引擎通过构建一个表示经过每一个函数的所有的路径的图表分析您代码中的每个函数的控制流
    数据追踪引擎用于分析从程序中每个路径中的声明收集的所有的整型和布尔型等数据
    统计引擎用于分析您的代码作为一个整体的行为特征
    过程间调用总结引擎一个主要的创新,使得Prevent 可以执行整个程序的分析,分析文件间和模块间的任何层次的复杂的调用链
    类型流程引擎用于提高C++分析中依赖于类层次关系的报告的结果的精度
    虚假路径引擎用于分析每个分支条件,以确定在当前路径它将是真、假或不确定
    加速引擎保存横越每个路径时的每个缺陷分析所收集的信息;消除冗余路径,不需要横越任何不必要的路径来找到最多的缺陷
    数据传播引擎把过程间调用总结引擎产生的所有总结和数据追踪引擎记录的所有数据汇总起来,是Coverity特有的、上下文敏感的过程间分析能力的关键
    增量分析引擎通过缓存分析数据来提高性能,以便后续的分析仅需要包含变化的数据

     

    能够发现的C/C++缺陷(部分)C/C++安全性问题(部分)

    并发

    • 死锁
    • 错误使用的阻塞调用

    性能下降

    • 内存泄漏
    • 文件句柄泄漏
    • 定制的内存和网络资源泄漏
    • 数据库连接泄漏

    导致崩溃的缺陷

    • 空指针引用
    • 释放后引用
    • 多次释放
    • 不正确的内存分配
    • 不匹配的数组新建/删除

    不正确的程序行为

    • 逻辑错误导致的死代码
    • 未初始化变量
    • 负数的无效引用

    不正确的APIs使用

    • STL使用错误
    • API错误处理
    展开全文
  •  本文是一个静态代码分析工具的清单,但是为公司产品需要付费使用。共有37个公司,有些公司包含多个工具。其中27个公司有多语言 工具,1个公司为PHP工具、2个公司为.NET工具、1个公司为Ada工具、4个公司为C++...
  • OCLint是一种静态代码分析工具,可以通过检查C,C ++和Objective-C代码来提高质量并减少缺陷。 它寻找编译器看不到的潜在问题,例如: 可能的错误-if / else / try / catch / finally语句为空 未使用的代码-未...
  • OCLint是一种静态代码分析工具,可以通过检查C,C ++和Objective-C代码来提高质量并减少缺陷。 它寻找编译器看不到的潜在问题,例如: 可能的错误-if / else / try / catch / finally语句为空 未使用的代码-未使用的...
  • 好的代码一定是整洁的,并且能够帮助阅读的人快速理解和定位。好的代码可以加快应用的开发迭代速度,不必花过多的时间来修复 bug 和完善代码。好的代码不但能够使得新的项目成员更容易加入项目,...
  • 大项目都需要小组中的多人共同完成,但是每个人都有自己的编码习惯,甚至很多都是不正确的。...Checkstyle是一个开源代码分析工具,能够帮助开发人员保证他们的代码遵循一定的代码规范。Checkstyle通过不断地
  • 静态代码检查工具 FindBugs

    千次阅读 2014-03-21 14:52:26
    静态分析工具承诺无需开发人员费劲就能找出代码中已有的缺陷。当然,如果有多年的编写经验,就会知道这些承诺并不是一定能兑现。尽管如此,好的静态分析工具仍然是工具箱中的无价之宝。在这个由两部分组成的系列文章...
  • java代码分析及分析工具

    千次阅读 2012-09-04 01:09:20
    代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰。 作为一个互联网电子商务网站的业务支撑系统,业务复杂不言而喻。从09年开始一直沿用到现在,中间代码经过了多少人的手,留下了多少的坑,已经记不清楚了,...
  • Java代码质量检查工具

    万次阅读 2018-05-13 08:27:00
    这样就需要我们需要团队开发,在我们团队中开发人员的经验、代码风格样式都不一致,以及缺乏统一的标准,从而导致我们的整个项目的的代码难以阅读,不便于后期维护。这几天在研究代码质量管理,根据在网上搜集的资料...
  • java开发之消除冗余代码的3种方法

    千次阅读 2020-05-13 21:00:24
    同,如果用代码来实现蛋糕制作流程,要写大量重复代码,容易产生BUG,我们可以使用工厂模式和模板方法模式来避免重复。 首先定义一个蛋糕类Cake: @Data public class Cake { // 蛋糕名称 String cakeName; ...
  • 静态代码分析工具大全

    千次阅读 2019-12-21 22:53:14
    本文是一个静态代码分析工具的清单,但是为公司产品,需要付费使用。共有37个公司,有些公司包含多个工具。其中27个公司有多语言工具,2个公司为.NET工具、1个公司为Ada工具、4个公司为C++工具、1个公司为Java工具、...
  • Java代码质量检查工具及使用案例

    万次阅读 2016-06-24 14:47:26
    Java代码质量检查工具及使用案例 在现在的软件开发中,由于软件的复杂度越来越高,业务也覆盖很广,各个业务模块业务错综复杂。这样就需要我们需要团队开发,在我们团队中开发人员的经验、代码风格样式都不一致,...
  • lint 代码审查工具

    千次阅读 2015-01-05 23:30:27
    lint是最著名的C语言工具之一,是由贝尔实验室SteveJohnson于1979在PCC(PortableC Compiler)基础上开发的静态代码分析,一般由UNIX系统提供。与大多数C语言编译器相比,lint可以对程序进行更加广泛的错误分析,是一...
  • 【Tools】Coverity代码静态检测工具

    千次阅读 2018-01-13 16:45:18
    Coverity静态分析工具的功能很强大,超乎我的期望。主要功能如下: 列出不会被执行到的代码列出没被初始化的类成员变量列出没有被捕获的异常列出没有给出返回值的return语句某个函数虽然有返回值,但调用该函数的...
  • pc-lint静态代码检查工具

    千次阅读 2012-07-28 02:57:42
    静态代码检查工具PC-Lint(一)  作者:星轨(oRbIt)  E_Mail :inte2000@163.com  [本文部分内容和例子都来自于PC-Lint用户手册,翻译得时候加上了点自己的理解] 摘要:C/C++语言的语法拥有其它语言所...
  • FindBugs Java代码审查工具

    千次阅读 2015-01-05 23:34:47
    简介编辑 尽管如此,好的静态分析工具仍然...代码质量工具的一个问题是它们容易为开发人员提供大量但并非真正问题的问题——即 伪问题(false positives)。出现伪问题时,开发人员要学会忽略工具的输出或者放弃
  • Lombok他是一个通过注解方式来减少你的POJO类的getter和setter等方法的一个工具,我这里演示的在Android Studio中的使用方式,当然如果你使用的是idea那么这方法也通用
  • Android Lint 是 SDK Tools 16(ADT 16)开始引入的一个代码扫描工具,通过对代码进行静态分析,可以帮助开发者发现代码质量问题和提出一些改进建议。除了检查 Android 项目源码中潜在的错误,对于代码的正确性、...
  • 这个python模块提供了一个基于'tar'的打包器/解压缩器,可以减少打包前的冗余。 有关此模块的信息可以在这里找到: 为什么是“ uppackpack”? 一般而言,经典的压缩工具效果很好。 但是在非常特殊的情况下,...
  • 受自己阅历和工作经验,项目工期的影响,很多时候我们检查自己的代码很难发现错误(不知道大家有没有这个感觉),或者我们不觉着这块代码有问题,这时候就需要用到“静态代码检测工具”,Andro...
  • 代码静态分析工具PC-LINT安装配置--step by step

    万次阅读 热门讨论 2006-05-20 12:40:00
    代码静态分析工具PC-LINT安装配置--step by step 作者:ehui928 2006-5-20 PC-Lint是C/C++软件代码静态分析工具,你可以把它看作是一种更加严格的编译器。它不仅可以检查出一般的语法错误,还可以检查出那些虽然...
  • 静态代码检查工具PC-Lint(一)

    万次阅读 2006-06-23 09:44:00
    如果能够在代码提交测试之前发现这些潜在的错误,就能够极大地减轻测试人员的压力,减少软件项目的除错成本,可是传统的C/C++编译器对此已经无能为力,这个任务只能由专用的代码检查工具完成。目前有很多C/C++静态...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,091
精华内容 21,636
关键字:

代码减少冗余工具