James Ashford Design Study
m |
m (Reverted edits by Ebybymic (Talk); changed back to last version by James Ashford) |
||
(130 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
= My Project = | = My Project = | ||
− | As part of my honours project, I am developing a plug-in to Eclipse to help illustrate how the state of software changes during runtime. My design study will focus of the data collection subsystem of my project. | + | As part of my honours project, I am developing a plug-in to Eclipse to help illustrate how the state of software changes during runtime. My design study will focus of the '''data collection''' subsystem of my project. |
− | Data collection works by inserting a data collection point into a line of code in your | + | Data collection works by inserting a data collection point (also known as a '''Pseudo Breakpoint''') into a line of code in your program. Every time the application hits this data collection point, a copy of all variables in scope (local, instance and static variables) are recorded and the program resumes. At the conclusion of runtime, the user can review what happened and generate visualisations. |
− | There are two main areas in the data collection subsystem | + | |
+ | [[Image:jra82-workspace.png]] | ||
+ | |||
+ | ''The entire plugin running showing visualisations'' | ||
+ | |||
+ | |||
+ | [[Image:jra82-psb.png]] | ||
+ | |||
+ | ''Adding/Removing a PseudoBreakpoint''' | ||
+ | |||
+ | == General Use Case == | ||
+ | Goal: '''Find and resolve a bug within your code.''' | ||
+ | |||
+ | 1. A user launches Eclipse | ||
+ | |||
+ | 2. The user will insert a PseudoBreakpoint on a single line on their application | ||
+ | |||
+ | 3. The user will then run their application (with debugging enabled) | ||
+ | |||
+ | 4. Whenever the PseudoBreakpoint is hit a copy of all the variables are taken (stored as our own datatype) with other state information such as which thread was executing, the current date/time and so on. | ||
+ | |||
+ | 5. At the conclusion of the program, the variables and state information is used to generate a diagram or other visualiation. | ||
+ | |||
+ | 6. (Use these visualisations to help find and resolve the bug) | ||
+ | |||
+ | == Main Subsystems == | ||
+ | There are two main areas in the data collection subsystem that will be part of this design study: | ||
1. Management (inserting / deleting of data collection points etc) | 1. Management (inserting / deleting of data collection points etc) | ||
Line 10: | Line 36: | ||
2. Event Handler (whenever the data collection point is encountered) | 2. Event Handler (whenever the data collection point is encountered) | ||
− | == Management == | + | === Management === |
− | + | The management area of this project is related to the creation, deletion, insertion and management of the '''data collection''' points (Pseudo Breakpoints). Currently, a data collection point is a thinly disguised breakpoint that is handled along side normal Java breakpoints. | |
+ | |||
+ | Main activities: | ||
+ | * Creates Pseudo Breakpoints | ||
+ | * Manages the Pseudo Breakpoint | ||
+ | * Adds the extension to the Eclipse editor (so the user can toggle the Pseudo Breakpoints on/off) | ||
+ | |||
+ | === Event Handler === | ||
+ | The event handler controls all the data collection events within the system. | ||
+ | |||
+ | Main activities: | ||
+ | * Handles events when the Pseudo Breakpoint is 'hit' | ||
+ | * Variable datastore (when the breakpoint is hit a copy of all inscope variables are stored in a datastore) | ||
+ | |||
− | + | Event Handler Process Flow: | |
− | + | ||
1. Event Handler is notified that a breakpoint has been hit | 1. Event Handler is notified that a breakpoint has been hit | ||
Line 31: | Line 69: | ||
== Requirements == | == Requirements == | ||
− | # Maintainability - Easily add new features (such as different data sources etc) | + | There are two main requirements that I hope to achieve by completing this design study: |
− | # Extensible - Add additional programming languages (such as PHP etc) easily | + | |
+ | # Maintainability - Easily add new features (such as different data sources etc) - Currently we only support a single data source. | ||
+ | # Extensible - Add additional programming languages (such as PHP etc) easily - Currently we only support Java. | ||
== Constraints == | == Constraints == | ||
− | + | The single biggest limitation to this project is the Eclipse model. This is an Eclipse plug-in, and we must structure our code to fit the Eclipse way of doing things. This means we must implement certain Eclipse interfaces. | |
== Initial Design == | == Initial Design == | ||
− | + | What design? | |
== UML Diagram == | == UML Diagram == | ||
[[image:Jra82_firstdesign_uml.png]] | [[image:Jra82_firstdesign_uml.png]] | ||
− | + | For sanity sake, I've excluded all the methods and variables from the diagram. Suffice to say, there was a lot of unnecessary junk. Classes have been separated into different packages, however you may notice that it was done without care and there is a lot of unnecessary coupling. | |
=== Description of Classes === | === Description of Classes === | ||
− | '''debugassist:''' | + | '''(package) debugassist:''' |
− | * Activator (extends AbtractUIPlugin) - Activator class | + | * Activator (extends AbtractUIPlugin) - Activator class that is run when the Plugin is loaded |
* DebugAssistLogic (implements DataUpdateBreakpointClient, DataUpdateEventClient) - Provides some basic functions to clean up breakpoint data, contains 2 clients which are activated when a breakpoint is inserted/updated/remove and when a breakpoint is 'hit' | * DebugAssistLogic (implements DataUpdateBreakpointClient, DataUpdateEventClient) - Provides some basic functions to clean up breakpoint data, contains 2 clients which are activated when a breakpoint is inserted/updated/remove and when a breakpoint is 'hit' | ||
− | '''debugassist.datasource:''' | + | '''(package) debugassist.datasource:''' |
* DataBreakpointStore - Stores all the pseudo breakpoints in a HashSet. | * DataBreakpointStore - Stores all the pseudo breakpoints in a HashSet. | ||
* DataEventStoreTime - Store all the data when a breakpoint is hit. | * DataEventStoreTime - Store all the data when a breakpoint is hit. | ||
− | '''debugassit.events:''' | + | '''(package) debugassit.events:''' |
− | * DataUpdateBreakpointClient - An interface | + | * DataUpdateBreakpointClient - An interface that needs to be implemented if you wish to listen to Breakpoint Events (i.e. when a breakpoint is added/removed etc) |
− | * DataUpdateBreakpointListener - The service | + | * DataUpdateBreakpointListener - The service that handles Breakpoint listener clients (where clients subscribe to events etc) |
− | * DataUpdateEventClient - An interface | + | * DataUpdateEventClient - An interface that needs to be implemented if you wish to listen to Breakpoint Data Events (i.e. when a breakpoint is encountered by the debugger). |
− | * DataUpdateEventListener - The service | + | * DataUpdateEventListener - The service that handles Breakpoint data listener clients (where clients subscribe to events) |
− | '''debugassist.handlers:''' | + | '''(package) debugassist.handlers:''' |
* BreakpointAddRemoveEventHandler (implements IBreakpointListener) - handles the breakpoint events from Eclipse (i.e. when a breakpoint is added / removed from Eclipse) - and notifies DataUpdateBreakpointListener. | * BreakpointAddRemoveEventHandler (implements IBreakpointListener) - handles the breakpoint events from Eclipse (i.e. when a breakpoint is added / removed from Eclipse) - and notifies DataUpdateBreakpointListener. | ||
* DebugEventHandler (implements IDebugEventSetListener) - Handles the breakpoint hit events from Eclipse (i.e. when a breakpoint is hit within Eclipse) - this performs data collection (collects all variables in scope), and notifies DataUpdateEventListener. | * DebugEventHandler (implements IDebugEventSetListener) - Handles the breakpoint hit events from Eclipse (i.e. when a breakpoint is hit within Eclipse) - this performs data collection (collects all variables in scope), and notifies DataUpdateEventListener. | ||
* EclipseDebuggerEventListenerManager - Attaches BreakpointAddRemoveEventHandler and DebugEventHandler to Eclipse. | * EclipseDebuggerEventListenerManager - Attaches BreakpointAddRemoveEventHandler and DebugEventHandler to Eclipse. | ||
− | '''debugassist.model:''' | + | '''(package) debugassist.model:''' |
* BreakpointEvent - A model for whenever a breakpoint is hit by the debugger (stores time, variables) | * BreakpointEvent - A model for whenever a breakpoint is hit by the debugger (stores time, variables) | ||
* ModelGenerator - A utility class for converting IVariable (Eclipse type) to Variable (our model type) | * ModelGenerator - A utility class for converting IVariable (Eclipse type) to Variable (our model type) | ||
* Variable - A model for a Variable | * Variable - A model for a Variable | ||
− | '''debugassist.pseudobreakpoint''' | + | '''(package) debugassist.pseudobreakpoint''' |
− | * AddRemovePseudoBreakPointHandler (extends AbstractRulerActionDelegate) - | + | * AddRemovePseudoBreakPointHandler (extends AbstractRulerActionDelegate) - PseudoBreakpoint handler type - so users can right click in Eclipse to add/remove a breakpoint. |
− | * PseudoBreakpointImpl (extends JavaLineBreakpoint) - The breakpoint used to represent our | + | * PseudoBreakpointImpl (extends JavaLineBreakpoint) - The breakpoint used to represent our PseudoBreakpoint. It is actually just a JavaBreakpoint with a different type and icon so the DebugEventHandler knows which breakpoints to collect. |
+ | |||
+ | === Inherited Classes === | ||
+ | A few classes are extended and implemented in this project. For completeness, I have included their details here: | ||
+ | |||
+ | '''AbstractUIPlugin''' - An abstract class providing some basic functionality required to create an plugin in Eclipse. [http://help.eclipse.org/help33/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/ui/plugin/AbstractUIPlugin.html AbstractUIPlugin Javadoc] | ||
+ | |||
+ | '''IBreakpointListener''' - An interface used by classes that require notification when breakpoints are added and removed [http://help.eclipse.org/help33/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/debug/core/IBreakpointListener.html IBreakpointListener Javadoc] | ||
+ | |||
+ | '''IDebugEventSetListener''' - An interface used by classes that require notification when Debugger Events occur [http://help.eclipse.org/help33/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/debug/core/IDebugEventSetListener.html IDebugEventSetListener Javadoc] | ||
+ | |||
+ | '''AbstractRulerActionDelegate''' - An abstract class to allow contributions to the vertical ruler's context menu in the text editor. [http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/ui/texteditor/AbstractRulerActionDelegate.html AbstractRulerActionDelegate Javadoc] | ||
+ | |||
+ | '''JavaLineBreakpoint''' - A class which provides the Java Line Breakpoint Implementation (required to insert a breakpoint into the JVM) | ||
== Design Critique == | == Design Critique == | ||
− | + | This program was created out of the prototyping space, and as such, there was not much consideration to design. There are therefore many things wrong! | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | == Design Improvements == | + | === Maxims Violated === |
− | I' | + | '''[[Avoid inheritance for implementation]]''' |
+ | My PseudoBreakpoint class directly inherits the Javabreakpoint class so that it can interact with the JVM. This effectively stops any other programming language from using our plugin. | ||
+ | |||
+ | '''[[Separation of concerns]]''' | ||
+ | DebugEventHandler handles the Breakpoint data events as well as being an Event Listener. | ||
+ | |||
+ | '''[[Beware singletons]]''' | ||
+ | There is a number of singleton classes in here including: DataUpdateBreakpointListener, DataUpdateEventListener and DebugAssistLogic. Something doesn't smell right here... Perhaps this could be refactored into a single class? | ||
+ | |||
+ | '''[[Single responsibility principle]] & [[Avoid god classes]]''' | ||
+ | DebugAssistLogic seems to be doing too much [[God object]] - it is listening to both Breakpoint and Data events, as well as providing data cleanup functionality. | ||
+ | |||
+ | '''[[Program to the interface not the implementation]]''' | ||
+ | Everything uses the direct class (no interfaces used) - Again, this limits our project to Java, and makes it hard to extend our application. | ||
+ | |||
+ | '''[[Software reuse]]''' | ||
+ | DataUpdateBreakpointListener and DataUpdateEventListener both have the same code to handle the adding and removal of listener classes. [[Duplicate code smell]] | ||
+ | |||
+ | '''[[Model the real world]]''' | ||
+ | The class and package names are currently inconsistent, and it isn't obvious which classes do what. | ||
+ | |||
+ | == First Attempt Design Improvements == | ||
+ | [[Image:Jra82_secondversion.jpg]] | ||
+ | |||
+ | === Main Improvements === | ||
+ | * Class names have become clearer (PsuedoBreakpointRulerActionDelegate & PseudoBreakpointRulerAction should be clearer than AddRemovePsuedoBreakpointHandler & PseudoBreakpointImpl) | ||
+ | * PseudoBreakpoint Factory | ||
+ | * Events Improved | ||
+ | * Model Generator moved | ||
+ | |||
+ | === PseudoBreakpoint Factory === | ||
+ | Previously the PseudoBreakpoint class was a extension of the JavaBreakpoint class. In order to support other programming languages (in the future) I created a [[Factory Method]] to return the appropriate Breakpoint class to insert into the virtual machine. | ||
+ | * PseudoBreakpoint (extends IBreakpoint) - A placeholder type for our PseudoBreakpoint | ||
+ | * PseudoBreakpointCreator - Determines which sort of Breakpoint to create and returns the generic PseudoBreakpoint | ||
+ | * PseudoBreakpointPython / PseudoBreakpointJava (implements PseudoBreakpoint) - an extension of the Java/Python breakpoint class. | ||
+ | |||
+ | === Events (BreakpointDataEvent / BreakpointChangeEvent) === | ||
+ | [[Observer]] - My original design was kind-of an Observer pattern (but poorly implemented - both classes had the same code for adding/removing observers ([[Don't repeat yourself]])). As Java's Observer pattern does not suport generics, I decided to create my own. A generic abstract class 'Subject' handles subscribers. | ||
+ | |||
+ | * Subject - Abstract class to handle observers attaching/detaching to the event handler | ||
+ | * DataUpdateEventObserver (was DataUpdateEventClient) - Essentially the same, but now with a proper name | ||
+ | * DataUpdateBreakpointObserver (was DataUpdateBreakpointClient) - Essentially the same, but now with a proper name | ||
+ | * BreakpointDataEvent [extends Subject, Implements IDebugEventSetListener (Eclipse)] (was DataUpdateEventListener) - Handles Breakpoint Data Events (i.e. when a breakpoint is 'hit') from the Eclipse Debugger directly and notifies subscribed observers | ||
+ | * BreakpointChangeEvent [extends Subject, Implements IBreakpointListener (Eclipse)] (was DataUpdateEventListener) - Handles Breakpoint Add/Remove Events from the Eclipse directly and notifies subscribes observers and the DataBreakpointStore | ||
+ | |||
+ | === Variables / BreakpointEvent === | ||
+ | The model generator class is now an inner class within the BreakpointEvent class (as it does not need to be exposed). | ||
+ | |||
+ | ==Second Attempt == | ||
+ | |||
+ | === Changes to the First Attempt === | ||
+ | I wasn't completely happy with my first design attempt, so I decided to run another design iteration over it. | ||
+ | |||
+ | '''Main changes include:''' | ||
+ | * Redesigned variable section. The main issue with the old variable class was that primitive variables were represented in the same way as an object - so they had the functionality to add children and so on. | ||
+ | ** Abstract class Variable (contains most of a variable definition) | ||
+ | ** PrimitiveVariable class (contains data about primitive variables) | ||
+ | ** ObjectVariable class (contains data about objects) | ||
+ | * Redesigned Events section | ||
+ | ** One of the design goals was to easily add new data sources, until now that hadn't been addressed! | ||
+ | ** Now there is a Abstract EventSource class (which is used by classes which generate events) | ||
+ | ** PseudoBreakpointEventData is an EventSource that will be hit whenever a PseudoBreakpoint is hit | ||
+ | ** EventHandler extends Subject and is called by EventSource variables whenever they change. | ||
+ | * Fixed PseudoBreakpointRulerActionDelegate classes. Previously it was implementing an interface, and I had a lot of duplicate code to get it working, now I have switched to an Eclipse supplied superclass that provides the required functionality ([[Software_reuse]]). | ||
+ | * Removed the last Singleton pattern from BreakpointEventDataStore | ||
+ | ** Turns out that it wasn't even necessary | ||
+ | ** It is now all handled by the Activator class (which handles the creation of the plugin) | ||
+ | * Removed DataBreakpointStore. We didn't need it. | ||
+ | ** [[You_ain't_gonna_need_it]]. It was only there for completeness, and wasn't used by my code at all. | ||
+ | ** It is unnecessary to take a copy of the installed PseudoBreakpoint as the underlying Eclipse model knew where the PseudoBreakpoint was installed. We can query the Eclipse model if we need to know where it is. This prevents redundancy. | ||
+ | * Renaming of Classes to better reflect their task and make it more consistant. | ||
+ | * Collections implementations are now hidden (most generic type possible) | ||
+ | * Collections are now returns as unmodifiable (where applicable) | ||
+ | * Packages were also revisited, a clear description of each package has now been defined: | ||
+ | |||
+ | === Packages === | ||
+ | {| border="1" | ||
+ | |- | ||
+ | ! Package | ||
+ | ! Function (Subsystem) | ||
+ | ! Description | ||
+ | |- | ||
+ | | debugassist | ||
+ | | Both | ||
+ | | The root package. Only contains a single class (Activator). | ||
+ | |- | ||
+ | | debugassist.datasource | ||
+ | | Event Handling | ||
+ | | Contains the data store for all the event data | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | Event Handling | ||
+ | | Encapsulates all the Event handling classes (from the Eclipse model) | ||
+ | |- | ||
+ | | debugassist.model | ||
+ | | Event Handling | ||
+ | | Our custom model (Variables and BreakpointEvents) | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | Management | ||
+ | | Classes to handle and represent the PseudoBreakpoint in the text editor and in the associated virtual machines. | ||
+ | |||
+ | |||
+ | |} | ||
+ | [[Image:jra82-umlpackage.png]] | ||
+ | |||
+ | === Class Diagram === | ||
+ | [[Image:jra82-umlclass3.png]] | ||
+ | |||
+ | |||
+ | === Class Descriptions === | ||
+ | {| border="1" | ||
+ | |- | ||
+ | ! Package | ||
+ | ! Class Name | ||
+ | ! Description | ||
+ | ! Public Attributes | ||
+ | ! Public Methods ''(returns void unless specified)'' | ||
+ | |- | ||
+ | | debugassist (root) | ||
+ | | class '''Activator''' (Extends AbstractUIPlugin) | ||
+ | | This class launches the entire plugin. It is instantiated by Eclipse itself, and operates in a similar way to a singleton. It contains the logic to attach all the event handlers to Eclipse. | ||
+ | | ''None'' | ||
+ | | ''public'' '''Activator()''' - public constructor (required by Eclipse); '''start(BundleContext context)''' - starts and launches the plugin (and attaches event handlers); '''stop(BundleContext context)''' - destroys instance, removes event handlers; '''static Activator getDefault()''' - Returns the current instance of the running plugin; ''static'' '''ImageDescriptor getImageDescriptor(String path)''' - Returns the image descriptor for the image file. | ||
+ | |- | ||
+ | | debugassist.datasource | ||
+ | | class '''BreakpointEventDataStore''' (implements DataUpdateEventObserver) | ||
+ | | This class stores a copy of the BreakpointEvents as they occur. | ||
+ | | ''None'' | ||
+ | | '''ArrayList<BreakpointEvent> getAllBreakpointEvents()''' - returns the data store; '''clearEvents()''' - removes all items from the data store; '''updateModelRequired(String frameName, String threadName, IVariable[] variables)''' - A method from the DataUpdateEventObserver interface - it is called whenever a PseudoBreakpoint is hit. | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | abstract class '''Subject<T>''' | ||
+ | | Provides basic functionality to add and remove observers. | ||
+ | | ''None'' | ||
+ | | '''attach(T child)''' - adds a child who requires notification when something changes; '''detach(T child)''' - removes the child from notification; ''protected'' '''Set<T> getObservers()''' returns a set of the observers. | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | interface '''DataUpdateEventObserver''' | ||
+ | | Any class which wishes to be notified whenever a PseudoBreakpoint is hit must implement this interface. | ||
+ | | ''None'' | ||
+ | | '''updateModelRequired(String frameName, String threadName, IVariable[] variables)''' - Whenever a PseudoBreakpoint is hit, this method will be called - containing the frame name, thread name, and an array of IVariables (An Eclipse model type) | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | class '''EventHandler''' extends Subject<DataUpdateEventObserver> | ||
+ | | A container class which holds all the subjects who wish to be notified when an event occurs | ||
+ | | ''None'' | ||
+ | | '''updateAll(String frameName, String threadName, IVariable[] vars)''' - to be called by an EventSource whenever an event occurs, '''dispose()''' - contains cleanup tasks (removing all observers) | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | abstract class '''EventSource''' | ||
+ | | An abstract class that all datasources must implement | ||
+ | | ''None'' | ||
+ | | ''public'' '''EventSource(EventHandler eventHandlerInstance)''' - the only constructor (it must take in the EventHandler instance). ''protected'' '''notifyEventHandler(String frameName, String threadName, IVariable[] vars)''' - notifies the event handler of a new event | ||
+ | |- | ||
+ | | debugassist.events | ||
+ | | class '''BreakpointEventData''' extends EventSource implements IDebugEventSetListener | ||
+ | | Handles all Eclipse breakpoint data events (like when a breakpoint is hit by the debugger), and passes variable information to the EventHandler only when a PseudoBreakpoint. This works by checking to see if the breakpoint that was hit is an instance of PseudoBreakpoint. | ||
+ | | ''None'' | ||
+ | | '''handleDebugEvents(DebugEvent[] events)''' - Required by IDebugEventSetListener interface, is called whenever any breakpoint is hit in Eclipse. Our implementation determines if the breakpoint was a pseudo breakpoint, and if so, notifies the EventHandler. | ||
+ | |- | ||
+ | | debugassist.model | ||
+ | | class '''BreakpointEvent''' | ||
+ | | The representation of a breakpoint event (when a breakpoint is hit). Contains information such as time hit, thread name, stack name and variables that were relevant. | ||
+ | | ''None'' | ||
+ | | ''static'' '''BreakpointEvent generateBreakpointEvent(Date timeHit, int numHit, String threadName, String stackName, IVariable[] variables)''' - generates a new BreakpointEvent instance from IVariables (the Eclipse model); '''List<Variable> getVariables()''' - returns an unmodifiable list of all the variables; '''String getStackName()''' returns the stack name; '''int getNumHit()''' - returns which nTh time the breakpoint has been hit; '''Date getTimeHit()''' - returns the time the breakpoint was hit; '''String getThreadName()''' - returns the thread name; '''String toString()''' - The string representation of the object. | ||
+ | |- | ||
+ | |debugassist.model | ||
+ | | abstract class '''Variable''' implements Comparable<Variable> | ||
+ | | Provides an abstract presentation of a variable. There is also a private class ModelGenerator that converts an IVariable array (the Eclipse type) to a list of Variables. | ||
+ | | ''None'' | ||
+ | | '''String getData()''' - returns a String representation of the data; '''String getType()''' - returns a String of the type; '''String getName()''' - returns a String of the name of the variable. '''int getUpdateCount()''' - returns the number of changes that variable has had; '''updateData(String data)''' - updates the data field of the variable (and increments the update counter); '''compareTo(Variable o)''' - compares another variable against itself for sorting (based on the name of the variable); ''static'' '''List<Variable> generateFromVariableArray(IVariable[] vars)''' - generates the entire variable model from the Eclipse model (with help from the inner ''ModelGenerator'' class). | ||
+ | |- | ||
+ | | debugassist.model | ||
+ | | class '''PrimitiveVariable''' extends Variable | ||
+ | | Represents a primitive variable (eg int) | ||
+ | | ''None'' | ||
+ | | '''boolean isLeaf()''' - always returns true (as will never have children); '''String toString()''' - Returns a string representation of the primitive; '''String getShortName()''' - returns a shortened version of the toString() method | ||
+ | |- | ||
+ | | debugassist.model | ||
+ | | class '''ObjectVariable''' extends Variable | ||
+ | | Represents an Object (which contains references to other Variables) | ||
+ | | ''None'' | ||
+ | | (The same as PrimitiveVariable) plus: '''Map<String, Variable> getReferences()''' - returns all the Variables which this variable refer to; '''addReference(String name,Variable ref)''' - Adds an object to the collection | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | interface '''PseudoBreakpoint''' extends ILineBreakpoint (Eclipse Model) | ||
+ | | An interface to represent a breakpoint which will be used by Eclipse (for use in the text editor) and by the programming language (to actually insert a breakpoint into the VM) | ||
+ | | ''None'' | ||
+ | | ''None'' | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | class '''PseudoBreakpointCreator''' | ||
+ | | Contains a factory method to determine which kind of PseudoBreakpoint type to create depending on the programming language in use. | ||
+ | | ''None'' | ||
+ | | ''static'' '''PseudoBreakpoint factoryMethod(IResource resource, String typeName, int lineNumber)''' - Determines which PseudoBreakpoint type to create | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | class '''PseudoBreakpointJava''' extends JavaLineBreakpoint (Internal Eclipse JDT breakpoint representation) implements PseudoBreakpoint | ||
+ | | A class to represent a PseudoBreakpoint for Java. | ||
+ | | ''None'' | ||
+ | | ''public'' '''PseudoBreakpointJava(final IResource resource, final String typeName, final int lineNumber)''' - (constructor) - Creates a breakpoint representation, and marker (for use in the text editor); '''String getMarkerMessage(boolean conditionEnabled, String condition, int hitCount, int suspendPolicy, int lineNumber)''' - Modifies the name of the PseudoBreakpoint (for identification only); | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | class '''PseudoBreakpointPython''' implements PseudoBreakpoint | ||
+ | | A class to represent a PseudoBreakpoint for Python. | ||
+ | | ''None'' | ||
+ | | ''Incomplete - it has not yet been implemented.'' | ||
+ | |- | ||
+ | | debugassist.pseudobreakpoint | ||
+ | | class '''PseudoBreakpointRulerActionDelegate''' extends AbstractRulerActionDelegate | ||
+ | | The delegate class used when the ruler menu action 'Add/Remove PseudoBreakpoint' is used | ||
+ | | ''None'' | ||
+ | | '''IAction createAction(ITextEditor editor,IVerticalRulerInfo rulerInfo)''' - Creates a new PseudoBreakpointRulerAction | ||
+ | |- | ||
+ | |debugassist.pseudobreakpoint | ||
+ | | class '''PseudoBreakpointRulerAction''' extends Action (inner class of PseudoBreakpointRulerActionDelegate) | ||
+ | | The class used when the ruler menu action 'Add/Remove PseudoBreakpoint' is used | ||
+ | | ''None'' | ||
+ | | ''public'' '''PseudoBreakpointRulerAction(ITextEditor e, IVerticalRulerInfo r)''' - constructor - Creates the menu action; '''runWithEvent(Event event)''' - the method which is called when the 'Add/Remove PseudoBreakpoint' menu item is used. Creates a PseudoBreakpoint and adds it to the Eclipse breakpoint manager or will delete the PseudoBreakpoint (if one already exists) | ||
+ | |||
+ | |} | ||
+ | |||
+ | === Design Patterns Used === | ||
+ | [[Observer]] - I used the Observer pattern in the event handling system. | ||
+ | * Subject = Subject<T> | ||
+ | * ConcreteSubject = EventHandler | ||
+ | * Observer = DataUpdateEventObserver | ||
+ | * ConcreteObserver = BreakpointEventDataStore | ||
+ | |||
+ | [[Image:jra82-observer.png]] | ||
+ | |||
+ | There are a few changes to the normal Observer pattern (as you can see from the UML diagram!). Essentially, the main difference is to allow multiple different EventSources to notify a number of Observers. It should be easy to add new observers, and new data sources (even though in this example we only have a single data source and observer). The Subject class provides generic and templated functionality to attach and detach a number of observers. Java has a built in Subject class, however it does not support generics. EventHandler is the realization of the Subject class. EventSource is an abstract class whereby the public constructor requires an EventHandler as the argument. Any EventSource must also take then EventHandler as the constructor argument (this is to enforce the Tell don't ask principle). | ||
+ | |||
+ | The interface DataUpdateEventObserver is required by any class that wants to subscribe to all the data events (in this case, our BreakpointEventDataStore). This also differs slightly from the standard Observer pattern. In the normal Observer pattern, it is up to the concrete observers to query the underlying model to fetch the updates. In my design, all subscribers are told of the changes. This helps decrease coupling between EventSources and the subscriber classes. It also means that we can have a number of new data sources without having to change any of the observer classes. It also helps with a potential concurrency issue - concrete observers would need to query the underlying data model. If a PseudoBreakpoint was inserted on a program that had multiple threads accessing the same method, the underlying data might change between the observer being notified and the observer querying the data model that could potentially cause data to be lost. | ||
+ | |||
+ | |||
+ | |||
+ | [[Composite]] - I used a Composite pattern to create the notion of a Variable. | ||
+ | * Component = Variable | ||
+ | * Leaf = PrimitiveVariable | ||
+ | * Composite = ObjectVariable | ||
+ | |||
+ | A Variable can either be a primitive or an object. Primitives types in Java are: byte, short, int, long, float, double, boolean, char. Object types are composed of other Variables (other objects or primitives). | ||
+ | |||
+ | |||
+ | [[Factory Method]] - A factory method has been used to determine which type of Breakpoint to generate. This allows us to easily add new programming languages without too much effort. | ||
+ | * Factory = PseudoBreakpointCreator | ||
+ | * Product = PseudoBreakpoint (extends org.eclipse.debug.core.model.ILineBreakpoint) | ||
+ | * ConcreteProduct = PseudoBreakpointJava / PseudoBreakpointPython | ||
+ | |||
+ | === Design Maxims Followed === | ||
+ | [[Acyclic dependencies principle]] - There are no cycles between packages. The debugassist package (actually Activator class) needs to instantiate the both Datasource Events packages. DataSource package implements the interface provided by the Events package. The Event package needs to check to see if a breakpoint event was an PseudoBreakpoint instance (from the PseudoBreakpoint package), and the DataSource package needs to store information from the Model package. | ||
+ | |||
+ | [[Don't expose mutable attributes]] - All collections have been modified so they are returned as unmodifiable collections. While it does not provide any additional security, it avoids the data collections from being accidentally modified. | ||
+ | |||
+ | [[Avoid equals]] - There is currently no equality test between objects. For example, variable objects are only created once and stored in a collection. Nothing else could ever be exactly the same (even if the variable contained the same name, type and value) - it would be a different collection time. | ||
+ | |||
+ | [[Getters and setters]] - All variables are hidden and only accessed by the minimum required getters and setters. For example, a variable can never change its name once constructed (so only a getter), but it can change its value (both getters and setters) | ||
+ | |||
+ | [[Single responsibility principle]] - Each class has a distinct and separate responsibility. For example, the abstract Subject<T> class used to be integrated into the EventHandler class. By creating an abstract class, anything that requires subscribers can now inherit the Subject class (this promoties [[Software reuse]]). | ||
+ | |||
+ | [[Hide your decisions]] - Collections are hidden behind the most generic type possible (eg list etc). | ||
+ | |||
+ | [[Single responsibility principle]] - In this final iteration I attempted to split the classes further down. In the previous iteration, the BreakpointDataEvent class handled breakpoint events and also notified all subscribed classes. This class had two distinct responsibilities (handling events and handling subscribers). It has now been split into two classes - PseudoBreakpointEventData (a subclass of the newly created EventSource class) and EventHandler. | ||
+ | |||
+ | [[Open_closed_principle]] - In the previous iteration the design made it very difficult to be extended. It would have been had to add additional functionality (such as new programming languages or new data sources) without major changes. New language support can be added by extending the abstract PseudoBreakpoint class. New data sources can be added by extending the abstract EventSource class. | ||
+ | |||
+ | [[Do the simplest thing that could possibly work]] - From the original design, the Variable class had many methods which did similar things (such as updateVariableCount() and incrementUpdateByOne(), isLeaf() and hasChildren(), etc). The interface to Variable has been simplified to only support the minimal number of operations. This should help maintainability, as if there are any changes required to the underly model only the minimal number of items need to change. | ||
+ | |||
+ | === Design Maxims Considered === | ||
+ | [[Singleton]] - In the initial designs there was a lot of Singletons used (DataUpdateBreakpointListener, DataUpdateEventListener, DebugAssistLogic, and to some extent Activator). However, as the design progressed, it became obvious that this was not entirely necessary. Rather than have a Singleton for each of the classes, only a single singleton was required to instantiate all the other objects. The most obvious place for this would be in the Activator class. However, the Eclipse model meant this was slightly more complicated. Eclipse instantiate the Activator class and then call the start() method. At this point all the event handlers are attached, and the plugin is loaded. When the plugin is closed Eclipse tells the Activator class to shutdown (removing all the event handlers and references) - and thereby clearing the memory. Unfortunately, because the Activator class requires a public constructor there is the potential for two instances of the Activator class to be created. It is up to the underlying Eclipse model to ensure that only a single instance of the Activator class at given point in time. | ||
+ | |||
+ | === Design Maxims Violated === | ||
+ | [[Avoid side effects]] - One method in Variable has a slight side effect that might catch a developer off-guard. Whenever the updateData method is called, a counter is incremented (so we can keep track of how many changes this object has). If someone did not realize this happens in their own developed subclass they may accidentally increment the counter twice. I still do not believe this is complete violation of the rule, but something to keep in mind. | ||
+ | |||
+ | [[Big design up front]] - There have been three (documented) iterations of development. This project began out of prototyping and evolved into the current product, it was therefore impossible to complete a big design up front as I was unaware of what I would need down the track. | ||
+ | |||
+ | [[Avoid inheritance for implementation]] - PseudoBreakpointJava extends Eclipses Java Breakpoint implementation to provide our PseudoBreakpoint for the Java Language. It would have been significantly harder for our PseudoBreakpoint to re-implement our own Java Breakpoint. | ||
+ | |||
+ | [[Don't burn your base class]] - The class EventHandler extends Subject to provide the observer functionality (also see [[Avoid inheritance for implementation]]). I cannot think of a situation where EventHandler would need to subclass anything other than Subject (''famous last words''.) The current Java implementation of listeners is exactly the same as my design except I use generics (''I'm not saying that Java was designed well!'') The EventHandler class only notifies other classes when breakpoint events occur, and does not provide any other functionality. Any EventSource subclass will also suffer from the same problem. If they required an EventSource to contain a different subclass, they could create another class (with the required superclass) that could be composed of a new EventSource subclass. | ||
+ | |||
+ | [[Class Encapsulation]] - '''Variable''' / '''ObjectVariable''' & '''PrimitiveVariable''' - Class encapsulation is broken. Some of the variable within the '''Variable''' class are marked as protected so the subclasses are able to access them and modify them. The power of the subclasses accessing the variables outweighs any risk. | ||
= Files = | = Files = | ||
[[Media:debugassist-swe_init.zip]] - The initial design. | [[Media:debugassist-swe_init.zip]] - The initial design. | ||
+ | |||
+ | [[Media:debugassist-swe_3rd.zip]] - The final design. | ||
== Installation == | == Installation == |
Latest revision as of 03:10, 25 November 2010
Contents |
My Project
As part of my honours project, I am developing a plug-in to Eclipse to help illustrate how the state of software changes during runtime. My design study will focus of the data collection subsystem of my project.
Data collection works by inserting a data collection point (also known as a Pseudo Breakpoint) into a line of code in your program. Every time the application hits this data collection point, a copy of all variables in scope (local, instance and static variables) are recorded and the program resumes. At the conclusion of runtime, the user can review what happened and generate visualisations.
The entire plugin running showing visualisations
Adding/Removing a PseudoBreakpoint'
General Use Case
Goal: Find and resolve a bug within your code.
1. A user launches Eclipse
2. The user will insert a PseudoBreakpoint on a single line on their application
3. The user will then run their application (with debugging enabled)
4. Whenever the PseudoBreakpoint is hit a copy of all the variables are taken (stored as our own datatype) with other state information such as which thread was executing, the current date/time and so on.
5. At the conclusion of the program, the variables and state information is used to generate a diagram or other visualiation.
6. (Use these visualisations to help find and resolve the bug)
Main Subsystems
There are two main areas in the data collection subsystem that will be part of this design study:
1. Management (inserting / deleting of data collection points etc)
2. Event Handler (whenever the data collection point is encountered)
Management
The management area of this project is related to the creation, deletion, insertion and management of the data collection points (Pseudo Breakpoints). Currently, a data collection point is a thinly disguised breakpoint that is handled along side normal Java breakpoints.
Main activities:
- Creates Pseudo Breakpoints
- Manages the Pseudo Breakpoint
- Adds the extension to the Eclipse editor (so the user can toggle the Pseudo Breakpoints on/off)
Event Handler
The event handler controls all the data collection events within the system.
Main activities:
- Handles events when the Pseudo Breakpoint is 'hit'
- Variable datastore (when the breakpoint is hit a copy of all inscope variables are stored in a datastore)
Event Handler Process Flow:
1. Event Handler is notified that a breakpoint has been hit
2. Event Handler checks to see if it was a PseudoBreakpoint
3. If so, do data collection
4. Event Handler checks to see if there was another breakpoint also hit (a normal breakpoint)
5. If so, suspend application (so normal breakpoint activity can occur)
6. Else resume program
Design Study
Requirements
There are two main requirements that I hope to achieve by completing this design study:
- Maintainability - Easily add new features (such as different data sources etc) - Currently we only support a single data source.
- Extensible - Add additional programming languages (such as PHP etc) easily - Currently we only support Java.
Constraints
The single biggest limitation to this project is the Eclipse model. This is an Eclipse plug-in, and we must structure our code to fit the Eclipse way of doing things. This means we must implement certain Eclipse interfaces.
Initial Design
What design?
UML Diagram
For sanity sake, I've excluded all the methods and variables from the diagram. Suffice to say, there was a lot of unnecessary junk. Classes have been separated into different packages, however you may notice that it was done without care and there is a lot of unnecessary coupling.
Description of Classes
(package) debugassist:
- Activator (extends AbtractUIPlugin) - Activator class that is run when the Plugin is loaded
- DebugAssistLogic (implements DataUpdateBreakpointClient, DataUpdateEventClient) - Provides some basic functions to clean up breakpoint data, contains 2 clients which are activated when a breakpoint is inserted/updated/remove and when a breakpoint is 'hit'
(package) debugassist.datasource:
- DataBreakpointStore - Stores all the pseudo breakpoints in a HashSet.
- DataEventStoreTime - Store all the data when a breakpoint is hit.
(package) debugassit.events:
- DataUpdateBreakpointClient - An interface that needs to be implemented if you wish to listen to Breakpoint Events (i.e. when a breakpoint is added/removed etc)
- DataUpdateBreakpointListener - The service that handles Breakpoint listener clients (where clients subscribe to events etc)
- DataUpdateEventClient - An interface that needs to be implemented if you wish to listen to Breakpoint Data Events (i.e. when a breakpoint is encountered by the debugger).
- DataUpdateEventListener - The service that handles Breakpoint data listener clients (where clients subscribe to events)
(package) debugassist.handlers:
- BreakpointAddRemoveEventHandler (implements IBreakpointListener) - handles the breakpoint events from Eclipse (i.e. when a breakpoint is added / removed from Eclipse) - and notifies DataUpdateBreakpointListener.
- DebugEventHandler (implements IDebugEventSetListener) - Handles the breakpoint hit events from Eclipse (i.e. when a breakpoint is hit within Eclipse) - this performs data collection (collects all variables in scope), and notifies DataUpdateEventListener.
- EclipseDebuggerEventListenerManager - Attaches BreakpointAddRemoveEventHandler and DebugEventHandler to Eclipse.
(package) debugassist.model:
- BreakpointEvent - A model for whenever a breakpoint is hit by the debugger (stores time, variables)
- ModelGenerator - A utility class for converting IVariable (Eclipse type) to Variable (our model type)
- Variable - A model for a Variable
(package) debugassist.pseudobreakpoint
- AddRemovePseudoBreakPointHandler (extends AbstractRulerActionDelegate) - PseudoBreakpoint handler type - so users can right click in Eclipse to add/remove a breakpoint.
- PseudoBreakpointImpl (extends JavaLineBreakpoint) - The breakpoint used to represent our PseudoBreakpoint. It is actually just a JavaBreakpoint with a different type and icon so the DebugEventHandler knows which breakpoints to collect.
Inherited Classes
A few classes are extended and implemented in this project. For completeness, I have included their details here:
AbstractUIPlugin - An abstract class providing some basic functionality required to create an plugin in Eclipse. AbstractUIPlugin Javadoc
IBreakpointListener - An interface used by classes that require notification when breakpoints are added and removed IBreakpointListener Javadoc
IDebugEventSetListener - An interface used by classes that require notification when Debugger Events occur IDebugEventSetListener Javadoc
AbstractRulerActionDelegate - An abstract class to allow contributions to the vertical ruler's context menu in the text editor. AbstractRulerActionDelegate Javadoc
JavaLineBreakpoint - A class which provides the Java Line Breakpoint Implementation (required to insert a breakpoint into the JVM)
Design Critique
This program was created out of the prototyping space, and as such, there was not much consideration to design. There are therefore many things wrong!
Maxims Violated
Avoid inheritance for implementation My PseudoBreakpoint class directly inherits the Javabreakpoint class so that it can interact with the JVM. This effectively stops any other programming language from using our plugin.
Separation of concerns DebugEventHandler handles the Breakpoint data events as well as being an Event Listener.
Beware singletons There is a number of singleton classes in here including: DataUpdateBreakpointListener, DataUpdateEventListener and DebugAssistLogic. Something doesn't smell right here... Perhaps this could be refactored into a single class?
Single responsibility principle & Avoid god classes DebugAssistLogic seems to be doing too much God object - it is listening to both Breakpoint and Data events, as well as providing data cleanup functionality.
Program to the interface not the implementation Everything uses the direct class (no interfaces used) - Again, this limits our project to Java, and makes it hard to extend our application.
Software reuse DataUpdateBreakpointListener and DataUpdateEventListener both have the same code to handle the adding and removal of listener classes. Duplicate code smell
Model the real world The class and package names are currently inconsistent, and it isn't obvious which classes do what.
First Attempt Design Improvements
Main Improvements
- Class names have become clearer (PsuedoBreakpointRulerActionDelegate & PseudoBreakpointRulerAction should be clearer than AddRemovePsuedoBreakpointHandler & PseudoBreakpointImpl)
- PseudoBreakpoint Factory
- Events Improved
- Model Generator moved
PseudoBreakpoint Factory
Previously the PseudoBreakpoint class was a extension of the JavaBreakpoint class. In order to support other programming languages (in the future) I created a Factory Method to return the appropriate Breakpoint class to insert into the virtual machine.
- PseudoBreakpoint (extends IBreakpoint) - A placeholder type for our PseudoBreakpoint
- PseudoBreakpointCreator - Determines which sort of Breakpoint to create and returns the generic PseudoBreakpoint
- PseudoBreakpointPython / PseudoBreakpointJava (implements PseudoBreakpoint) - an extension of the Java/Python breakpoint class.
Events (BreakpointDataEvent / BreakpointChangeEvent)
Observer - My original design was kind-of an Observer pattern (but poorly implemented - both classes had the same code for adding/removing observers (Don't repeat yourself)). As Java's Observer pattern does not suport generics, I decided to create my own. A generic abstract class 'Subject' handles subscribers.
- Subject - Abstract class to handle observers attaching/detaching to the event handler
- DataUpdateEventObserver (was DataUpdateEventClient) - Essentially the same, but now with a proper name
- DataUpdateBreakpointObserver (was DataUpdateBreakpointClient) - Essentially the same, but now with a proper name
- BreakpointDataEvent [extends Subject, Implements IDebugEventSetListener (Eclipse)] (was DataUpdateEventListener) - Handles Breakpoint Data Events (i.e. when a breakpoint is 'hit') from the Eclipse Debugger directly and notifies subscribed observers
- BreakpointChangeEvent [extends Subject, Implements IBreakpointListener (Eclipse)] (was DataUpdateEventListener) - Handles Breakpoint Add/Remove Events from the Eclipse directly and notifies subscribes observers and the DataBreakpointStore
Variables / BreakpointEvent
The model generator class is now an inner class within the BreakpointEvent class (as it does not need to be exposed).
Second Attempt
Changes to the First Attempt
I wasn't completely happy with my first design attempt, so I decided to run another design iteration over it.
Main changes include:
- Redesigned variable section. The main issue with the old variable class was that primitive variables were represented in the same way as an object - so they had the functionality to add children and so on.
- Abstract class Variable (contains most of a variable definition)
- PrimitiveVariable class (contains data about primitive variables)
- ObjectVariable class (contains data about objects)
- Redesigned Events section
- One of the design goals was to easily add new data sources, until now that hadn't been addressed!
- Now there is a Abstract EventSource class (which is used by classes which generate events)
- PseudoBreakpointEventData is an EventSource that will be hit whenever a PseudoBreakpoint is hit
- EventHandler extends Subject and is called by EventSource variables whenever they change.
- Fixed PseudoBreakpointRulerActionDelegate classes. Previously it was implementing an interface, and I had a lot of duplicate code to get it working, now I have switched to an Eclipse supplied superclass that provides the required functionality (Software_reuse).
- Removed the last Singleton pattern from BreakpointEventDataStore
- Turns out that it wasn't even necessary
- It is now all handled by the Activator class (which handles the creation of the plugin)
- Removed DataBreakpointStore. We didn't need it.
- You_ain't_gonna_need_it. It was only there for completeness, and wasn't used by my code at all.
- It is unnecessary to take a copy of the installed PseudoBreakpoint as the underlying Eclipse model knew where the PseudoBreakpoint was installed. We can query the Eclipse model if we need to know where it is. This prevents redundancy.
- Renaming of Classes to better reflect their task and make it more consistant.
- Collections implementations are now hidden (most generic type possible)
- Collections are now returns as unmodifiable (where applicable)
- Packages were also revisited, a clear description of each package has now been defined:
Packages
Package | Function (Subsystem) | Description |
---|---|---|
debugassist | Both | The root package. Only contains a single class (Activator). |
debugassist.datasource | Event Handling | Contains the data store for all the event data |
debugassist.events | Event Handling | Encapsulates all the Event handling classes (from the Eclipse model) |
debugassist.model | Event Handling | Our custom model (Variables and BreakpointEvents) |
debugassist.pseudobreakpoint | Management | Classes to handle and represent the PseudoBreakpoint in the text editor and in the associated virtual machines.
|
Class Diagram
Class Descriptions
Package | Class Name | Description | Public Attributes | Public Methods (returns void unless specified) |
---|---|---|---|---|
debugassist (root) | class Activator (Extends AbstractUIPlugin) | This class launches the entire plugin. It is instantiated by Eclipse itself, and operates in a similar way to a singleton. It contains the logic to attach all the event handlers to Eclipse. | None | public Activator() - public constructor (required by Eclipse); start(BundleContext context) - starts and launches the plugin (and attaches event handlers); stop(BundleContext context) - destroys instance, removes event handlers; static Activator getDefault() - Returns the current instance of the running plugin; static ImageDescriptor getImageDescriptor(String path) - Returns the image descriptor for the image file. |
debugassist.datasource | class BreakpointEventDataStore (implements DataUpdateEventObserver) | This class stores a copy of the BreakpointEvents as they occur. | None | ArrayList<BreakpointEvent> getAllBreakpointEvents() - returns the data store; clearEvents() - removes all items from the data store; updateModelRequired(String frameName, String threadName, IVariable[] variables) - A method from the DataUpdateEventObserver interface - it is called whenever a PseudoBreakpoint is hit. |
debugassist.events | abstract class Subject<T> | Provides basic functionality to add and remove observers. | None | attach(T child) - adds a child who requires notification when something changes; detach(T child) - removes the child from notification; protected Set<T> getObservers() returns a set of the observers. |
debugassist.events | interface DataUpdateEventObserver | Any class which wishes to be notified whenever a PseudoBreakpoint is hit must implement this interface. | None | updateModelRequired(String frameName, String threadName, IVariable[] variables) - Whenever a PseudoBreakpoint is hit, this method will be called - containing the frame name, thread name, and an array of IVariables (An Eclipse model type) |
debugassist.events | class EventHandler extends Subject<DataUpdateEventObserver> | A container class which holds all the subjects who wish to be notified when an event occurs | None | updateAll(String frameName, String threadName, IVariable[] vars) - to be called by an EventSource whenever an event occurs, dispose() - contains cleanup tasks (removing all observers) |
debugassist.events | abstract class EventSource | An abstract class that all datasources must implement | None | public EventSource(EventHandler eventHandlerInstance) - the only constructor (it must take in the EventHandler instance). protected notifyEventHandler(String frameName, String threadName, IVariable[] vars) - notifies the event handler of a new event |
debugassist.events | class BreakpointEventData extends EventSource implements IDebugEventSetListener | Handles all Eclipse breakpoint data events (like when a breakpoint is hit by the debugger), and passes variable information to the EventHandler only when a PseudoBreakpoint. This works by checking to see if the breakpoint that was hit is an instance of PseudoBreakpoint. | None | handleDebugEvents(DebugEvent[] events) - Required by IDebugEventSetListener interface, is called whenever any breakpoint is hit in Eclipse. Our implementation determines if the breakpoint was a pseudo breakpoint, and if so, notifies the EventHandler. |
debugassist.model | class BreakpointEvent | The representation of a breakpoint event (when a breakpoint is hit). Contains information such as time hit, thread name, stack name and variables that were relevant. | None | static BreakpointEvent generateBreakpointEvent(Date timeHit, int numHit, String threadName, String stackName, IVariable[] variables) - generates a new BreakpointEvent instance from IVariables (the Eclipse model); List<Variable> getVariables() - returns an unmodifiable list of all the variables; String getStackName() returns the stack name; int getNumHit() - returns which nTh time the breakpoint has been hit; Date getTimeHit() - returns the time the breakpoint was hit; String getThreadName() - returns the thread name; String toString() - The string representation of the object. |
debugassist.model | abstract class Variable implements Comparable<Variable> | Provides an abstract presentation of a variable. There is also a private class ModelGenerator that converts an IVariable array (the Eclipse type) to a list of Variables. | None | String getData() - returns a String representation of the data; String getType() - returns a String of the type; String getName() - returns a String of the name of the variable. int getUpdateCount() - returns the number of changes that variable has had; updateData(String data) - updates the data field of the variable (and increments the update counter); compareTo(Variable o) - compares another variable against itself for sorting (based on the name of the variable); static List<Variable> generateFromVariableArray(IVariable[] vars) - generates the entire variable model from the Eclipse model (with help from the inner ModelGenerator class). |
debugassist.model | class PrimitiveVariable extends Variable | Represents a primitive variable (eg int) | None | boolean isLeaf() - always returns true (as will never have children); String toString() - Returns a string representation of the primitive; String getShortName() - returns a shortened version of the toString() method |
debugassist.model | class ObjectVariable extends Variable | Represents an Object (which contains references to other Variables) | None | (The same as PrimitiveVariable) plus: Map<String, Variable> getReferences() - returns all the Variables which this variable refer to; addReference(String name,Variable ref) - Adds an object to the collection |
debugassist.pseudobreakpoint | interface PseudoBreakpoint extends ILineBreakpoint (Eclipse Model) | An interface to represent a breakpoint which will be used by Eclipse (for use in the text editor) and by the programming language (to actually insert a breakpoint into the VM) | None | None |
debugassist.pseudobreakpoint | class PseudoBreakpointCreator | Contains a factory method to determine which kind of PseudoBreakpoint type to create depending on the programming language in use. | None | static PseudoBreakpoint factoryMethod(IResource resource, String typeName, int lineNumber) - Determines which PseudoBreakpoint type to create |
debugassist.pseudobreakpoint | class PseudoBreakpointJava extends JavaLineBreakpoint (Internal Eclipse JDT breakpoint representation) implements PseudoBreakpoint | A class to represent a PseudoBreakpoint for Java. | None | public PseudoBreakpointJava(final IResource resource, final String typeName, final int lineNumber) - (constructor) - Creates a breakpoint representation, and marker (for use in the text editor); String getMarkerMessage(boolean conditionEnabled, String condition, int hitCount, int suspendPolicy, int lineNumber) - Modifies the name of the PseudoBreakpoint (for identification only); |
debugassist.pseudobreakpoint | class PseudoBreakpointPython implements PseudoBreakpoint | A class to represent a PseudoBreakpoint for Python. | None | Incomplete - it has not yet been implemented. |
debugassist.pseudobreakpoint | class PseudoBreakpointRulerActionDelegate extends AbstractRulerActionDelegate | The delegate class used when the ruler menu action 'Add/Remove PseudoBreakpoint' is used | None | IAction createAction(ITextEditor editor,IVerticalRulerInfo rulerInfo) - Creates a new PseudoBreakpointRulerAction |
debugassist.pseudobreakpoint | class PseudoBreakpointRulerAction extends Action (inner class of PseudoBreakpointRulerActionDelegate) | The class used when the ruler menu action 'Add/Remove PseudoBreakpoint' is used | None | public PseudoBreakpointRulerAction(ITextEditor e, IVerticalRulerInfo r) - constructor - Creates the menu action; runWithEvent(Event event) - the method which is called when the 'Add/Remove PseudoBreakpoint' menu item is used. Creates a PseudoBreakpoint and adds it to the Eclipse breakpoint manager or will delete the PseudoBreakpoint (if one already exists) |
Design Patterns Used
Observer - I used the Observer pattern in the event handling system.
- Subject = Subject<T>
- ConcreteSubject = EventHandler
- Observer = DataUpdateEventObserver
- ConcreteObserver = BreakpointEventDataStore
There are a few changes to the normal Observer pattern (as you can see from the UML diagram!). Essentially, the main difference is to allow multiple different EventSources to notify a number of Observers. It should be easy to add new observers, and new data sources (even though in this example we only have a single data source and observer). The Subject class provides generic and templated functionality to attach and detach a number of observers. Java has a built in Subject class, however it does not support generics. EventHandler is the realization of the Subject class. EventSource is an abstract class whereby the public constructor requires an EventHandler as the argument. Any EventSource must also take then EventHandler as the constructor argument (this is to enforce the Tell don't ask principle).
The interface DataUpdateEventObserver is required by any class that wants to subscribe to all the data events (in this case, our BreakpointEventDataStore). This also differs slightly from the standard Observer pattern. In the normal Observer pattern, it is up to the concrete observers to query the underlying model to fetch the updates. In my design, all subscribers are told of the changes. This helps decrease coupling between EventSources and the subscriber classes. It also means that we can have a number of new data sources without having to change any of the observer classes. It also helps with a potential concurrency issue - concrete observers would need to query the underlying data model. If a PseudoBreakpoint was inserted on a program that had multiple threads accessing the same method, the underlying data might change between the observer being notified and the observer querying the data model that could potentially cause data to be lost.
Composite - I used a Composite pattern to create the notion of a Variable.
- Component = Variable
- Leaf = PrimitiveVariable
- Composite = ObjectVariable
A Variable can either be a primitive or an object. Primitives types in Java are: byte, short, int, long, float, double, boolean, char. Object types are composed of other Variables (other objects or primitives).
Factory Method - A factory method has been used to determine which type of Breakpoint to generate. This allows us to easily add new programming languages without too much effort.
- Factory = PseudoBreakpointCreator
- Product = PseudoBreakpoint (extends org.eclipse.debug.core.model.ILineBreakpoint)
- ConcreteProduct = PseudoBreakpointJava / PseudoBreakpointPython
Design Maxims Followed
Acyclic dependencies principle - There are no cycles between packages. The debugassist package (actually Activator class) needs to instantiate the both Datasource Events packages. DataSource package implements the interface provided by the Events package. The Event package needs to check to see if a breakpoint event was an PseudoBreakpoint instance (from the PseudoBreakpoint package), and the DataSource package needs to store information from the Model package.
Don't expose mutable attributes - All collections have been modified so they are returned as unmodifiable collections. While it does not provide any additional security, it avoids the data collections from being accidentally modified.
Avoid equals - There is currently no equality test between objects. For example, variable objects are only created once and stored in a collection. Nothing else could ever be exactly the same (even if the variable contained the same name, type and value) - it would be a different collection time.
Getters and setters - All variables are hidden and only accessed by the minimum required getters and setters. For example, a variable can never change its name once constructed (so only a getter), but it can change its value (both getters and setters)
Single responsibility principle - Each class has a distinct and separate responsibility. For example, the abstract Subject<T> class used to be integrated into the EventHandler class. By creating an abstract class, anything that requires subscribers can now inherit the Subject class (this promoties Software reuse).
Hide your decisions - Collections are hidden behind the most generic type possible (eg list etc).
Single responsibility principle - In this final iteration I attempted to split the classes further down. In the previous iteration, the BreakpointDataEvent class handled breakpoint events and also notified all subscribed classes. This class had two distinct responsibilities (handling events and handling subscribers). It has now been split into two classes - PseudoBreakpointEventData (a subclass of the newly created EventSource class) and EventHandler.
Open_closed_principle - In the previous iteration the design made it very difficult to be extended. It would have been had to add additional functionality (such as new programming languages or new data sources) without major changes. New language support can be added by extending the abstract PseudoBreakpoint class. New data sources can be added by extending the abstract EventSource class.
Do the simplest thing that could possibly work - From the original design, the Variable class had many methods which did similar things (such as updateVariableCount() and incrementUpdateByOne(), isLeaf() and hasChildren(), etc). The interface to Variable has been simplified to only support the minimal number of operations. This should help maintainability, as if there are any changes required to the underly model only the minimal number of items need to change.
Design Maxims Considered
Singleton - In the initial designs there was a lot of Singletons used (DataUpdateBreakpointListener, DataUpdateEventListener, DebugAssistLogic, and to some extent Activator). However, as the design progressed, it became obvious that this was not entirely necessary. Rather than have a Singleton for each of the classes, only a single singleton was required to instantiate all the other objects. The most obvious place for this would be in the Activator class. However, the Eclipse model meant this was slightly more complicated. Eclipse instantiate the Activator class and then call the start() method. At this point all the event handlers are attached, and the plugin is loaded. When the plugin is closed Eclipse tells the Activator class to shutdown (removing all the event handlers and references) - and thereby clearing the memory. Unfortunately, because the Activator class requires a public constructor there is the potential for two instances of the Activator class to be created. It is up to the underlying Eclipse model to ensure that only a single instance of the Activator class at given point in time.
Design Maxims Violated
Avoid side effects - One method in Variable has a slight side effect that might catch a developer off-guard. Whenever the updateData method is called, a counter is incremented (so we can keep track of how many changes this object has). If someone did not realize this happens in their own developed subclass they may accidentally increment the counter twice. I still do not believe this is complete violation of the rule, but something to keep in mind.
Big design up front - There have been three (documented) iterations of development. This project began out of prototyping and evolved into the current product, it was therefore impossible to complete a big design up front as I was unaware of what I would need down the track.
Avoid inheritance for implementation - PseudoBreakpointJava extends Eclipses Java Breakpoint implementation to provide our PseudoBreakpoint for the Java Language. It would have been significantly harder for our PseudoBreakpoint to re-implement our own Java Breakpoint.
Don't burn your base class - The class EventHandler extends Subject to provide the observer functionality (also see Avoid inheritance for implementation). I cannot think of a situation where EventHandler would need to subclass anything other than Subject (famous last words.) The current Java implementation of listeners is exactly the same as my design except I use generics (I'm not saying that Java was designed well!) The EventHandler class only notifies other classes when breakpoint events occur, and does not provide any other functionality. Any EventSource subclass will also suffer from the same problem. If they required an EventSource to contain a different subclass, they could create another class (with the required superclass) that could be composed of a new EventSource subclass.
Class Encapsulation - Variable / ObjectVariable & PrimitiveVariable - Class encapsulation is broken. Some of the variable within the Variable class are marked as protected so the subclasses are able to access them and modify them. The power of the subclasses accessing the variables outweighs any risk.
Files
Media:debugassist-swe_init.zip - The initial design.
Media:debugassist-swe_3rd.zip - The final design.
Installation
- Download & Install Eclipse 3.5.2 (with SDK)
- Extract the zip file of my project
- Run Eclipse
- Click File -> Import -> Existing Projects into Workspace
- Select root directory of extracted plugin
- Click Finish
- Create a new Run Configuration: 'Eclipse Application'