First - sorry about the time it took for me to produce this answer.
Things are hectic in Scandinavia for certain.

:)

All right - here we go.

2010/11/18 Kristian Rosenvold <[email protected]>
[znip]

> As for the patterns discussion nothing specific is being said, so I'm
> eagerly anticipating specifics.
>
>
I got half an hour this weekend, so did some dissection of the first class
one encounters
within the Maven model project. That happens to be the Activation class, and
found some things
I would suggest refactoring. I'll present them in the form Symptom, Remedy,
Example.
For the discussion below, exact semantics are not quite important - the code
quality and
potential for refactoring is.

*Current antipatterns and remedies*

a) *Symptom:* Lots of empty entity setters/getters (i.e. bloat). If getters
and setters do not provide any
   validation, annotation placeholding or type conversion, they do not
really provide any value.
   (No proper information hiding if the setter/getter simply access the
property).
   Instead, empty setters/getters confuse the reader and yield more
maintenance time/cost/pain than if
   they simply did not exist in the code base.
*Remedy:* Replace empty JavaBean property methods with public access to
members.
*Example (JavaDoc deleted for brevity): *

Current Pattern
===========
private String jdk;
public String getJdk() {
        return this.jdk;
}
public void setJdk( String jdk ) {
        this.jdk = jdk;
}

Recommended Pattern
=================
public String jdk;

b) *Symptom:* Lacklustre Separation of Concern and Lack of Object oriented
structure.
   Entities/model holds not only state, but also methods/properties dealing
with location within the pom/XML configuration file.
   Moreover, these methods and properties are copied within all Maven's
model entities (as opposed to
   being placed within an abstract superclass or commonly used, factored-out
delegate).
*Remedy:* Refactor the model to introduce abstract superclasses or delegate
types holding functionality
   dealing with configuration file tracking and/or integration to the
Lifecycle.
   Place only model state within the entity classes.
*Example (JavaDoc deleted for brevity):*

**Current Pattern (in most entities)
==================================

private java.util.Map<Object, InputLocation> locations;
public void setLocation( Object key, InputLocation location )
    {
        if ( location != null )
        {
            if ( this.locations == null )
            {
                this.locations = new java.util.LinkedHashMap<Object,
InputLocation>();
            }
            this.locations.put( key, location );
        }
    }
public InputLocation getLocation( Object key )
    {
        return ( locations != null ) ? locations.get( key ) : null;
    }

Recommended Pattern
===================
public class SomeEntity extends AbstractEntity
{
   // Only domain properties here
   // No framework utility methods or properties.
   // They could/should be placed within superclasses.
}

public abstract class AbstractEntity implements LocationAware
{
   // Deal with hashCode(), equals(), clone() and the like here.
   // Use reflection, to handle subclass needs.
   // Also, implement the LocationAware interface here, to
   // provide required integration with the configuration
   // framework.
}

public interface LocationAware
{
   // Define the integration API to the configuration FW here.
}

c) *Symptom:* Metadata strewn around in model, in the form of properties and
methods.
   This is a matter of style, but I advocate that we separate domain
properties (i.e.
   the properties within domain classes) from metadata which could/should be
represented
   as annotations.
*Remedy:* Separate out metadata, such as configuration representation into
annotations,
   to reduce the potential for data/metadata confusion. Examples of such
metadata could
   be the precise format for configuration file storage. Also, we could get
rid of
   unnecessary constrained criteria and properties, that could be replaced
with common
   types and - optionally - metadata. Provide extensible structures, such as
typed
   collections instead of specific properties (this appears to be something
of a syndrome
   in the Activation class, but is likely similar in other places).
*Example (JavaDoc deleted for brevity):*

**Current Pattern
===============
<All of the classes within the .io package [throughout Maven core]>
<Lots of Plexus dependencies, such as>
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.MXParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

Recommended Pattern
===================
<remove .io packages + their content>

Remove dependencies on Plexus as per above, et. al.
Introduce metadata to represent configuration file structure, such as the
standard JAXB annotations.


*...@xmltype(namespace = "http://maven.apache.org/model/nazgul";)*
*...@xmlaccessortype(XmlAccessType.FIELD)*
@SuppressWarnings("all")
public class Activation extends AbstractEntity implements Serializable
{
   ...

    /**
     * List holding all ActivationCriteria of this Activation block.
     * ActivationCriterion instances will be checked in the order
     * they were read from the configuration (i.e. the same order
     * with which they occur within this List).
     */
    *...@xmlelementwrapper(name = "criteria", nillable = false, required =
true)*
*    @XmlElements({*
*            @XmlElement(name = "jdkCriterion", type =
JdkActivationCriterion.class, required = false),*
*            @XmlElement(name = "osCriterion", type =
OsActivationCriterion.class, required = false),*
*            @XmlElement(name = "propertyCriterion", type =
PropertyActivationCriterion.class, required = false),*
*            @XmlElement(name = "fileCriterion", type =
FileActivationCriterion.class, required = false)*
*    })*
*    public List<ActivationCriterion> criteria = new
ArrayList<ActivationCriterion>();*

   ...

d) *Symptom:* Identical documentation strewn on property, setter and getter.
Along the
   same lines of reasoning as in (a) above, this is simply confusing.
   I would recommend against copy/paste-programming.
*Remedy:* Fixed when removing unnecessary setters/getters, as per (a) above.

*Example (JavaDoc deleted for brevity): *See (a) above.

*Concluding example for the Activation class:*

This is simply an example of how we could refactor the maven domain,
to reduce its footprint, and - hopefully - facilitate understanding.
Feel free to compare this incarnation of Activation class with the
current one, and comment on the simplicity and clarity.
I feel there is quite a lot of places where similar simplifications
can be done within the maven model and core.

... and I'll be back with more code and thoughts later on.

package org.apache.maven.model;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;

/**
 * The conditions within the build runtime environment which will
 * trigger the automatic inclusion of the build profile.
 */
@XmlType(namespace = "http://maven.apache.org/model/nazgul";)
@XmlAccessorType(XmlAccessType.FIELD)
@SuppressWarnings("all")
public class Activation extends AbstractEntity implements Serializable
{
    /**
     * If set to <code>true</code>, this profile will be active unless
another
     * profile in this pom is activated using the command line -P
     * option or by one of that profile's activators.
     */
    @XmlAttribute
    public boolean activeByDefault = false;

    /**
     * List holding all ActivationCriteria of this Activation block.
     * ActivationCriterion instances will be checked in the order
     * they were read from the configuration (i.e. the same order
     * with which they occur within this List).
     */
    @XmlElementWrapper(name = "criteria", nillable = false, required = true)
    @XmlElements({
            @XmlElement(name = "jdkCriterion", type =
JdkActivationCriterion.class, required = false),
            @XmlElement(name = "osCriterion", type =
OsActivationCriterion.class, required = false),
            @XmlElement(name = "propertyCriterion", type =
PropertyActivationCriterion.class, required = false),
            @XmlElement(name = "fileCriterion", type =
FileActivationCriterion.class, required = false)
    })
    public List<ActivationCriterion> criteria = new
ArrayList<ActivationCriterion>();
}

-- 

-- 
Lennart Jörelid

Reply via email to