Open Collections

UBC Theses and Dissertations

UBC Theses Logo

UBC Theses and Dissertations

Dependencies in the context of aspect-oriented programming Gudmundson, Stephan 2002

Your browser doesn't seem to have a PDF viewer, please download the PDF to view this item.

Item Metadata

Download

Media
831-ubc_2002-0090.pdf [ 1.5MB ]
Metadata
JSON: 831-1.0051218.json
JSON-LD: 831-1.0051218-ld.json
RDF/XML (Pretty): 831-1.0051218-rdf.xml
RDF/JSON: 831-1.0051218-rdf.json
Turtle: 831-1.0051218-turtle.txt
N-Triples: 831-1.0051218-rdf-ntriples.txt
Original Record: 831-1.0051218-source.json
Full Text
831-1.0051218-fulltext.txt
Citation
831-1.0051218.ris

Full Text

Dependencies in the Context of Aspect-Oriented Programming by Stephan Gudmundson B.Sc, University of British Columbia, 1999  A THESIS SUBMITTED IN PARTIAL F U L F I L L M E N T OF THE REQUIREMENTS FOR T H E D E G R E E OF Master of Science in T H E FACULTY OF G R A D U A T E STUDIES (Department of Computer Science)  We accept this thesis as conforming to the required standard  The University of British Columbia May 2002 © Stephan Gudmundson, 2002  In  presenting  degree  at  this  the  University  freely available for copying  of  this  department publication  or of  thesis  this  fulfilment  of  of  Columbia,  I agree  British  reference  thesis by  i n partial  for  his  and study. scholarly  or  thesis  for  her  I further  purposes  gain  permission.  Department of  fn^^W  The University of British Vancouver, Canada  Date  DE-6 (2/88)  ^ ' , \  S C'l I Columbia  ^0  02.  lft  shall  requirements that  agree that  may  representatives.  financial  the  It not  be is  the  Library  permission  granted  by  understood be  for  allowed  an  advanced  shall make for  the that  without  it  extensive  head  of  my  copying  or  my  written  Abstract Software developers use dependencies to understand designs. An inappropriate dependency can be confusing, reduce theflexibilityof the system or re-usability of a module, or cause problems in system maintenance or evolution. This thesis proposes three new kinds of dependencies: whole-class, whole-subsystem and call-graph dependencies. Several examples of each dependency are encountered in the analysis of two implementations of the same system, one in the AspectJ programming language and the other in the Java programming language. The expected effect of these dependencies on comprehensibility, evolvability and re-usability is described. Finally, the new dependencies proposed in this thesis are set in the context of existing work.  Contents Abstract  11  Contents  iii  List of Tables  v  List of Figures  vi  1 Introduction  1  2 Dependency Characterization  2  3  2.1  Overview  2  2.2  Name-based Dependencies  3  2.3  Whole-Class Dependencies  4  2.3.1  Whole-Interface Dependencies  4  2.3.2  Whole-Implementation Dependencies  5  2.4  Whole-Subsystem Dependencies  5  2.5  Call Graph Dependencies  6 7  Dependency-Based System Analysis 3.1  Overview  3.2  The System  3.3  Concerns  7 .'  7 8  iii  3.4  AspectJ Implementation  3.5  Java Implementation  10  3.6  Dependency Differences  13  3.6.1  Point Bounding Feature  13  3.6.2  Move Notification Feature  14  3.6.3  Display Updating Rule  16  3.6.4  Tracing  19  3.6.5  Factory Pattern Rule  21  3.7  9  Summary  22  3.7.1  Name-Based Dependencies  22  3.7.2  Whole-class Dependencies  25  3.7.3  Whole-Subsystem Dependencies  25  3.7.4  Call-Graph Dependencies  27  4 Implications of Dependencies  28  4.1  Comprehensibility  28  4.2  Evolvability  29  4.3  Re-Usability  30  5 Related Work  32  6 Conclusion  34  6.1  Summary  34  6.2  Future Work  35  Bibliography  36  List of Tables 3.1  Dependency differences in the implementation of point bounding  13  3.2  Correspondence between dependency differences in point bounding  14  3.3  Dependency differences in the implementation of basic move notification . . .  15  3.4  Correspondence between dependency differences in basic move notification . . 17  3.5  Dependency differences in the implementation of nested move notification suppression  3.6  18  Correspondence between dependency differences in nested move notification suppression  18  3.7  Dependency differences in the implementation of display updating  18  3.8  Correspondence between dependency differences in display updating  19  3.9  Dependency differences in the implementation of tracing  20  3.10 Correspondence between dependency differences in tracing  20  3.11 Dependency differences in the implementation of factory pattern enforcement  21  3.12 Correspondence between dependency differences in factory pattern enforcement 22 3.13 Dependency Difference Summary  23  3.14 Modularization Difference Summary  24  List of Figures 3.1  Dependency differences created by using explicit method invocations  3.2  Using an abstract method to obscure the true identity of the invocation target 25  3.3  Whole-class dependency addressed with explicit method invocations  26  3.4  Whole-class dependency addressed with pointcut and advice  26  vi  24  Chapter 1  Introduction Software developers use dependencies to understand designs. A dependency is a connection between implementation entities that indicates that one of the entities can affect the other. For example, a dependency is created whenever a software module invokes functionality in another software module. Inappropriate dependencies can be confusing, reduce the flexibility of the system or the re-usability of a module, or cause problems in the maintenance and evolution stages [7]. In this thesis, I propose three new kinds of dependencies for software developers to consider. These dependencies have come to light in the context of aspect-oriented programming[3], but they are not introduced by AOP. To demonstrate this, we provide examples in both an AOP and non-AOP programming language. This thesis uses the AspectJ[4] and Java™ programming languages and assumes that the reader is familiar with their syntax and semantics. The remainder of this thesis is organized as follows. Chapter 2 describes the dependencies generally. Chapter 3 presents an application of the use of the dependencies in analyzing implementations of a simple system in AspectJ and Java. Chapter 4 discusses some of the implications of the results of the analysis in Chapter 3. Chapter 5 surveys related work and Chapter 6 concludes the thesis.  1  Chapter 2  Dependency Characterization 2.1  Overview  This thesis proposes three new kinds of dependencies and positions the existing common notion of dependency relative to these. It is not the intent of this thesis to develop a comprehensive set of dependency types, but only to identify several kinds of dependencies within the general category of dependencies. The dependencies used in this thesis are described briefly below. 1. Name-based dependencies. These are the dependencies created wherever some element is referenced by name. In Java and AspectJ, the referent can be a class, interface, aspect, method, field, or pointcut. For example, a method invocation is a use of the name of the invoked method. These are the dependencies that developers have used for decades to understand system structure. 2.  Whole-class dependencies. These are dependencies created wherever the  whole class depends on something, or something depends on the whole class. For instance, an aspect might need to know all of the ways that a Point can be moved, so that it can react to movements consistently across all move operations. 3. Whole subsystem dependencies. These are dependencies created wherever some element must know all of the classes in the system or a specific subsystem (package,  2  packages or entire system) that meet some criteria. 4. Call graph dependencies. These are dependencies created wherever some element makes assumptions about the nature of the call graph. For example, in AspectJ, such dependencies typically arise from using cflow pointcuts.  2.2  Name-based Dependencies  These dependencies arise when one syntactic element names another. The common notion of dependency is the name-based dependency. In Java, there are several ways that name-based dependencies are created: • Afieldaccess introduces a dependency on thefieldsignature onto the code making the access. For example, if method getColor accesses the variable color, then getColor depends on color. • A method invocation introduces a dependency on the method signature onto the code making the invocation. For example, if method setWidth calls method setX, then setWidth depends on setX.  • Afield,parameter, or local variable declaration introduces a dependency on the type of the declaration onto the code making the declaration. For example, thefielddeclaration Color color depends on the class Color. • A method signature depends on its return type. For example, the method Color getColor() depends on class Color because its return value is of type Color. • Expressions depend on all types of nested expressions. For example, rect.getColor().toString(), where rect is of type Rectangle, depends on Rectangle, Color, and String (the return  type of the toString method). Even though the Color class is not named explicitly, the expression depends on it through the method signature Color getColorQ. • Inheritance and interface implementation introduce a dependency on the type of the extended or implemented onto the extending or implementing type. For example, in  3  the declaration class Rectangle implements Shape, the Rectangle class depends on the  Shape interface. • Method overriding or implementation of an abstract/interface method introduce a dependency on the signature of the overridden/implemented method onto the overriding/implementing method. For example, if setWidth in class Rectangle is an implementation of the setWidth method declared by interface Shape, then Rectangle.setWidth depends on Shape.setWidth. AspectJ's additional language features can introduce name-based dependencies as well. Pointcuts can name methods, fields, class, interfaces and aspects, and in so doing become dependent on the referent. Advice depends on the pointcut at which it applies. Aspects can depend on classes or interfaces in their extends, implements, and of each clauses.  2.3 Whole-Class Dependencies Whole-class dependencies arise when either an entity depends on the whole class, or the whole class depends on an entity. Whole class does not mean that the dependency affects or is affected by literally every element in the class, but rather that the dependency is not confined to any particular element or elements of the class. We identify two kinds of whole-class dependencies: whole-interface and whole-implementation dependencies. The only difference between a whole-implementation and whole-interface dependency is the level of knowledge that the dependent entity requires. A whole-interface dependency indicates that the dependent needs only information about the interface, and not the implementation, of the module. A whole-implementation dependency indicates that the dependent needs information about both the interface and implementation of the module.  2.3.1  Whole-Interface Dependencies  These are dependencies upon the interface of a class. For example, consider the pointcut signature pointMoves(Point pt). This pointcut signature implies that the pointcut promises  4  to identify every join point at which a Point moves. This is a dependency on the whole interface of Point A whole-interface dependency is different from a name-based dependency on a class, such as would be created by a variable declaration that names the class. In that case, the dependency is only upon the name of the class, while the whole-interface dependency targets the interface elements (methods and/or fields) contained within the class. A whole-interface dependency is also different from the name-based dependencies on particular elements of a class created by a method call site. For a method call, the client only needs to know that the identified method is one way to get a given task done. In the whole-interface case, the client needs to know all of the ways that a task might be done. The difference is clear in the context of evolution. For instance, consider a client X wishing to move a Point instance pt dx units to the right. X can use pt.setX(pt.getXQ-hdx) to implement this operation. As an evolutionary change, suppose the Point class receives a moveBy method. X is not obligated to use this new interface, and is therefore not affected by its addition. However, the pointMove pointcut must be updated to capture calls to the new moveBy method.  2.3.2  Whole-Implementation Dependencies  These are dependencies on the whole implementation of a class. For example, suppose that Points are to be bounded within a given region of the plane. One implementation of this would have Point methods that alter coordinate values check that the new coordinates are valid before making any changes. The whole-implementation dependency in this case is that the whole implementation must perform these checks.  2.4  Whole-Subsystem Dependencies  These are dependencies upon the whole collection of classes, interfaces and aspects in a given package, packages or the entire system. For example, consider the pointcut moveQ: Rectangle.moveQ || Ellipse.moveQ (where the move pointcuts in Rectangle and Ellipse capture  5  movements of Rectangle and Ellipse). This pointcut is intended to capture the movements of all shapes, so it needs to know all of the classes that represent shapes. This is a wholesubsystem dependency. As with whole-class dependencies, the term w/io/e-subsystem dependency does not imply that the dependent is tied to everything in the subsystem, but rather that the dependency cannot be localized to particular modules in the subsystem.  2.5  Call Graph Dependencies  These are dependencies upon the dynamic call graph of the system. These dependencies arise commonly in using AspectJ's cflow construct. For example, the pointcut call(void Shape.repaint()) && cflow(call(void Shape.setColor(Color)) captures direct or indirect calls from Shape's setColor method to Shape's repaint method.  Call-graph dependencies usually include an indication of the purpose of the call. For instance, for the pointcut colorChangeRepaint():call(void Shape.repaint()) && cflow(call(void  Shape.setColor(Cqlor)) indicates not only that setColor may call repaint, but that the purpose of the call is to repaint the display because the color has changed. Call-graph dependencies exist in Java as well. For instance, the Swing library in JDK 1.2+ depends on calls to a variety of Swing methods being made in a certain Swing dispatch thread. This dependency allows Swing to avoid some locking and consequently improve the library's performance.  6  Chapter 3  Dependency-Based System Analysis 3.1  Overview  This chapter uses the dependencies introduced in the preceding chapter to reason about design. We consider two implementations of similar functionality, one in AspectJ and the other in Java. This analysis has two purposes. First, it shows the use of the dependencies in the context of both an AOP and non-AOP language, which demonstrates that the dependencies are not specific to AOP. Second, it provides a basis for comparing the AOP and non-AOP implementations, which provides some insight into the new capabilities offered by AOP.  3.2  The System  This example is taken from the "figures" example used in [5]. I obtained the AspectJ implementation from the AspectJ group and derived an almost-equivalent-functionality 1  pure Java implementation. from http://www.aspectj.org  1  7  The basic functionality is as follows. The system is concerned with representing and rendering 2D figures. The FigureElement interface represents a 2D object; in this implementation, only Point and Line are provided, but others like Rectangle, Ellipse, etc are easily imagined. FigureElements are components of a Figure, which is the entire 2D entity. Figure acts as a factory for its FigureElement components; the programmer is not to construct FigureElements directly. A Figure is rendered onto a Display, though this functionality is not provided in this sketch of the implementation. The only interaction with Display is: whenever a Figure changes, Display's needsRepaint method must be called to update the visual image presented to the user. For the Java and AspectJ implementations described below, there are several alternatives for the implementation. I have chosen what I believe to be representative of implementations one would see in practice. I have considered several other implementations, and the conclusions drawn in this chapter hold for most of these other implementations as well.  3.3  Concerns  The analysis is driven by examining specific concerns in the system. We consider three types of concerns: classes, rules, and features. A class is the usual notion of state plus behavior. A rule is a requirement of the system. A feature is a behavior that is not localized in the class structure. In our example, we will consider the following concerns: 1. The Point, Line, Figure, and Display classes and the FigureElement interface. These  are the primary data abstractions in the system. 2. Point's x- and y-coordinates are to be restricted to the range 0-100. This feature is referred to as point bounding.  3. Whenever any FigureElement moves, interested parties should be informed. This feature is referred to as (basic) movement notification.  8  3.1. As an extension to basic move notification, an additional requirement to suppress move notifications for logically nested FigureElements (such as the two Points that make up a Line). This rule is referred to as nested move notification suppression.  4. Several messages are to be printed to the console for debug purposes. This feature is referred to as tracing. 5. The display must be updated whenever the visual appearance of a FigureElement changes. This rule is referred to as display updating. 6. The system architecture mandates that programmers obey the factory pattern established by the Figure class for creating FigureElement object instances. This rule is referred to as factory pattern enforcement.  3.4  AspectJ Implementation  The AspectJ and Java implementations are largely similar. This subsection describes the AspectJ-specific parts of the implementation. It makes use of 4 aspects: Tracing, PointBoundsChecking, Displayllpdating, and Factory Enforcement.  Tracing prints messages whenever Point.setX/Y or Display.needsRepaint is called. In the case of Display.needsRepaint, the tracing message includes an indication of whether the call was made directly by Displayllpdating or not. The tracing messages are produced by advice on calls to the methods. The PointBoundsChecking aspect ensures that the coordinates of a Point remain within the range of 0-100. This is achieved with advice that runs before the Point.setX/Y methods. The advice throws an error if the parameter to the set call would move the Point outside of the valid area. The FactoryEnforcement aspect provides a compile-time check that only the factory methods in Figure construct Point and Line. That is, it ensures that there are no calls to constructors of Point or Line outside of the factory methods in Figure. It has no effect on the behavior of the system.  9  DisplayUpdating implements the requirement that Display.needsRepaint be called whenever the appearance of a Figure changes. This is implemented by advising calls to methods that can change the appearance of FigureElements, which in this case are only the operations that move FigureElements. The detailed semantics are that DisplayUpdating does not know which specific FigureElement has changed, and that nested move operations do not trigger repaints. The only nested move operation is within Line.moveBy, which nudges a line by given X and Y deltas. This method calls down into Point.moveBy. DisplayUpdating ensures that such calls to Point.moveBy do not trigger calls to Display.needsRepaint by using cflow to eliminate these nested calls in the pointcut definition.  3.5 Java Implementation This subsection describes how the AspectJ-specific parts of the implementation described above are implemented in Java. Tracing is implemented by scattering the print statements throughout the code base. For instance, the bodies of Point.setX/Y  include calls to Trace.println. (The dif-  ference between .setX and setX is described below.) Noting whether or not the call to Display.needsRepaint came from DisplayUpdating is not easily implemented in Java, so the behavior is slightly different: all calls to Display.needsRepaint log the fact that the method was called, and all call sites within DisplayUpdating log the fact that they are about to call Display.needsRepaint. This provides the equivalent information, but in a slightly different form: the presence or absence of the line from DisplayUpdating indicates whether or not the call to Display.needsRepaint came from DisplayUpdating. I believe that this is the most reasonable Java implementation, even if it deviates slightly from the AspectJ behavior. Similarly, bounds checking for Point is implemented by moving the tests for boundary violations into the Point.setX/Y  methods.  The compile-time FactoryEnforcement aspect can not be implemented statically as it is in AspectJ, because the standard Java compiler does not support thithis kind of static checking. As a compromise, the check is implemented at run-time instead. A special Token  10  class is introduced, which can only be created by the factory using Java's scoping rules. The constructors for Point, Line, and any other FigureElements take this type as a parameter in their constructors. If the token is non-null, then it must have been created by the factory. As long as the token is only given to trusted code, then it is proof that the factory is creating the FigureElement. If the token is null, then an exception is thrown to indicate the "unauthorized" attempt to construct the FigureElement. Implementing Display Updating's functionality in the Java model is more complex. The straightforward approach is to simply place calls to Display.needsRepaint directly into Point, Line and any other FigureElements that are added. However, this solution ties the FigureElement classes too closely to the display updating concern for most Java programmers. Instead, the familiar JavaBeans™ event model is employed. The concept of "move 2  events" is added at the FigureElement interface level. This has the effect of requiring all FigureElement classes to support the event model. Whenever a FigureElement moves, it must notify any registered MoveListeners by dispatching a MoveEvent. The event includes the identity of the moved FigureElement, but not the "before" and "after" positions. It is clear that the event machinery is doing more work than the Display Updating.move pointcut. The event mechanism is an interface for use by a wide range of clients, whereas the move pointcut is only for the DisplayUpdating aspect. As we will see, this difference has an impact on modularity, but we will be careful to point out where the modularity differences arise because of this difference in functionality. To relieve Point and Line of some burden, the listener registration methods and event dispatching functionality are implemented in the AbstractFigureElement class. Point and Line inherit from this class. They must call the protected AbstractFigureElement.fireMoveEvent method whenever they move. As mentioned earlier, the DisplayUpdating aspect in the AspectJ implementation avoids capturing nested move operations in its advice. I decided that this was an important http://java.sun.com/products/javabeans/  2  11  behavioral property that must be maintained in the Java version. Supporting this behavior requires several changes to Point and Line. Consider Point first. The implementation of Point.moveBy calls setX and setY to effect the move. If setX and setY were to callfireMoveEvent,then the call to Point.moveBy would generate at least two move events, one each for setX and setY, instead of one. To avoid this problem, Point is instead structured such that setX and setY are called only by clients, and two new methods, setX and .setY, are called internally. The internal methods do not fire move events. This pattern can suppress generation of nested events within a single class, but it does not solve the problem of nested operations across classes. In particular, Line.moveBy call Point.moveBy on each of its endpoints to effect the move. This would generate at least two move events, one from each endpoint, which is not acceptable. To solve this problem, Point.moveBy must be informed that event generation should be suppressed in some situations. This is accomplished by introducing an overloaded form of Point.moveBy that includes a boolean parameter fireEvent which indicates whether or not the move operation should generate an event. This pattern of overloading each move method with a form that includes a specific directive regarding event generation is repeated for all move methods on Point and Line. However, this is not propagating up to the FigureElement interface, because "regular" clients of Point and Line should not have control over event generation. In effect, it is a special interface. It would have also been possible to solve this problem using thread-local storage. In this design, the fireEvent indicator is stored in a thread-local variable instead of being passed on the stack. The two approaches are similar enough that we will only, consider the fireEvent parameter version.  12  3.6  Dependency Differences  3.6.1  Point Bounding Feature  In both implementations, the point-bounding feature is implemented in two parts. The check of whether a proposed coordinate falls within the valid range is implemented by checkX and checkY. In order to implement the bounding consistently, the implementation must also include the rule that "all potential moves of Points are validated before being applied." In the AspectJ version, the PointBounding aspect implements the point-bounding feature. The checkX and checkY methods are in the aspect and the "moves must be validated" rule is implemented by the before advice. In the Java version, the Point class implements the point-bounding feature. The checkX and checkY methods are in this class. The "moves must be validated" results in a non-localized dependency throughout the Point class. The dependency differences between the two implementations of this concern are shown in Table 3.6.1 and correlated in Table 3.2.  Point - setX -  {whole i/f)  Point (whole impl)  Core Point Class - setX  setY  -  PointBoundsChecking - before Point.setX - before Point.setY  AspectJ Implementation  setY  Point Bounds Checking checkX > checkY  Rule: all move methods must check new coordinates  Java Implementation  Table 3.1: Dependency differences in the implementation of point bounding In the AspectJ implementation, the pointcut used by the before advice in the PointBoundsChecking aspect names Point.setX, setY and moveBy. In addition, the aspect knows all the ways to change the coordinates of a Point. 13  In the Java implementation, the core Point class depends on the point bounding concern. Specifically, all of the methods that change a Point's coordinates call the checkX and/or checkY methods. In addition, the entire implementation of the core Point class depends on the "must validate coordinates" rule. This is a whole-implementation dependency because adding any new means of changing a Point's coordinate would be required to follow this rule. Table 3.2: Correspondence between dependency differences AspectJ Dependency Java Dependency before Point .setX -t Point.setX -> Point.checkX Point.setX; before Point.setX -» checkX PointBoundsChecking -)• Whole implementation of Whole interface of Point Point -> "must validate coords" rule  3.6.2  in point bounding Comments Reversed  Reversed  Move Notification Feature  We will consider basic move notification, and then suppression of notification of nested moves. Basic move notification would be without the suppression of nested move notifications. In the Java implementation, all of the methods with a fireEvent parameter would be absent, and all move methods would unconditionally callfireMoveEventas their last action. In the AspectJ implementation, the cflow component would be absent from the advice. Basic Move Notification In the AspectJ implementation, this concern is contained completely within the DisplayUpdating.move pointcut. Clients may receive movement notifications by attaching advice to this pointcut. In the Java implementation, the movement notification feature is implemented in several components. AbstractFigureElement provides the implementation of the event dispatching functionality. In addition, this design introduces a rule: "all FigureElement realizations must initiate movement notification whenever they are moved."  14  The dependency differences between the two implementations of this concern are shown in Table 3.6.2 and correlated in Table 3.4. All classes that implement FigureElement  FigureElement (whole subsystem)  L  DisplayUpdating - pointcut move  Move Notification - addMoveListener  (whole i/fj AbstractFigureElement - addMoveListener — fireMoveEvent  "FigureElement - moveBy (whole i/fl|  k  Point - setPl  Point — setX  i-| Line - setPl-  (whole i/f)| Rule: move methods must initiate notification. AspectJ Implementation  Java Implementation  Table 3.3: Dependency differences in the implementation of basic move notification In the AspectJ version, the Display Updating.move pointcut names specific methods in FigureElement, Point and Line. In addition, DisplayUpdating must know all of  the ways that each kind of FigureElement can move (the whole interface dependencies). DisplayUpdating also needs to know all of the classes that implement FigureElement (the whole-subsystem dependency). Finally, Point and Line refer to the FigureElement interface in their implements clause. In the Java version, FigureElement contains the move notification concern. Ab-  15  ?  stractFigureElement names the FigureElement interface in its implements clause, and implicitly names FigureElement. addMoveListener (the dependency is introduced because the signature in AbstractFigureElement must match the signature in FigureElement exactly, and so the implementers are tied to the abstract declaration). Each FigureElement class names AbstractFigureElement as its parent. Each of the methods in classes that implement FigureElement call AbstractFigureElement.fireMoveEvent.  The Java version has another collection of dependencies relating to the "move methods must initiate notification" rule. This rule exists at the subsystem level, rather than being localized within any particular class. All realizations of FigureElement must obey this rule throughout their implementation. Nested Move Suppression Feature The dependency differences between the two implementations of this concern are shown in Table 3.6.2 and correlated in Table 3.6. In the AspectJ implementation, the pointcut to which the after advice applies is based on an assumption about the call graph, as is described above. In the Java implementation, the implementations of Point and Line depend on the nested notification suppression rule. In any situation, before initiating a move notification, one would have to ensure that the move operation is not logically nested within some largerscope move operation. The dependencies correspond as follows:  3.6.3  Display Updating Rule  The dependency differences between the two implementations of this concern are shown in Table 3.6.3 and correlated in Table 3.6.3. In the AspectJ implementation, the Displayllpdating aspect contains the implementations of both the display updating rule and the move notification concern. The display updating rule is enforced by receiving move notifications by advice attached to the Dis-  16  AspectJ Dependency ptc move -¥ Point .setX/Y  Java Dependency Point.setX/Y -»• MoveEvent  fire-  Comments Reversed (fireMoveEvent corresponds with pointcut move, in that they are both the interface for announcing movements) Reversed  Line.setPl/P2 ->• fireMoveEvent ptc move -> FigElt.moveBy Point.moveBy — > fire- Reversed; in the Java case, MoveEvent; each realization of the abLine.moveBy -> fire- stract method contains the dependency MoveEvent Point — > FigureElement Point -¥ AbstractFigureEle- Java impl uses inheritance ment Line — > FigureElement Line —• AbstractFigureEle- Java impl uses inheritance ment n/a AbstractFigElt -> FigureEle- Introduced due to interface ment; implementation AbstractFigElt -> fireMoveEvent ptc move -» "all methods that Point -> Rule: "all move Reversed move Points" methods must initiate move notification" ptc move -» "all methods that Line - 4 Rule: "all move meth- Reversed ods must initiate move notifimove Lines" cation" ptc move "all methods that n/a In Java version, this is incormove FigureElements" porated into the above two ptc move — > "all classes that n/a The Java version avoids implement FigureElement" this dependency by tangling move notification into the FigureElement interface ptc move -¥ Line.setPl/P2  Table 3.4: Correspondence between dependency differences in basic move notification  17  (whole irnpf) Point - setX(x) - setX(x, fireEvent) (whole implM Displayllpdating - after move && !cflow(move)  Line - setPl(pl) - setPl(pl, fireEvent)  (call graph! Rule: notifications of moves of nested objects must be suppressed  move operation is in the cflow of another move operation • » a nested object is being moved  AspectJ Implementation  Java Implementation  Table 3.5: Dependency differences in the implementation of nested move notification suppression  AspectJ Dependency Association of nested method invocations with nested move operations  Java Dependency Point, Line Nested move notification suppression rule  Comments Transformed  Table 3.6: Correspondence between dependency differences in nested move notification suppression  18  Displayllpdating Move Notification - pointcut move  Core Displayllpdating - after move —  AspectJ Implementation  FigureElement  Figure - makePoint - makeLine "- updater  Move Notification - addMoveListener  DisplayUpdater - moved  MoveListener - moved  Java Implementation  Table 3.7: Dependency differences in the implementation of display updating playUpdating.move pointcut. In the Java implementation, Figure owns an instance of DisplayUpdater. This instance is registered as a listener with any FigureElement instances created by the move* factory methods via FigureElement.addMoveListener. DisplayUpdater names the MoveListener interface in its implements clause and provides a realization for the FigureListener.moved method. FigureElement's addMoveListener method names MoveListener as a parameter. AspectJ Dependency after move -> ptc move  Java Dependency DisplayUpdatertmoved MoveListener.moved  n/a  Figure —• addMoveListener; FigureElement —¥ MoveListener  n/a  Figure -¥ DisplayUpdater  -¥  Comments Note that this name dependency is not reversed: in both cases, the dependency runs from the client to the interface. The Java implementation is more general and therefore has more components and more dependencies. Figure needs to connect the concrete objects that interact via abstract interfaces.  Table 3.8: Correspondence between dependency differences in display updating  19  3.6.4  Tracing  The dependency differences between the two implementations of this concern are shown in Table 3.9 and correlated in Table 3.10. Trace Tracing - pointcut setXY - after setXY - after Display.needsRepaint.  Display  Display Tracing  — - needsRepaint  Display - needsRepaint  DisplayUpd ai£L - - moved D.U. i  DisplayUpdating  Tracing  AspectJ Implementation  Java Implementation  Table 3.9: Dependency differences in the implementation of tracing In the AspectJ implementation, the Tracing aspect refers to several methods in other classes. Perhaps more notable is the absence of any whole-interface dependencies, which pointcuts have created in all examples up to this point: Here, the Tracing aspect is applied to specific methods, whereas in other cases pointcuts were interested in all methods with a given property. In the Java implementation, Point, Display and Display Updater all contain separate tracing concerns; tracing in general is not recognizable as a coherent concern. All methods 20  that do tracing name Trace.println. AspectJ Dependency ptc setXY -» Point.setX, setY after Display.needsRepaint -¥ Display.needsRepaint  Java Dependency Point.setX, setY Trace.println Display.needsRepaint i t ace.println  ->  Comments Reversed  -fr-  Reversed  Table 3.10: Correspondence between dependency differences in tracing  3.6.5  Factory Pattern Rule  The dependency differences between the two implementations of this concern are shown in Table 3.11 and correlated in Table 3.12. All object constructions happen in |^ the body of a factory method i (call graph H  Factory Enforcement - pointcut illegalNewFigElt — - declare error illegalNewFigElt FigureElement  Rule: all FigureElement classes must check factory pattern (whole impl)  Figure - makeLine  - makePoint AspectJ Implementation  (whole  Java Implementation  Table 3.11: Dependency differences in the implementation of factory pattern enforcement In the AspectJ implementation, the illegalNewFigElt pointcut names the FigureElement and Figure classes directly. In addition, it assumes that every method in Figure (or a subclass) whose name starts with "make" is a FigureElement factory method, and viceversa. Note that no whole-subsystem dependency is present, because AspectJ can determine 21  all classes that implement the FigureElement interface automatically. The AspectJ implementation also includes a call-graph dependency arising from the use of the withincode pointcut designator. The AspectJ idiom call(x) && withincode(y) means "j/ directly calls x". This is more narrow than call(x) && cflow(y) which means "y directly or indirectly calls x". In our example, the dependency is that the Figure.make* directly constructs the FigureElement instance. In the Java implementation, the factory enforcement rule is not localized within any single class. Instead, the implementation of each FigureElement class depends on the rule. In addition, there are references in the signatures of the Point and Line constructors as well as in the bodies of the make* factory methods in Figure to the Token class. AspectJ Dependency ptc illegalNewFigElt -» Figure; ptc illegalNewFigElt "factory methods start with make"; ptc illegalNewFigElt -» FigureElement ptc illegalNewFigElt -> callgraph dependency  Java Dependency Figure.makePoint -> Token; Figure.makeLine — > Token  Comments Reversed and summarized  Point whole impl —> factory Transformed enforcement rule; Line whole impl — > factory enforcement rule  Table 3.12: Correspondence between dependency differences in factory pattern enforcement  3.7 Summary This section summarizes the differences between the two implementations from several perspectives. Table 3.13 summarizes the dependency differences on a per-concern basis. Table 3.14 summarizes the differences between the modularizations of the two implementations. Finally, per-dependency-type summaries are provided in sections 3.7.1 to 3.7.4.  22  Concern  Display DisplayUpdating  Dependencies only in Java impl. follows bounding, factory enforcement, move notification rules calls move notification names AbstractFigElt follows rule "move ops issue move notifications" follows rule "move ops validate coords" contains factory enforcement calls move notification names AbstractFigElt follows rule "move ops issue move notifications" contains move notification concern names factory enforcement names DisplayUpdater (none) (none)  FactoryEnforcement  (none)  PointBoundsChecking  (none)  Tracing AbstractFigElt  (none) contains impl of move notification names interface of move notification  Point  Line  FigureElement Figure  Dependencies pectJ impl. (none)  (none)  (none) (none) (none) names Point.setX/Y, Line.setPl/P2, FigureElement.moveBy whole-subsystem dependency on all FigureElements for each FigureElement class, whole-interface dependency on all move methods whole-interface dependency on Figure: named make* factory method names Point.setX/setY/moveBy whole-interface dependency on all Point movers names various methods (none)  Table 3.13: Dependency Difference Summary  23  only in As-  Concern Point Line FigureElement Figure Display Display updating Move notification Point bounding Logging Factory enforcement  Java Contains bounding, logging, factory enforcement Contains factory enforcement Contains move notification Contains factory, "wiring" for display updating (modularized) (modularized) Contained in FigureElement, AbstractFigureElement Contained in Point Contained in various Contained in Point, Line  AspectJ (modularized) (modularized) (modularized) Contains factory (modularized) Contains move notification Contained in DisplayUpdating (modularized) (modularized) (modularized)  Table 3.14: Modularization Difference Summary  3.7.1  Name-Based Dependencies  Point bounding and tracing demonstrated a common difference involving name-based dependencies. A general characterization of the difference is shown in Figures 3.1 and 3.7.1. In the Java implementation, several methods calleri,  all call the method callee. In the  corresponding AspectJ code, a pointcut p names each of the calleri methods, and advice attached to p invokes callee. The overall effect in both cases is that callee runs either before or after any of the calleri methods run. The key feature of this pattern is that it removes dependencies from the coders, creating corresponding dependencies in the pointcut p. Several variants are possible depending upon how the elements are grouped into classes and aspects, so the class/aspect structures are not shown.  method caller  1  method callee method caller  2  Figure 3.1: Dependency differences created by using explicit method invocations A variant of this pattern is observed in the combination of basic move notifica-  24  method caller! method caller  2  zn  pointcut p  advice a  method callee  tion and display updating. In this scenario, the Java implementation uses inheritance to obscure the identity of the concrete target method from the capers; the callers name an abstract method. At run-time, an object with a concrete implementation of this method is provided to the callers. In our example, basic move notification establishes the abstract interface (MoveListener.moved) and display updating provides the concrete target (DisplayUpdater.moved). The general AspectJ dependency structure is identical to Figure 3.1; the Java dependency structure is shown in Figure 3.2. This variant is more similar to the AspectJ dependency structure in that the callers are completely isolated from the ultimate callee method, but a dependency from each of the callers is still present.  method caller  }  method caller?  method absif  I  method impl (implements absjf)  method callee  Figure 3.2: Using an abstract method to obscure the true identity of the invocation target  3.7.2  Whole-class Dependencies  Point bounding and basic move notification both demonstrate a common pattern of dependency difference. In the Java implementation, a whole-implementation dependency exists (e.g., Point depends on the rule "move methods must validate coordinates") causing a col-  25  lection of method invocations (e.g., the set of calls from move methods to checkX). In the AspectJ implementation, a whole-interface dependency exists (e.g., the pointMove pointcut depends on all of the methods that move Point) causing a collection of methods to be named (e.g., the pointMove pointcut names all methods in the current implementation that move Point). A generalized pattern for this scenario is shown in Figures 3.7.2 and 3.7.2.  (whole impl) Class c Rule: all methods such thatX must do Y  method ml (satisfies X) method y (realizes action Y)  method m2 (satisfies X)  Figure 3.3: Whole-class dependency addressed with explicit method invocations  3.7.3  Whole-Subsystem Dependencies  In the AspectJ implementation of basic move notification, a whole subsystem dependency arises because a pointcut depends on all concrete implementations of the FigureElement interface. In the Java implementation, the FigureElement interface includes a requirement that all concrete implementations support basic move notification; this avoids a corresponding whole-subsystem dependency. Therefore, this is a case in which a dependency in one implementation (AspectJ) is absent in the other implementation (Java). The factory pattern requires that all FigureElements be constructed within the  26  (whole i/f): all methods in class c such thatX  Class c  pointcut p method ml (satisfies X)  method m2 (satisfies X)  method^ (realizes action Y) "  advice a  Figure 3.4: Whole-class dependency addressed with pointcut and advice context of a factory method, and enforcement of this rule seems likely to introduce a wholesubsystem dependency. However, neither the Java nor AspectJ implementation introduces a whole-subsystem dependency: in both cases, the implementation is able to leverage the existing inheritance structure instead.  3.7.4  Call-Graph Dependencies  This system has two examples of call-graph dependencies. In both cases, a single call-graph dependency in the AspectJ implementation corresponds to several name-based dependencies in the Java implementation. In both nested move suppression and factory pattern enforcement, the AspectJ implementation has a single call-graph dependency arising from the use of cflow or within, while the Java implementation has several whole-implementation dependencies arising from the scattering of the concern. No call-graph dependencies were present in the Java implementation.  27  Chapter 4  Implications of Dependencies Dependencies have been a focal point of software engineers because they have a demonstrated impact on a variety of system properties[7][10]. In this section, we consider some of these properties: comprehensibility, re-usability and evolvability. Empirical study of these properties is beyond the scope of the present thesis, so only analytical consideration is provided.  4.1  Comprehensibility  Comprehensibility is the ease with which the system's code can be understood by a developer. In general, dependencies reduce the comprehensibility of the dependent modules[8], but not all to the same degree. First, we consider the effect of name-based dependencies on comprehensibility. As an example, consider the point-bounding rule. In the Java implementation, the core Point class validated coordinates, creating a name-based dependency on the signatures of the methods that check the coordinate values. This dependency makes the core Point class less comprehensible, because a developer interested in the operation of the core Point class needs to understand the nature of the invoked coordinate-checking methods. In contrast, the AspectJ version of Point has no dependency on point bounds check-  28  ing; instead, the dependency is reversed so that the PointBoundsChecking aspect depends on the Point class. In this case, we see a trade-off: the AspectJ Point class is more comprehensible at the expense of point bounds checking functionality. The impact of whole-class, whole-subsystem and call-graph dependencies on comprehensibility cannot be established without some empirical evaluation. This exploration must be left for future research. However, we note briefly that explicit dependencies should improve comprehensibility simply because implicit dependencies are easier to overlook.  4.2 Evolvability Evolution is the change to a system over time. Evolution may be done to enhance the performance of the existing system, to extend its functionality, or to adapt to changes in the environment (e.g., user interface trends or new platforms)[9]. In this section, we consider a subset of the evolution spectrum: we consider the range of changes that can be made independently, or changes to one module that do not require simultaneous changes to related modules. Coupled changes are problematic because they require more effort (making several modifications) and are potentially more error prone (if the affected dependents are not easy to find). This is the same range of evolutionary changes considered by Parnas [7] [8]; this class of evolutionary change has the advantage of being analytically examinable. First, we consider the impact of named-based dependencies on evolution. Namebased dependencies always constrain independent evolution in the same way: the name or signature of the referent cannot be changed without simultaneously changing the dependents. For instance, a method named close could not be changed to release without simultaneously changing the existing call sites that name close. Next we consider the impact of whole-class dependencies on evolution. For instance, recall the PointBoundsChecking aspect in the AspectJ implementation. It depends on the Point class' whole interface, namely, on all methods that move Points. If a new move method is added to Point's interface, for example, then the PointBoundsChecking aspect needs to be updated simultaneously. 29  The corresponding dependency in the Java implementation was the whole-implementation dependency: "all code that moves a Point must first validate the new coordinates." If a new move method is added to Point, it must be immediately updated to validate new coordinates. This is a kind of simultaneous change, and corresponds to the simultaneous change required by the AspectJ dependency described earlier. Next we consider the impact of whole-subsystem dependencies on evolution. These dependencies tend to require simultaneous changes to the dependent when new modules with relevant characteristics are added to the subsystem. For instance, the DisplayUpdating.move pointcut in the AspectJ implementation is dependent on the collection of classes that implement the FigureElement interface. If a new FigureElement, Rectangle, is added, then the DisplayUpdating.move pointcut must be simultaneously modified accordingly. Next we consider the impact of call-graph dependencies on evolution. These dependencies require simultaneous changes whenever the call graph is changed such that the existing assumptions are rendered invalid. For example, the nested move suppression rule assumes that any moves made in nested calls correspond to moves of nested objects. However, suppose that new functionality is added to the system so objects may be aligned relative to one another. In this scenario, the movement of one object may cause the movement of a non-nested peer object, and so the call graph dependency would be broken. This change would therefore require a simultaneous change to the DisplayUpdating.move pointcut.  4.3  Re-Usability  Re-use is an important goal of software engineering. It has the potential to decrease development time and expense while simultaneously increasing software quality[6]. The challenge for re-use, from a dependency perspective, is that all dependencies that a given module has must be satisfied in the target system. For name-based dependencies, this means that the modules, methods, fields, etc. referenced by a module must be available exactly as named in the target system. Typically, name-based dependencies force any dependent modules to be included in the target system.  30  For instance, one could not use PointBoundsChecking aspect without the Point class, since it names Point explicitly. Re-usable components are typically organized into libraries, and entire library is included in the target system. In Java and AspectJ, libraries are packages at the source level and JAR files at the binary level. Because of this pattern of re-use, any whole-class, whole-subsystem or call-graph dependencies that refer to elements included in the same library will always be satisfied without any cooperation by the target system. If there are any dependencies that refer outside of the library, then these must be satisfied by the target system. For instance, if the library has a call-graph dependency that involves the call into the library, then the target system must be careful to avoid breaking this dependency. For example, the Swing user interface library included with JDK 1.2+ requires that any modifications to UI elements be made in the context of the Swing worker thread. Similarly, the Java security API is founded on a whole-system dependency that all classes which can do I/O in the entire system check with the security manager before allowing the I/O operation to proceed. Adding a new I/O class would violate Java's security model unless it obeys this dependency.  31  Chapter 5  Related Work Key early work in this area was done by Stevens, Myers and Constantine[10]. The authors use the term "connection" in the sense that we use "dependency". The authors state that, "a connection is a reference to some label or address defined (or also defined) elsewhere." The connections that they considered are therefore what this thesis considers name-based dependencies. This work also introduced the notion "coupling," which is a connection plus an associated measure of "weight". The authors proposed a weighting scheme, based on the nature of the interaction that gives rise to the coupling. For instance, if the caller can affect the callee's controlflowthen the coupling is higher than if the caller is simply passing a data value to the callee. Subsequent work in software metrics has explored coupling in the context of object-oriented programming[2][l]. For instance, a method invocation between a subclass and superclass may have a different weight than a method invocation between two classes unrelated via inheritance. In this more recent work, the underlying dependencies remain name-based dependencies, from our perspective, but these dependencies are further categorized for the purpose of assigning coupling weightings. This thesis has focused on the underlying dependencies, but it does not exclude the possibility of augmenting these dependencies with weights to produce measures of coupling. Furthermore, our grouping of all name-based dependencies into a single category does not  32  imply that all name-based dependencies are equivalent. Since it is not a goal of this thesis to assign weights to dependencies, the further subdivision of name-based dependencies is not necessary. Determining a weighting scheme for whole-class, whole-subsystem and call-graph dependencies, both within each type and relative to other types, remains a task for future work. Parnas provided an early notion of the connections between modules[7] that is broader than the notion of coupling proposed Stevens et. al. Parnas proposed the following notion of connection: "the connections between modules are the assumptions which the modules make about each other." Parnas' notion of connection would capture all of the dependencies described in this thesis except for whole-subsystem dependencies (which are not relations between specific modules), although whole-subsystem dependencies would be captured by the spirit of his definition. Similarly, the definition of a dependency in the Object Management Group's Unified Modeling Language[ll] adopts a broad definition of dependency: "[a dependency] indicates a situation in which a change to the target element may require a change to the source element in the dependency." This definition captures all of the dependencies in this thesis except for whole-subsystem dependencies, which are captured by the spirit of the concept. Therefore, both Parnas' "connection" and UML's "dependency" are essentially supersets of the dependencies discussed in this thesis. This thesis does not attempt to identify all of the kinds of dependencies that can exist in a software system; rather, the focus is on dependencies that seem to be important in the emergingfieldof Aspect-Oriented Software Development. So it should not be considered a failure that this thesis does not expand the scope of dependencies; rather, this thesis should be viewed as providing detail within the context of these concepts. Additional kinds of dependencies certainly do exist and may be identified more clearly in future work.  33  Chapter 6  Conclusion 6.1  Summary  This thesis proposes three additional dependencies to supplement the traditional notion of dependency in object-oriented systems. With whole-class dependencies, the dependent is sensitive to changes to certain changes to the class, but not restricted to particular parts of the existing class. For example, a pointcut may need to know all of the methods that move a Point; this pointcut has a whole-class dependency on Point because adding a new move method to Point would impact the pointcut. With whole-subsystem dependencies, the dependent is sensitive to certain changes to a package or packages, but not restricted to particular classes in the existing class. For example, a pointcut may need to know all of the kinds of FigureElements in the system; this pointcut has a whole-subsystem dependency because adding a new kind of FigureElement would impact the pointcut. With call-graph dependencies, the dependent is sensitive to changes to the system's call graph. For example, an aspect may assume that all nested invocations of FigureElement move methods correspond to movements of logically nested FigureElement objects; this pointcut has a call-graph dependency because moving a non-nested FigureElement in a move method would impact the pointcut.  34  6.2  Future Work  These dependencies axe intended to be useful to developers in evaluating system designs, in the same way that traditional name-based dependencies are used by developers currently. Some additional work is needed to explore the application of these dependencies for system design evaluation and comparison. We also hope to be able to use these dependencies as a basis for comparing implementations in different programming languages.  35  Bibliography [1] Briand, L., Daly, J., and Wiist, J. "A Unified Framework for Coupling Measurement in Object-Oriented Systems," IEEE Trans, on Software Eng., vol. 25, no. 1, pp. 91-121, 1999. [2] Chidamber, S. and Kemerer, C. "A Metrics Suite for Object Oriented Design," IEEE Trans, on Software Eng., vol. 20, no. 6, pp. 476-493, 1994. [3] Kiczales, G., Lamping, J., Mendkehar, A., Maeda, C , Lopes, C , Loingtier, J.-M., and Irwin, J. "Aspect-Oriented Programming," Proc. of the 11 European Conf. on th  Object-Oriented Programming, pp. 220-242, 1997. [4] Kiczales, G., Hilsdale, E., Hugunin, J., Kersten, M., Palm, J., and Griswold, W. "An Overview of AspectJ," Proc. of the 15 European Conf. on Object-Oriented Programth  ming, 2001. [5] Kiczales, G., Hilsdale, E., Hugunin, J., Kersten, M., Palm, J., and Griswold, W. "Getting Started with AspectJ," Communications of the ACM, vol. 44, iss. 10, pp. 59-65, 2001. [6] McDroy, M. "Mass produced software components," Proc. Nato Software Eng. Conf, Garmisch, Germany, pp. 138-155, 1968. [7] Parnas, D. "Information Distribution Aspects of Design Methodology," Proc. of IFIP Intl. Congress 1971, TA-3, pp. 26-30, 1972.  36  [8] Parnas, D. "On the Criteria To Be Used in Decomposing Systems into Modules," Comm. of the ACM, 15(12), pp. 1053-1058,1972. [9] Parnas, D. "Software Aging," Proc. of the 16 Intl. Conf. on Software Eng., pp. 279th  287, 1994. [10] Stevens, W., Myers, G., and Constantine, L. "Structured Design," IBM Systems Journal, vol. 13, no. 2, 1974. [11] UML Partners. The Unified Modelling Language, version 1.4- OMG document formal/01-09-67, Sept. 2001.  37  

Cite

Citation Scheme:

        

Citations by CSL (citeproc-js)

Usage Statistics

Share

Embed

Customize your widget with the following options, then copy and paste the code below into the HTML of your page to embed this item in your website.
                        
                            <div id="ubcOpenCollectionsWidgetDisplay">
                            <script id="ubcOpenCollectionsWidget"
                            src="{[{embed.src}]}"
                            data-item="{[{embed.item}]}"
                            data-collection="{[{embed.collection}]}"
                            data-metadata="{[{embed.showMetadata}]}"
                            data-width="{[{embed.width}]}"
                            async >
                            </script>
                            </div>
                        
                    
IIIF logo Our image viewer uses the IIIF 2.0 standard. To load this item in other compatible viewers, use this url:
http://iiif.library.ubc.ca/presentation/dsp.831.1-0051218/manifest

Comment

Related Items