Last issue of JOT 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 in 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 of 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 of a model transformation refactoring

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 collect(e | e.ownedProperty).flatten()
	->select (e | not e.primitiveType.oclIsUndefined())) (
	   name collect(e | e.ownedProperty).flatten()
	->select(e | not e.complexType.oclIsUndefined() and not e.isContainment)) (
	   name collect(e | e.ownedProperty).flatten()
	->select(e | not e.complexType.oclIsUndefined() and e.isContainment)) (
	   name 

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 collect (e | thisModule.Feature(e)) )}

lazy abstract rule Feature{
   from s: UML!Property
   to t: ER!Feature (
	name 

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 abstractness is higher. All of this helping the readability, reusability, and extendibility of the transformation.

Want to build better software faster?

Want to build better software faster?

Read about the latest trends on software modeling and low-code development

You have Successfully Subscribed!

Pin It on Pinterest

Share This