While working with legacy systems, you find often many areas in your application that are a mess. Often a mess originates in the problem that it's unclear where to put all the things. But even if you would find a structure that fits your needs, you are always restricted by time and budget which absence is the cause that you cannot put all the software entities where they belong to. In reality, you can only invest a little amount of time for refactoring tasks which prohibits a holistic improvement of your software system.
But there is another way for long-running, continuous refactorings that I'll show you in this blog post. The idea originates from the outstanding book " Object-Oriented Reengineering Patterns" (the "OORP book", freely available) that shows us many ways to tackle legacy code. One pattern that inspired me for this proof of concept "Tie Code and Questions" (p. 121) that says
Keep the questions and answers concerning your reengineering activities synchronized with the code by storing them directly in the source files.
This pattern suggests that we write down our knowledge about the code directly in, well, the code itself. The authors even suggest a ways to do it via special comments of by the use of annotations.
We, too, use this approach for finding hidden structures in software systems by annotating parts of the system with special Java annotations. In our example, we use an annotation named @Pattern
to identify different architectural and design patterns that are in the code. We'll do that on different levels of code: Packages, classes and methods.
But only marking code parts isn't sufficient to get an overview over the chaos. Thus, we utilize the annotated code by using jQAssistant and Neo4j. With these tooling, we can read out those code parts that we've annotated and inspect the dependencies between our findings.
I'll explain how you can do this by annotating some code parts of the Spring Framework demo application "PetClinic", that comes with a fully integrated jQAssistant code analysis feature.
The first part of our exercise is easy: We need a special annotation in our code base that we can use to mark specific parts of our code:
package org.springframework.samples.petclinic.architecture;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Marker for a code entity that implements a specific architecture or design patten.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Pattern {
String value();
}
import py2neo
import pandas as pd
graph = py2neo.Graph()
query = """
MATCH
(a:Artifact { name: "spring-petclinic"})-[*]->
(packageInfo:Java { name: "package-info"})-[:ANNOTATED_BY]->
(anno:Annotation)-[:OF_TYPE]->(annoType:Type { name: "TechnicalAspect"}),
(anno)-[:HAS]->(v:Value),
(packageInfo)<-[:CONTAINS]-(p:Package)-[:CONTAINS*]->(t:Type)
MERGE (ta:TechnicalAspectTEST {
name : v.value,
type : "per_package",
direct : false})
MERGE (ta)<-[:IS_A]-(t)
RETURN t.name, v.value
"""
pd.DataFrame(graph.data(query)).head()
t.name | v.value | |
---|---|---|
0 | NamedEntity | DataModel |
1 | Pet | DataModel |
2 | PetType | DataModel |
3 | Person | DataModel |
4 | BaseEntity | DataModel |
graph = py2neo.Graph()
query = """
MATCH
(a:Artifact { name: "spring-petclinic"})-[:CONTAINS*]->
(t:Type)-[:ANNOTATED_BY]->(annotation:Annotation)-[:OF_TYPE]->
(annotationType:Type { name: "TechnicalAspect"}),
(annotation)-[:HAS]->(v:Value)
WHERE t.name <> "package-info"
MERGE (ta:TechnicalAspect)<-[:IS_A]-(t)
SET
ta.name = v.value,
ta.type = "class_annotation",
ta.direct = true
RETURN DISTINCT t.name, v.value
"""
pd.DataFrame(graph.data(query)).head()
t.name | v.value | |
---|---|---|
0 | EntityUtils | PersistenceMechanism |
1 | CallMonitoringAspect | Monitoring |
graph = py2neo.Graph()
query = """
MATCH
(m:Method)<-[:DECLARES]-(t:Type)-[:IS_A]->(existingTa:TechnicalAspect)
MERGE (ta:TechnicalAspect)<-[:IS_A]-(m)
SET
existingTa.name = ta.name,
ta.type = "derived_from_class"
RETURN DISTINCT m.name, ta.name
"""
pd.DataFrame(graph.data(query)).head()
m.name | ta.name | |
---|---|---|
0 | setName | None |
1 | toString | None |
2 | <init> | None |
3 | getName | None |
4 | None | None |
graph = py2neo.Graph()
query = """
MATCH
(a:Artifact { name: "spring-petclinic"})-[:CONTAINS*]->
(t:Type)-[:DECLARES]->(m:Method)-[:ANNOTATED_BY]->(annotation:Annotation)-[:OF_TYPE]->
(annotationType:Type { name: "TechnicalAspect"}),
(annotation)-[:HAS]->(v:Value)
WHERE t.name <> "package-info"
MERGE (ta:TechnicalAspect)<-[:IS_A]-(m)
SET
ta.name = v.value,
ta.type = "method_annotation",
ta.direct = true
RETURN DISTINCT m.name, v.value
"""
pd.DataFrame(graph.data(query)).head()
m.name | v.value | |
---|---|---|
0 | findPetById | DataModel |