Catalogue of refactorings for model transformations

Tweet about this on TwitterShare on FacebookBuffer this pageShare on RedditShare on LinkedInShare on Google+Email this to someone


Last issue of JOT, apart from the Special issue for the ICMT conference, it also included our paper Manuel Wimmer, Salvador Martínez, Frédéric Jouault, Jordi Cabot, A Catalogue of Refactorings for Model-to-Model Transformations. You can read here the full version or continue reading this post to read the summary of the paper prepared by Salva.

Current research on model transformation focuses on developing languages for specifying transformations. However, there are no available techniques focusing on the maintainability of existing transformations. Such support is clearly needed, e.g., to improve the readability of transformations and to facilitate their evolution in response to changes on the transformation requirements and/or the source/target. metamodels used in the transformation. A maintainable, reusable, and extensible set of transformation definitions is a key aspect in any high-quality model-based solution.

In the area of object-oriented programming, refactorings are the technique of choice for improving the structure of existing code without changing its external behavior. Unfortunately, no catalogue of refactorings for model transformation exists. Given the potential complexity of model transformations, manual modifications may lead to unwanted side-effects and result in a tedious and error-prone maintenance process.

In this sense, and to tackle this problem, we provide a refactoring catalogue for rule-based M2M transformations. The refactorings may improve not only quality attributes related to maintainability such as readability, reusability, and extensibility of the transformations, but also the performance of transformations. The refactorings are structured into four categories:

 

  1. Renaming : This category comprises refactorings needed for renaming identifiers as well as their references within the transformation. As in programming languages, one of the easiest but nonetheless very useful things one can do to improve code is simply changing names. In transformation languages, rules and helpers with proper names will give a precise idea of what functionality they are providing, thus saving the time needed to go to the definition itself.
  2.  Restructuring : Transformations are composed by rules for generating output elements from input elements and by additional helpers for calculating certain values. Thus, refactorings are needed for improving the structure of a transformation by means of restructuring and introducing rules and helpers (splitting/merging rules, inlining helpers,…). The refactorings of this category deal with the problem that transformation rules, especially matched rules, tend to grow big, i.e., one rule does most of the work by generating a multitude of elements using a large output pattern block consisting of a huge amount of output pattern elements.
  3.  Inheritance-related : The refactorings from this category deal with the extraction or elimination of commonalities between rules by introducing and removing inheritance between rules.
  4.  OCL Expression Optimization : Finally, transformation languages are built upon OCL for queries and computation of feature values. Thus, also the improvement of OCL expressions should be possible through the execution of refactorings (e.g. to shorten navigations).

 

Example.

We illustrate and motivate the need for transformation refactorings by means of the transformation scenario presented in the following. The goal of this transformation scenario is to transform UML class diagrams into Entity Relationship (ER) diagrams.



module UML2ER;
create OUT : ER from IN : UML;

helper context UML!Class def: allClasses() : Sequence(UML!Class) =
self.superClasses->iterate(e; acc : Sequence(UML!Class) = Sequence {} |
acc->union(Set{e})->union(e.allClasses()) );

rule Class {
   from
	s: UML!Class
   to
	t: ER!EntityType (
	   name < s.name,
	   features <- attributes,
	   features <- weakReferences,
	   features <- strongReferences
	),
	attributes : distinct ER!Attribute foreach(a in
	s.allClasses().including(s).flatten()->collect(e | e.ownedProperty).flatten()
	->select (e | not e.primitiveType.oclIsUndefined())) (
	   name <- a.name,
	   type <- a.primitiveType
	),
	weakReferences : distinct ER!WeakReference foreach(a in
	s.allClasses().including(s).flatten()
	->collect(e | e.ownedProperty).flatten()
	->select(e | not e.complexType.oclIsUndefined() and not e.isContainment)) (
	   name <- a.name,
	   type <- a.complexType
	),
	strongReferences : distinct ER!StrongReference foreach(a in
	s.allClasses().including(s).flatten()
	->collect(e | e.ownedProperty).flatten()
	->select(e | not e.complexType.oclIsUndefined() and e.isContainment)) (
	   name <- a.name,
	   type <- a.complexType
	)
}

Although the previous transformation does the job, i.e., it correctly produces ER models from UML ones, it has several bad smells that compromise its quality in terms of maintainability and performance:

 

  1. The transformation consists of one complex rule doing all the work, instead of decomposing the transformation based on the different types of elements in the source pattern. This bad smell may be seen as the rule-based paradigm equivalent to the well-known “God Class” bad smell in object-oriented programming. Thus, we name this bad smell “God Rule”.
  2. Duplicated code hampers evolution. For instance, if the reference ownedProperty is renamed in the source metamodel, three complex OCL expressions have to be adapted in the transformation.
  3.  Unnecessary repetitive calls that compromise the performance. The helper allClasses()  is called several times for the same element. This results in recalculating the return value every time.
  4.  Use of deprecated constructs. The distinct-foreach construct used in the transformation has been classified as deprecated

Transformation designers may not be aware of these problems or are unsure of how to solve them without breaking the transformation. The refactoting catalogue clearly improves this situation by contributing to the body of knowledge of transformation engineering.

In order to solve the related problems, we have applied the catalogue of refactorings over the example. First, the existing helper allClasses  is converted from an operation helper to an attribute helper, and subsequently, the additional helper getAllProperties is produced by applying the Extract Helper  refactoring for eliminating the duplicated code for querying all direct and indirect possessed properties of a class. Second, three lazy rules (Attribute , WeakReference , and StrongReference ) have been created by extracting the distinct-foreach constructs from the rule EntityType . Third, a superrule is extracted from the rules WeakReference  and StrongReference, and subsequently, from the combination of this resulting rule with the rule Attribute , the rule Feature is extracted. Fourth, the redundant bindings and filters of the subrules are pulled up to the new superrules. Finally, the feature assignments in the rule EntityType  are merged by exploiting polymorphism and retrieving properties from the trace model in one step. The result after applying the refactorings can be seen in the following listing:

 
module UML2ER;
create OUT : ER from IN : UML;

helper context UML!Class def: allClasses : Sequence(UML!Class) =
   self.superClasses->iterate(e; acc : Sequence(UML!Class) = Sequence {} |
   acc->union(Set{e})->union(e.allClasses) );

helper context UML!Class def : getAllProperties : Sequence (UML!Properties) =
   self.allClasses.including(self).flatten()->collect(e | e.ownedProperty).flatten();

rule EntityType {
   from s: UML!Class
   to t: ER!EntityType (
	name <- s.name,
	features <- s.getAllProperties->collect (e | thisModule.Feature(e)) )}

lazy abstract rule Feature{
   from s: UML!Property
   to t: ER!Feature (
	name <- s.name ) }

lazy rule Attribute extends Feature{
   from s: UML!Property (not s.primitiveType.oclIsUndefined())
   to t: ER!Attribute (
	type <- s.primitiveType )}

lazy rule Reference extends Feature {
   from s: UML!Property (not s.complexType.oclIsUndefined() )
   to t: ER!Reference (
	type <- s.complexType)}

lazy rule WeakReference extends Reference{
   from s: UML!Property (not s.isContainment)
   to t: ER!WeakReference }

lazy rule StrongReference extends Reference{
   from s: UML!Property (s.isContainment)
   to t: ER!StrongReference }

Three bad smells existed in the initial version, namely code clones, overlong OCL expressions, and one god rule, all removed in the refactored version. Then, several quality attributes have been improved. Rule dependency and rule complexity are much lower whereas abstracness is higher. All of this helping the readability, reusability and extendability of the transformation.

Tweet about this on TwitterShare on FacebookBuffer this pageShare on RedditShare on LinkedInShare on Google+Email this to someone

Reply

Your email address will not be published. Required fields are marked *