Supporting Object-Oriented Software Development ... - Semantic Scholar

2 downloads 506 Views 246KB Size Report
Supporting Object-Oriented Software Development with. Intentional Software Views. Kim Mens. Département INGI. Univ. catholique de Louvain.
Supporting Object-Oriented Software Development with Intentional Software Views Kim Mens

Tom Mens

Michel Wermelinger



Departement ´ INGI Univ. catholique de Louvain Louvain-la-Neuve, Belgium

Programming Technology Lab Vrije Universiteit Brussel Brussels, Belgium

Departamento de Informatica ´ Universidade Nova de Lisboa 2829-516 Caparica, Portugal

[email protected]

[email protected]

[email protected]

ABSTRACT Developing, maintaining and understanding large software systems is hard. One underlying cause is that existing modularization mechanisms are inadequate to handle crosscutting concerns. Intentional software views are an intuitive and lightweight means of modelling such concerns. They increase our ability to understand, modularize and browse the implementation of a software system by grouping together source-code entities that address a same concern. Alternative descriptions of the same intentional view can be provided and checked for consistency. Relations among intentional views can be declared, verified and enforced. Our model of intentional views is implemented using a logic meta-programming approach. This allows us to specify a kind of knowledge developers have about (object-oriented) software systems that is usually not captured by traditional program documentation mechanisms. It also facilitates software evolution by providing the ability to verify automatically the consistency of views and detect invalidation of important intentional relationships among views when the software is modified.

1.



INTRODUCTION

Goal. We propose the model of intentional software views as a means of enhancing the limited modularization mechanisms of current-day programming languages. In particular we focus on how the knowledge codified by intentional views makes it easier to understand and browse source code and how it can support software evolution. In this paper1 , we only explain the general approach and some concrete examples that have already been implemented. A ∗Tom Mens is a Postdoctoral Fellow of the Fund for Scientific Research - Flanders (Belgium) †Supported by ATX Software SA, and by project POSI/32717/00 (Formal Approach to Software Architecture) funded by Fundac¸a˜ o para a Ciˆencia e Tecnologia. 1 An extended version of this paper has been submitted to the Software Engineering and Knowledge Engineering 2002 conference.

more thorough explanation of the formal model and an experience report based on a real case study are the subject of two forthcoming papers.

Problem. Once software systems reach a certain size, the modularization constructs provided by current programming languages fall short. As recognized by the AOP community [3], system-wide concerns often do not fit nicely into the chosen modularizations. They are said to crosscut the dominant modularizations [7]. Choosing another modularization merely shifts the problem, leading to another set of concerns that crosscuts the ‘dominant’ modularizations into which everything else needs to be fit. Perry et al. call this problem the tyranny of the dominant decomposition [7].

The AOP solution. AOP addresses this problem by implementing crosscutting concerns as separate aspects and merging them together afterwards with a special-purpose compiler called aspect weaverT M . Unfortunately, AOP implies a completely new way of programming and is not mature enough yet to be used in every-day programming practice. Our solution. To counter the tyranny of the dominant decomposition, we propose a more pragmatic complementary approach that provides a powerful and expressive software modularization mechanism on top of an existing programming language. Instead of describing different concerns in separate aspects that are weaved afterwards, we allow the implementation to be modularized into a number of user-defined intentional views that may crosscut the actual implementation structure and that may be overlapping. Each intentional view corresponds to an important concern that may be spread throughout the source code. It groups the set of source-code entities that address this concern. An intentional view is a view in the sense that it provides only partial information and does not have to be explicit in the actual source code.2 It is intentional as it describes the common characteristic of the entities belonging to a view in a declarative and intuitive way that clearly describes the ‘intent’ of the view. More specifically, we describe this intent in a Prolog-like logic programming language. In addition to defining intentional views, our proposed model allows us to express, verify and enforce important relationships among intentional views (and thus, indirectly, on the source code). As such, many hidden assumptions in the source code are codified as explicit knowledge about the software system. Using intentional views and their relationships to make software concerns explicit increases software maintainability and evolvabil2

In this sense, it is similar to a database view.

ity. First of all, they enhance software understanding because they provide important knowledge about where and how certain concerns are implemented and how they relate with other concerns. As such, intentional views and their relationships serve as a kind of active and enforceable knowledge that is not explicitly available in the source code. Secondly, it becomes easier to manage the software because important concerns have been made explicit in the intentional views, even if they are spread throughout the source code. Finally, when the software evolves, we can analyze the constraints imposed by the intentional views and their relationships to verify that no hidden assumptions have been invalidated. This verification can be done automatically because the description of views and their relations is an executable specification in a logic metaprogramming language.

2.

MOTIVATING EXAMPLE

Before presenting our model of intentional views we give concrete examples of some views, how they are related, and how this knowledge may help us in understanding and maintaining the software. The example is taken from an ongoing case study on the evolution of SOUL (Smalltalk Open Unification Language), a mediumsized object-oriented application in Smalltalk / VisualWorks.

Context. In essence, SOUL is an interpreted logic meta-programming language. It comes with an associated library of logic predicates for reasoning at metalevel about object-oriented source code at a high level of abstraction. The SOUL software development process made extensive use of unit testing, one of the essential ingredients of eXtreme Programming [1]. This gave rise to two implicit constraints on the SOUL implementation: (1) for each method in the SOUL implementation there is a corresponding test method; (2) for every logic predicate in the library there is a corresponding test method that verifies whether the predicate fails and succeeds when expected. These constraints imply that whenever a method or predicate is modified a software engineer needs to rerun the corresponding test method, to make sure that the method or predicate still behaves as expected. The second constraint is particularly important as incorrectly working predicates are important indicators of deeper problems with the SOUL language implementation. It happened on several occasions that a predicate that had worked correctly in many earlier versions, suddenly gave rise to errors, due to incorrect changes to the SOUL interpreter. With the unit testing approach such errors could be detected at a very early stage. Test-suite completeness. In practice not every predicate has a corresponding test method. As most predicates were ported in bulk from an earlier version of SOUL in which no unit tests were available, the unit tests had to be added a posteriori on a predicate per predicate basis, which was time-consuming and labour-intensive. Nevertheless, the developers of SOUL were well aware that completeness of the test-suite was an important issue and they put quite some energy in attaining this goal. Our model of intentional views helped the developers in achieving this completeness. Two intentional views played a crucial role. One view groups all logic predicates, and another one groups all unit test methods. The completeness constraint (2) was codified explicitly as a relation between these two intentional views: for every predicate in the first view there must exist a corresponding test method in the second one. All invalidations of this relation corresponded to a breach of constraint (2). How exactly we defined

these views and the relation will be illustrated in the next section. Finally, we also used these views and relation to automatically generate ‘stub’ test methods for all methods that did not have a corresponding test method. These stub test methods were very primitive and always failed when executed, so that it was easy for a software engineer to see for which predicates the test methods needed to be filled in.

Alternative definitions for intentional views. The above example illustrates that relations among views can be used to express and verify important implicit constraints and invariants on source code. But even the description of a single intentional view can already be very helpful, because it allows us to express various alternatives. Every alternative describes all software entities that are supposed to belong to the view. All alternatives are supposed to be consistent in the sense that they all describe exactly the same set of software entities. This consistency constraint among the alternative definitions implicitly codifies an essential convention or assumption in the source code. For example, consider the intentional view that contains all logic predicates. Since SOUL is implemented entirely in Smalltalk, the logic predicates are actually wrapped in Smalltalk methods, so this view actually contains methods instead of predicates. There are two alternative ways of defining this view: (1) by using a naming convention: all logic predicates are wrapped in methods of a class that belongs to a Smalltalk class category of which the name starts with the string ‘SoulLogic’; (2) by relying on the fact that all classes containing logic predicates must be descendants of the abstract class LogicRoot. It is this abstract class that defines a means of wrapping logic predicates in ordinary Smalltalk methods. The fact that both alternative descriptions have to be consistent implies that the naming convention (1) must be respected by all subclasses of LogicRoot. Although this particular constraint is not ‘crucial’ in the sense that breaking it would not give rise to program errors, respecting it does make the software ‘cleaner’ and thus more understandable and easier to browse. In fact, the constraint gives an explicit semantics to the naming convention (1) so that we can actually be sure that everything in a Smalltalk category with the correct name represents a real logic predicate. A behavioural description of test methods. Intentional views may also codify more behavioural knowledge about the software. For example, we can define an intentional view that collects all ‘correct’ unit test methods for logic predicates. These correct test methods are characterized by the fact that their body should have a certain format (amongst others, they should invoke a method whose name starts with ‘processQuery:’).

3. THE MODEL Our formal model of intentional views basically consists of two parts. The first part is a language model that describes the kinds of software entities in the particular object-oriented language we would like to view and the primitive implementation relationships in terms of which we can define high-level relationships among views. The second — and most important — part is the intentional view model itself. It defines the notions of intentional views and relations among views, as well as alternative intentions and which constraints can be imposed on them.

3.1 The language model The purpose of the language model is to abstract away from lan-

name

Name

contains

Entity

startsWith subString endsWith inherits

meta base

Category

Namespace

meta

hasExpression

arguments

super

Class

sub

Method

Variable

classInCategory

-isAbstract : Boolean -isConstructor : Boolean -hasClassScope : Boolean hasMethod

Expression receiver

-isAbstract : Boolean

methodInProtocol

Invocation

hasParameter Self Variable Parameter hasAttribute

Attribute

Return accesses Access -isUpdate : Boolean -toSuper : Boolean

-hasClassScope : Boolean

Figure 1: UML class diagram representing a particular language model for Smalltalk/VisualWorks. guage details that are not relevant to define views or relationships among views, like the actual parse-tree representation of a method, or the way how abstract methods are represented in a particular language. The language model reflects only those language constructs that programmers want to reason about using views. Those concepts might correspond to actual language constructs (e.g., method calls), idiomatic conventions (e.g., naming conventions) or concepts provided by the development environment (e.g., class categories and namespaces in Smalltalk). Note that one may have different language models for the same language, depending on the particular concepts one is interested in. In the current paper, we use intentional views to reason about object-oriented applications written in VisualWorks Smalltalk and the particular language model we use is shown in the restricted UML class diagram of Figure 1. To reason about language entities and their relations, for every class, association and attribute in the UML language model, we define a primitive logic metapredicate in a Prolog-like language with a meta-level interface to reason about Smalltalk programs. (For more details on this logic meta-programming approach, see [6].) For example, for each class α in the UML language model, there is a corresponding predicate α(?X) that describes whether a given source-code entity ?X is an instance of α. Examples of such predicates are: class(?X), method(?X) and attribute(?X) to check that the logic variable ?X is a class, method or instance variable, respectively. When ?X is uninstantiated, the predicate returns all such instances through backtracking. Similarly, the predicates that express which relations can be defined among source-code entities, correspond to attributes and associations in the UML language model. For each attribute β, we have a predicate β(?entity, ?value). For example, the predicate isAbstract(?entity, true) can be used to find all Smalltalk classes and Smalltalk methods that are abstract. The predicate hasClassScope(?entity, ?value) can be used to detect whether an attribute of a Smalltalk class is either a class variable or an instance variable. For each association γ in the language model, there is a predicate γ(?from, ?to). For example, inherits(?sub, ?super) checks if the first argument is a subclass of the second one, and name(?entity, ?name) finds or checks the name of a certain entity. These predicates can also be used with one or two arguments uninstantiated.

3.2 The model of intentional views While the language model defines what kinds of source-code entities and relations are of interest to the software engineer, the intentional view model defines how views and their relationships can be defined for the given language.

3.2.1 Views An intentional view describes a set of source-code entities. It contains one or more alternative intentional descriptions of this set (one of which is called the ‘default’ intentional description). Each such description provides an alternative insight on the intention behind the view. The description is intentional in the sense that the source-code entities in the view are not explicitly enumerated. Instead, logic predicates are used to describe all entities belonging to the view. As explained in Subsection 3.1, the logic predicates are defined in terms of primitive logic predicates that correspond to the language constructs specified by the language model.3 As an example, reconsider the intentional view from Section 2 that groups all logic predicates. As explained in that section, this can be defined in two alternative ways: view(soulPredicates,). default(soulPredicates,alternative2). intention(soulPredicates,alternative1,?C) if class(?C), classInCategory(?C,?K), name(?K,?N), startsWith(?N,’SoulLogic’). intention(soulPredicates,alternative2,?C) if class(?C), inherits(?C, [LogicRoot]).

Known exceptions to the intentional descriptions are specified separately. For example, for alternative1, we need to include three extra classes that are not captured by the category naming convention. There are no exceptions that have to be excluded. include(soulPredicates,alternative1,[TestClauses1]). include(soulPredicates,alternative1,[TestClauses2]). include(soulPredicates,alternative1, [TestClassifications]). 3 In the Prolog-variant we used, the keyword if separates the body from the head of a rule; logic variables start with question marks; a comma denotes logical conjunction; lists are delimited with and terms between square brackets are reified Smalltalk values.

Internal consistency of a view requires that the entities in an include predicate are not part of the intentional description (but are included afterwards), and that elements in an exclude predicate are part of the intentional description (but are excluded afterwards). Additionally, all alternative intentions (together with their inclusions and exclusions) must be mutually consistent, i.e., they must have the same extension. The extension of a view can be computed automatically from an intentional description by the following pre-defined predicate: extension(?View, ?alt, ?Entity) or( intention(?View, ?alt, include( ?View, ?alt, not( exclude( ?View, ?alt,

if ?Entity), ?Entity) ), ?Entity) ).

In addition to user-defined views, for every kind of entity in the language model we have a predefined intentional view (where Entity denotes the kind of entity, for example, class): view(Entity, ). default(Entity, primitive). intention(Entity, primitive, ?e) if Entity(?e).

If we want to know whether a certain entity satisfies a certain intention, but no particular alternative is specified, the default alternative is assumed: intention(?View,?Entity) if default(?View,?Alternative), intention(?View,?Alternative,?Entity).

The same holds for extensions. One advantage of using intentional descriptions over explicit enumerations of source-code entities is that they are often much more concise. Another is that they are more intuitive and precise, as they define exactly which property all entities in a view have in common. Thirdly, intentional descriptions are more robust towards changes than explicit enumerations. The only disadvantage of intentional descriptions has to do with efficiency of computation. An explicit enumeration stores all values explicitly and thus can be retrieved immediately. An intentional description can be stored much more concisely, but when its values are needed, they need to be computed from the definition, which may take some time (unless a caching mechanism is used).

3.2.2 Relations Intentional views may be explicitly related. Since views are sets of source-code entities, our model provides a series of predefined relations on sets like subsets, disjointness and partitioning. For example, suppose that we have two intentional views logicPrimitives and logicLibraries. We can express the fact that each logic predicate is either a logic primitive or belongs to a library, as follows: relation( partition,soulPredicates, ).

As an example of a user-defined relation, consider the view logicTestClasses that contains all classes that implement unit tests for logic predicates. We can check whether this view is consistent with the view validTestMethods that describes all ‘correct’ unit test methods for logic predicates, by checking that each method in validTestMethods belongs to a class in logicTestClasses, and that each of these classes contain only valid test methods. This is codified in the following logic rule: relation( hasMethods, logicTestClasses, validTestMethods) if forall( intention(validTestMethods,?method), exists( intention(logicTestClasses,?class), hasMethod(?class,?method))),

forall( intention(logicTestClasses,?class), forall( hasMethod(?class,?method), intention(validTestMethods,?method))).

In other words, hasMethods is a generalization over sets (using universal and existential quantification) of the more primitive implementation relationship hasMethod between classes and methods. An example of a unary relation on views is the one that checks whether all elements in a view are of the same type, e.g., relation(homogeneous(class),logicTests). relation(homogeneous(method),validTestMethods).

which is defined by the following general rule: relation(homogeneous(?EntityKind),?View) if forall( intention(?View, ?Entity), ?EntityKind(?Entity)).

As a final example of a user-defined binary relation among views, the predicate below codifies that there is one test entity in the second view for each entity in the first one. This generic relation, which can be used for method views as well as class views, is based on the naming convention that the name of the test entity is the name of the tested entity prepended with ’test’. relation(testedBy,?testedview,?testview) if forall( intention(?testedview,?e), exists(intention(?testview,?t), and( name(?e,?n), append(‘test’,?n,?tn), name(?t, ?tn) ) ).

A more detailed definition might actually check that the test entity actually refers to the tested entity. A predicate like this one was used to check the ‘test-suite completeness’ constraint of Section 2.

4. TOOL SUPPORT Contribution. As the examples in the previous sections indicated, intentional views provide support for a whole range of software engineering tasks: • Provide generic and flexible modularisation and code structuring mechanisms to make the software more understandable and more maintainable; • Serve as an active and enforcable documentation of the code structure; • Ensure the consistent use of naming and coding conventions throughout the software; • Provide support for software evolution by making implicit constraints in the source code explicit and guaranteeing that these constraints remain satisfied when the software evolves; • Offer a useful abstraction mechanism for supporting crosscutting software concerns; • Provide support for code generation and transformation based on intentional views.4 4 Due to space limitations, no examples hereof were worked out in this paper.

Figure 2: User Interface for the Intentional View Model

Implementation. An important advantage of our intentional view model is that it is non-intrusive. It can be added on top of an existing programming language or environment, allowing the definition of intentional views on top of the modularisation mechanisms supported by that language. As a proof of concept, we implemented a prototype tool for supporting intentional views on top of the VisualWorks Smalltalk development environment. The intentional view model is implemented in the logic language SOUL5 , which was itself implemented in VisualWorks Smalltalk. The library of logic predicates that is used to reason about Smalltalk code is implemented entirely in SOUL. The intentional view model is also implemented in SOUL as a collection of logic predicates, and uses a subset of the logic library.

User interface. Because we do not want to burden the software engineer with the implementation details or syntactic peculiarities that are due to the fact that the intentional view model is implemented in the logic language SOUL, we have defined an intuitive user interface that is shown in Figure 2. As can be seen from this picture, we envision two different kinds of uses of the model. The Extension Viewer, which is shown on the left of Figure 2, allows the user to browse, for each intentional view that is defined, all software entities contained in this view. Each software entity can be selected to inspect and modify its contents. The Intention Editor, which is shown on the right of Figure 2, can be used to inspect, modify or remove existing alternatives for an intentional view, add new alternative intentions, or add new userdefined views to the model. Both the Extension Viewer and Intention Editor provide buttons for checking the well-formedness of the intentional view and the consistency of each of its alternative intentions. A so-called Relation Editor, which can be used to view, add, remove and modify relations between intentional views, is under construction.

5.

FUTURE WORK

We are currently conducting concrete experiments with the proposed model to understand and manage the evolution of SOUL. We can already report some interesting results: As VisualWorks 3 had no support yet for namespaces (a language construct similar to Java packages), many intentional views 5

The same language we used as our case study.

on an earlier version of SOUL were defined in terms of naming conventions (brittle). When porting SOUL to VisualWorks 5i4, some of these intentional views were codified more explicitly by using namespaces (disciplined). To ensure consistency between versions during this port, we relied on the fact that our model supports having mutually consistent alternative intentions (in our case: one using naming conventions and one using namespaces). We already hinted on how intentional views helped the SOUL developers with their test unit approach. In addition to the examples mentioned earlier, we experienced how intentional views offer a useful abstraction for generating crosscutting code. For example, after defining an intentional view that contained all logic predicates for which no corresponding test method existed, we used it as a starting point to automatically generate a ‘stub’ test method (which always fails) for all those predicates. In the future, we seek to apply our model of intentional views to architectural conformance [5], architectural views [4], and refactoring [2]. As for the first two, we intend to use intentional views as a basis to specify the software architecture of a system under multiple perspectives and to check whether the implementation conforms to the architecture. As for refactoring, we hope that the crosscutting and abstract nature of intentional views might help in designing non-trivial and powerful refactoring mechanisms.

6. CONCLUSION Intentional views offer a simple, intuitive and lightweight model that facilitates many software engineering activities, amongst which software understanding, maintenance and evolution. They make the code more understandable and easier to navigate through by grouping together source-code entities that address a similar concern and by allowing the definition, verification, and enforcement of relations among these groups of source-code entities. They provide an extra code structuring mechanism and as such make the software more maintainable. They allow us to ensure that certain coding conventions are consistently used throughout the source code. They provide support for software evolution by explicitly codifying hidden assumptions and constraints in the source code and by indicating which constraints have been invalidated when the software evolves. Finally, like aspects, they offer a useful abstraction for generating code that may crosscut the implementation structure.

7.

REFERENCES

[1] K. Beck. Extreme Programming Explained: Embrace Change. Addison-Wesley, 2000. [2] M. Fowler. Refactoring: Improving the Design of Existing Programs. Addison-Wesley, 1999. [3] G. Kiczales, J. Lamping, A. Mendhekar, C. Maeda, C. Lopes, J.-M. Loingtier, and J. Irwin. Aspect-oriented programming. In Proceedings of European Conference on Object-Oriented Programming, volume 1241 of Lecture Notes in Computer Science, pages 220–242. Springer-Verlag, 1997. [4] P. B. Kruchten. The 4+1 view model of architecture. IEEE Software, November 1995. [5] K. Mens. Automating Architectural Conformance Checking by means of Logic Meta Programming. PhD thesis, Department of Computer Science, Vrije Universiteit Brussel, Belgium, October 2000. [6] K. Mens, I. Michiels, and R. Wuyts. Supporting software development through declaratively codified programming patterns. In Proc. Software Engineering and Knowledge Engineering, pages 236–243. Knowledge Systems Institute, 2001. [7] P. Tarr, H. Ossher, W. Harrison, and J. S. M. Sutton. N degrees of separation: Multi-dimensional separation of concerns. In Proc. Int’l Conf. Software Engineering, 1999.