Sunday, 20 December 2015

Software analysis using JQAssistant

Last week, I was playing a bit with a very interesting tool: JQAssistant. JQAssistant parses and analyses source files and loads them into a Neo4j graph database. By default, JQAssistant will analyze Java source code files, but JQAssistant is built using a plugin based architecture. So, there are various plugins available. For example, there are plugins to analyse Maven Files, XML Files or JUnit test results. Hopefully, there will also be other language plugins be added in the future. Especially, C++ would be nice!
So, why would you want to have your source code in a graph database? Because this gives you a very powerful way to analyze your source code! JQAssistant creates a graph database with nodes and relations such as:
  • TYPE declares METHOD
  • TYPE declares FIELD
  • TYPE extends TYPE
  • TYPE implements TYPE
  • METHOD invokes METHOD
  • METHOD throws METHOD
  • METHOD reads FIELD
  • METHOD writes FIELD
  • METHOD annotated_by METHOD
  • ...
Each of the nodes also has properties like full qualified name, visibility, md5 hash (of classes) or signature and cyclomatic complexity for methods. As you can see, a big graph with lot's of information is created. This graph can now be queried for certain properties using the powerful Neo4j language. Several examples that come to my mind are:
  • It can be used to ensure architecture guidelines, such as "frontend classes may only call service layer classes, but not backend classes".
  • Ensure naming guidelines, "e.g. all Service classes must end in *Service or *ServiceImpl and frontend classes in a certain package must only be *Model or *Controller".
  • All unit tests must call an Assert.* method - otherwise the test does not test anything.
  • What are the most complex methods in my code that are not called from unit tests?
  • Analyze properties of your source, e.g. number of classes, methods etc.

Important to note is that there is a Maven plugin to execute JQAssistant during builds. This allows you to run JQAssistant queries during the build and for example let's you fail the built if certain architecture guidelines are not met.
We are doing this for QualityCheck already, even though we have only implemented a very simple check so far. This checks verifies that all unit tests match our name pattern ".*(Test|Test_.*)". Here is the relevant JQAssistant configuration my-rules.xml.
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">

    <constraint id="my-rules:TestClassName">
        <requiresConcept refId="junit4:TestClass" />
        <description>All JUnit test classes must have a name with suffix "Test".</description>
        <cypher><![CDATA[
            MATCH
                (t:Junit4:Test:Class)
            WHERE NOT
                t.name =~ ".*(Test|Test_.*)"
            RETURN
                t AS InvalidTestClass
        ]]></cypher>
    </constraint>

    <group id="default">
        <includeConstraint refId="my-rules:TestClassName" />
    </group>

</jqa:jqassistant-rules>
JQAssistant is even more useful for applications using QualityCheck! For example, QualityCheck encourages you to use the methods from Check.*, such as Check.notNull in all public methods of your classes and annotate methods usings @ArgumentsChecked. So, you could use JQAssistant> to find methods, annotated with @ArgumentsChecked, but who do not call any Check.* methods:
--
-- Find all methods having @ArgumentsChecked but not calling Check.* methods
--
MATCH
 (checkType:Type),
 (type:Type)-[:DECLARES]->(method:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(checkAnnotationType:Type)
WHERE 
 checkType.fqn = 'net.sf.qualitycheck.Check' AND 
 method.visibility = 'public' AND
 checkAnnotationType.fqn='net.sf.qualitycheck.ArgumentsChecked' AND 
 NOT type.fqn =~ ".*(Test|Test_.*)" AND 
 NOT (method:Method)-[:INVOKES]->(:Method)<-[:DECLARES]-(checkType:Type)
RETURN
 method.signature AS Method, type.fqn as Type;
Additionally, the other way round should be checked, i.e. find all methods who call Check.* but that do not have the annotation.
--
-- Find methods calling Check.* but not having @ArgumentsChecked in all non-test classes
--
MATCH
 (checkType:Type)-[:DECLARES]->(checkMethod:Method),
 (type:Type)-[:DECLARES]->(method:Method)-[:INVOKES]->(checkMethod:Method),
 (checkAnnotationType:Type)
WHERE 
 checkType.fqn = 'net.sf.qualitycheck.Check' AND 
 method.visibility = 'public' AND
 checkAnnotationType.fqn='net.sf.qualitycheck.ArgumentsChecked' AND 
 NOT type.fqn =~ ".*(Test|Test_.*)" AND 
 NOT (method:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(checkAnnotationType:Type)
RETURN
 checkMethod.name AS CHECK_METHOD_CALLED, method.signature AS Method, type.fqn as Type;
As a software architect, you should now create a third rule, which indicates which methods must use check and must have the annotation, i.e. all public methods in classes call "*ServiceImpl".
I hope this was a small introduction and gave you a small impression of the power of this tool. I would be happy to get some feedback on this and also share some questions that arise for the future of this.
  • Is there C++ support planned? Is someone working on a C++ plugin to parse C++ code?
  • How does this perform with big systems? Does anyone have used this on a larger system?
  • Is there a Sonar Qube integration? Can I show failed checks as violations or in charts in Sonar?
  • What are your use cases and queries?