Matthew's Design Study
For my design study, I am going to be working to improve the design of my COSC460 project. The project is a plug-in for Eclipse designed to provide a tool for visualising software metrics directly onto source code. The plug-in is written in Java and is approximately 5K LOC.
Desired Behaviour & Requirements
Requirements
- Produce a system that can take in metrics information and update a JavaEditor with overlaid decorations.
- Code Colouring
- Colour/shape chips
- ...
- Multiple metrics/displays should be able to be displayed per file.
- The system should be able to deal with static and dynamic sources of metrics.
- XML and an API should be usable to update information.
- It should be possible to determine mappings/metrics that are applied.
Constraints
- Eclipse can not open the same type of editor within the same perspective or same window.
- The JavaEditor may NOT be subclassed. Note this will force us to create a wrapper/strategy for the JavaEditor which can prod at it to gain the additional functionality required.
- The Eclipse structural hierarchy must be used.
Existing Code Base
This system is my first attempt at developing this system. At first glance it appears fairly logical; however, some more serious OOD issues lurk beneath the surface...
Design Discussion/Overview
In this section I will briefly discuss each of the current components and their roles. Please note that these functional groupings do not directly correspond to existing packages. The diagrams to the left provide further detail.
- Plugin: This Singleton class provides a launcher for the Eclipse plugin. It provides all of the initial driving calls to instantiate the underlying document monitor and listeners.
- Document Monitor: This package provides handling of Java files in the editor, keeping track of those that have been loaded and also as a location to retrieve updated metrics data. Generally, this class relies on Singletons to keep track of the open files.
- Editor Listeners: These classes listen to the Eclipse editor, in most cases this is effectively the current document's buffer. Each change of state is reported to the EditorListener class.
- The Model: This set of packages provides a representation of the stored data. A MetricDocument contains all of the metrics, mappings and displays for a single Java file. There can be multiple
metrics represented using multiple mappings in any one document, and each of these may have multiple MetricSections, which represent the an actual appearance of the metric in the editor.
- Display & Editor Manipulation: This set of classes deal with the current Eclipse editor and provide the functionality to add adornments to the editor. A wide variety of these adornments exist,
they are backed up with appropriate Shape and Color managers.
- API: These classes provides an Observer interface where external 'Providers' can listen for and then as necessary provide metrics information as requested.
- Serialisation: This package allows metrics data to be provided and/or converted via a defined XML format.
- Eclipse View: These classes provide a view that allows users to alter which metric visualizations are currently active and which displays are being used for each.
Existing Usage of OOD Features & Techniques
- Design patterns currently used:
- Observer is used in the API.
- Singleton is used widely in both the Editor Listeners and the Document Monitor groupings.
- Strategy is used with the Display and DisplayStrategy.
- Command MetricFile acts like a passable object to facilitate interactions.
- Template Method is used in Range and Display Strategy.
- Factory Method is used in the API and with the Display.
- Design Maxims/Idioms followed (imo):
- Model the real world the classes represent solid concepts.
- Favor composition over inheritance composition is used in favor of inheritance where possible.
- Once and only once repeated code is minimal, even in the similar DisplayStrategy classes.
- ...
- Design Maixim/Idioms neglected:
- Behavioral completeness vs You ain't gonna need it neither of these principles have been explicitly followed.
- Extensibility has not been considered to a sufficient degree.
- Encapsulate that which varies has been broken as Strings are used to convey data in certain parts of the system.
- ...
Areas of Greatest Concern
- API: Is the architectural model appropriate? Is the use of the Observer pattern correct here? Maybe the MVC should be explored...
- Model: Are there any possible improvements?
- String Manipulation: Is there any way to avoid the current String manipulation in Mapping Properties?
- Display Strategies: Is there any way of simplifying this hierarchy? Are the current methods of instantiation appropriate?
- Eclipse View: Dynamic updating of data is a concern here.
- File Instances: Is the current design the most sound way of dealing with Eclipse's editor model (i.e. multiple files, with multiple views).
- Alternative Languages: What can be done to make the system more modular and appropriate for multiple languages?
Iterative Design Process
This section is divided into the may concerns of the project, each of these has essentially chronological actions/iterations to improve certain areas.
Architecture
The key point to note here is that, if we consider the MVC pattern, the old architecture is clearly wrong as the provider of the Model is being told to update itself rather than informing the View when it is updated. This clearly breaks Tell, Don't Ask and a number of other significant maxims. In order to complete this transformation, the existing single Eclipse plugin will be broken into 3+ or more eclipse plug-ins with defined extension points and dependencies. This alteration will also have a significant affect on the API of the MetricsOverlay plugin.
Eclipse Plugin Considerations
- Eclipse allows the ability to instantiate individual plugins as required at runtime. This functionality has been deemed currently unnecessary.
- Extension points have been set up for the EditorListener and MetricOverlay plugins to notify others of their functionality.
- All of the plugins at this point have been defined as singletons, it is intended that any providers intending to provide additional functionality implement their own plug in. Would it be possible to subclass objects in MetricsProvider to make this easier?
The UpdatedEditor Issue
In order for information on the editor that has been updated to flow through the system, a few things need to be considered.
- In the first design, as it formed a single plugin, all of the data stored in what is now UpdatedEditor was stored in MetricFile with the appropriate metric information for that instance of the file.
- In order to provide necessary information to the system, data on the updated editor needs to be forwarded through the plug in.
- We want to maximise component encapsulation while minimising complexity.
Our options, therefore, are:
- UpdatedEditor is held by MetricsOverlay, this means that EditorListener needs to know about the existence of MetricsOverlay. This seems unnecessary for a simple component designed to create a well-rounded architecture. It should be able to provide its functionality by itself.
- UpdatedEditor is held by EditorListener. This seems sensible as Keep related data and behavior in one place is conserved. It does provide a slight problem, in that MetricsOverlay needs to know of the existence of EditorListener. This is usually not a problem, but implementations of MetricsDataProvider need not use EditorListener, introducing unnecessary coupling.
- We create a new plug-in containing information needed by all sections. This adds unnecessary complexity.
- We accept that the states of both are separate and that they should never know about each other. In order to do this, we must commit the sin of Avoid becomes in the MetricsDataProvider. This has the additional disadvantage that the extensibility of the system is reduced as additional knowledge is required.
I feel the best option is leaving UpdatedEditor in EditorListener and allow MetricsOverlay to see it.
EditorListener component
This new plug-in responds to changes in Eclipse by listening to provided listener classes and collating all listen events.
Design Overview
- Activator - Provides the plug in instantiation.
- ServiceChecker - Checks available services that can provide dispatches and attaches dispatches as possible.
- Dispatch + Sublasses - These hierarchy checks Eclipse's hierarchy and listens to the available listeners to generate updates as appropriate, ie if the file type is right etc. Updates are call an appropriate method in EdiotrListener.
- EditorListener - Forms a part of an Observer pattern providing updates for any registered objects. Constructs an UpdatedEditor object to transfer during a update notification. This object keeps track of the previous state and uses this, with the incoming updates to determine the current state of the editor part that is currently active. If an update is needed, this class notifies any observers. This class also makes sure the ServiceTracker keeps up to date.
- UpdatedEditor - This data class stores the necessary information for accessing and modifying the editor.
- EditorState - This enum represents the state of the current editor. There are only 3 states we are actually interested in.
- AcceptableFileTypeHandler - This class allows the system to change the file types it is interested in. (Note: the default constructor is designed specifically for Java as this is its primary design. This should be subclassed for the Java version).
Comparison to Initial Design
New Features:
- Now abstracted to new plug in.
- Listens to a much wider variety of events.
- Can handle different file types.
Class Comparison:
- Editor Listener and FileBufferDispatch existed with mostly the same job. Editor listener is now much less complicated as it only produces results from the listening.
- Other Listeners (Dipatch) are all new, as is the service checker.
- File handling is no longer completely statically defined as AcceptableFileypeHandler is new.
- Updated editor is a new type of data object.
Design Considerations
Maxims/Ideas
- Removing EditorListener from its previous plug in was a result of using Separation of concerns.
- Getters and setters have been used only where absolutely required.
- Tell, don't ask is generally practised by all components.
- Classes that have the potential to be subclassed have protected fields, all the rest are private.
- I considered a much more heavy weight solution where this plug in kept track of all open pages and made sure they were listened to with an iron fist. However, this level of detail is unnecessary for the updates required. This fulfils Do the simplest thing that could possibly work.
- I have avoided Premature optimization, several classes contain inefficient code that it is current unnecessary to improve.
Design Patterns
- Singleton on Activator, EditorListener, ServiceChecker, Two Dispatches.
- Observer with EditorListener as the concrete observable object.
- Unclear Mediator between EditorListener and ServiceChecker.
- Unclear Strategy between EditorListener and AcceptableFileTypeHandler.
Still ToDo
- Create an abstract superclass for AcceptableFileTypeHandler and sub class for Java/UserDefined. This will clean up that strategy pattern.
- Should UpdatedEditor be part of this plug in, while it is the logical place to include it, this means two things: 1) If an EditorListener is not used, then this class will not be visible, unless explicitly included, in MetricsOverlay. 2) Currently MetricsOverlay stores the same data + MetricDocument in a MetricFile, this is suboptimal as the UpdatedEditor has to be split up to become a MetricFile. Maybe move UpdatedEditor into MetricOverlay and let EditorListener know about MetricOverlay
MetricsOverlay component
Comparison to Initial Design
New Features
- Ability to have annotations
New Design Features
- Comparators for model elements
- Simplified model
The Model - Part 1 - Unnecessary Model Element: MappedMetric
This class was unnecessary, it simply held Mappings and MetricSections. MetricSections are now held in Mapping, this makes the model tidier. Arguably, the previous solution broke the Law of Demeter, as objects would have to query the MappedMetric just to get at the underlying Mapping. It has the slight disadvantage of adding to the complexity of Mappings, but it doing so, decreases the complexity of all classes that use the model.
The Model - Part 2 - Interpolation between Metric and Display
A value for each section of the metric has been interpolated into a value that can be used by the display. This interpolation can be linear, logarithmic (on the metric or display side, by a certain log) or exponential (on the metric or display side, by a certain exp). It should also be extensible to allow new interpolation methods, like tanh to be added.
In the old design, this was one huge method (89 lines long) with a massive switch statement in Mapping.
This was clearly a strategy of Mapping. A strategy pattern has been implemented from Mapping which deals with each exponential type as a separate subclass of the abstract InterpolationStrategy class. The instantiation is completed in Mapping and uses lazy instantiation, apart from being a good design feature, an unintended side effect is that this will also help the initial startup efficiency, which is quite important. See the attached thumbnail for details.
The Model - Part 3 - Multiple Metrics on One Display
The Issue
- Some of the display types have the ability to display multiple metrics. How should we deal with this?
- This is an issue because the model is currently designed to support mappings between one metric and one display.
- This may be considered a major issue; however, it may be too difficult to realistically fix.
- Displaying multiple metrics, while possibly useful, has no proven applications currently. Having said that, one has yet to prove that metrics themselves are useful...
- Not only this, the display code is fairly ugly. Some refactoring is probably good here regardless.
Potential Solutions
- We intentionally don't solve it.
- We could just leave it, but this is probably not a nice thing to leave people who might want to extend the system.
- Have a mapping be able to take multiple metrics.
- If this change is made, some way of differentiating between different kinds of displays is needed.
- We will also need to make the mapping strategy more generic.
- This will require changes to the XML.
I believe this is (or atleast should be) one of the Amelioration patterns. As I can't find any documentation describing this pattern, I shall create it and call it the One Into Many pattern.
Final Decision
- Metrics should not ever have multiple displays.
The Model - Part 4 - Metric Sections & Metrics
Wait! In part 1 we moved the set of MetricSections to be held by Mapping. Work on the GUI prompted a dramatic rethink...
- This decision was wrong, we want have the general concept of Mapping from a Metric to a Display.
- There should be able to exist multiple mappings for each type of metric.
- A MetricSection represents a instance point of a Metric.
- Currently this design breaks Separation of concerns.
Solution: Move the set of MetricSections into Metric.
This solution then prompted another rethink. So, each Metric has a Range, this seems right. However:
- It should be possible to use the same Metric data with different Ranges.
Solution: Use the Proxy pattern to model the two different types of Metric, ActualMetric & DerivedMetric, with an abstract superclass Metric. DerivedMetric is a Proxy as it uses an ActualMetric to provide the MetricSections yet defines its own name and range.
The Model - Part 5 - Concept Redefinition: Display -> Augmentation
The Model - Part 6 - The Augmentation Hierarchy
MetricsProvider component
I have implemented a simple version of this section as an exemplar. The tool is primarily design to allow others to add information, but this should give some ideas. Currently this system provides metrics that are related to the:
- Current line of code
- The length of a line of code
- Opening/closing code blocks
Design Considerations
- Observer - This observes the EditorListener.
- Observer - This is observable by the MetricsOverlay
- Singleton