Developing an Approach for the Recovery of Distributed Software ...

10 downloads 3747 Views 91KB Size Report
We are developing the X-RAY1 approach for the recovery of distributed software architectures. X-RAY is the result of our attempts to achieve three main goals:.
Proceedings of the Sixth IEEE International Workshop on Program Comprehension (IWPC’98), pp. 28-36, Ischia, Italy, June 24-26, 1998.

Developing an Approach for the Recovery of Distributed Software Architectures Nabor C. Mendonc¸a Jeff Kramer Department of Computing, Imperial College of Science, Technology and Medicine 180 Queen’s Gate, London SW7 2BZ, U.K. fndcm,[email protected] Abstract The extraction of high-level architectural information from existing software systems, or architecture recovery, is a recent research area. This paper presents X-RAY, an approach for recovering distributed software architectures. X-RAY builds on previous work on architecture recovery and more traditional reverse engineering techniques, as well as on notations for architecture description. The key features of the approach are illustrated through the depiction of a step-by-step recovery experiment performed on a small yet non-trivial distributed software system. Initial results from an ongoing experiment involving a larger-scale system are also discussed.

1. Introduction Reasoning about software systems in terms of their architectures is an effective way to tackle the complexity inherent in software development and evolution. However, most of the work in the field of software architecture [20] proposed thus far addresses issues related to the early stages of the software life-cycle, such as specification and analysis. Relatively little work is concerned with existing (legacy) systems for which high-level architecture descriptions are often missing or inaccurate. To help in the process of extracting the architecture of existing software systems, tools and techniques are being developed. This recent research area, which uses and integrates concepts from the reverse engineering and software architecture fields, is commonly referred to as architecture recovery [4, 12]. Among other benefits, architecture recovery technologies can facilitate system understanding and maintenance, help in the identification of commonalities within a family of related systems, and also bring about the possibility of reusing system structure and components in the development of new systems.  Supported by the National Council for Scientific and Technological Development of Brazil (CNPq) under grant No 200603/94-9.

Architectural abstractions—such as servers, clients, filters and pipes—are difficult to identify in a conventional programming language code because they are generally implemented as a set of “low-level” constructs (e.g. variable assignments and function calls) spread over several source files. The discrepancy between code and architectural constructs is even more evident in the case of distributed environments, where typical architecture elements such as components, interaction mechanisms and configuration are generally not explicitly supported by the underlying programming language. This characteristic often makes the overall architecture of a large distributed software system more difficult to extract from source code than each of the individual constituent components [18]. We are developing the X-RAY1 approach for the recovery of distributed software architectures. X-RAY is the result of our attempts to achieve three main goals: (1) identify and use existing reverse engineering techniques that help understand a distributed system source code; (2) propose extensions or alternative solutions to those techniques that produce limited or inaccurate results; and (3) represent recovered abstractions using or adapting existing notations for software architecture description. Thus far we have been able to achieve the first and second goals to some extent and have started to address the third one. Most of the techniques encompassed by X-RAY have been implemented and integrated with other off-theshelf tools. Together these form our architecture recovery prototype. In this paper we present X-RAY through the description of a step-by-step recovery experiment. We carried out the experiment on a relatively small yet reasonably complex existing distributed software system. To help verify the accuracy and limitations of the existing techniques used, and the impact of the extensions and alternative solutions proposed, we first analysed the software manually and thoroughly. This decision allowed us to compare abstrac1 X-RAY stands for eXtraction and Representation techniques for Architecture recoverY. The acronym is also a convenient reference to its medical counterpart as the approach aims to reveal the software “skeleton”.

animation script

animation scripts

I N T E R E X P S E R V

CONTROL

FRED

EGESP

COLLISION

message signal

input output

Figure 1. Architecture of ANIMADO. tions recovered by our prototype with abstractions that had been manually recovered, in addition to those described in the software documentation. The rest of the paper is organised as follows. In the next section we give a detailed description of the recovery experiment. We overview the software system analysed and describe in steps how we extracted and represented several types of “high-level” abstractions from the system source code. In section 3 we describe the current status of our recovery prototype. In section 4 we compare X-RAY with related work on reverse engineering and software architecture. Finally, in section 5 we summarise our main results, briefly report on a further experiment that we are currently performing on a larger-scale system, and conclude the paper by giving directions for future work.

2. The Experiment We analysed the source code for ANIMADO [17], a computer animation prototype. The system is based on the client/server architectural style and written mostly in C, with a single utility class in C++, under a UNIX environment. Despite being relatively small, roughly 3,000 LOC, the software presents a non-trivial complexity: five independent executable components, namely INTEREXPSERV, EGESP, FRED, COLLISION and CONTROL, communicating through message exchange (implemented using sockets) and event notification (implemented via exchange of process signals). ANIMADO is accompanied by clear documentation, at least in terms of its highlevel components and interaction mechanisms. One of its original developers was also readily available. The user initially provides all information necessary to the animation process in the form of an animation script. The script is read and stored by the INTEREX PSERV component, which acts as the system server, accepting requests from the other components (or clients) to read

or update the state of the animation objects. EGESP is a tool for resolution of sparse linear systems. FRED is a tool for the resolution of ordinary differential equations. EGESP and FRED cooperate to solve the set of equations that describe the intermediate states of each object during the animation. COLLISION is responsible for detecting and responding to rigid collisions between objects. Finally, CONTROL coordinates the overall animation process and partially synchronises the execution of the other clients. At each animation step, the system outputs the current state of all objects as a new animation script. The set of animation scripts generated this way can then be displayed by some compatible visualisation tool. The architecture of ANIMADO, exactly as given in its documentation, is shown in Fig.1. We verified the accuracy of this architecture during our manual inspection of the code, and hence we used it to validate the results produced by our techniques. Due to space restrictions, in this paper we report only on the issues related to recovery of components and their socket-based interactions. The experiment resulted in several views describing the system from different perspectives and at different levels of detail. We start by presenting the views closer to the source code itself and gradually abstract to identify higherlevel concepts.

2.1. Modules and Subsystems The system source code is spread over 19 files (8 header files and 11 source files or modules). As we are primarily interested in recovering system components as independent units of execution, we statically extracted information about every function that each module defines and in which other modules those functions are called. This information revealed the system (static) inter-modular activation graph, which is depicted in the module view shown in Fig.2. Since there are many more modules in this view than components in the documented architecture, it is clear that modules are concepts not yet in the appropriate level of abstraction to be considered as representing system components. One way to assign modules to higher-level concepts is by clustering them into subsystems. Among the many clustering techniques for subsystem classification that have been proposed in the literature, the technique proposed in [1] seemed to be the one that more closely satisfied our need for recovering components as high-level units of execution. This technique groups program units into subsystems based on the concept of node dominance on the system activation graph. The technique can produce subsystems of two types: One type contains activation subgraphs which nodes would be exclusively contributing to the implementation of a more general functionality defined by each subgraph’s root; we refer to

stack

stack

parser inter control

collision

inter

table matrix

message

table

parser

control

egesp message

response response

fred

collision

egesp

matrix

fred

Figure 2. The module view.

Figure 3. The subsystem view.

subsystems of this type as entry subsystems. The other type contains subgraphs which nodes would be providing resources to nodes belonging to at least two other subsystems; we refer to subsystems of this second type as library subsystems. In X-RAY we implemented a slightly extended version of this technique. Our extension basically circumvents the problem that the (original) technique requires the system activation graph to be single rooted, while a typical distributed system activation graph has several roots representing the entry node2 for each system component. The system activation graph shown in Fig.2 has five entry nodes corresponding to the five modules (inter, control, collision, fred and egesp) that define a main function for a component. Given this graph as input, our version of the technique identifies six subsystems, as depicted in the subsystem view shown in Fig.3. Five are entry subsystems—and can be seen as representing the code specific to each component—since each contains a component entry module and the activation of the other modules in the subsystem is only possible through the activation of the component entry module. One is a library subsystem—and thus represents code shared by two or more components— since it contains a module (message) which may be activated through the activation of modules in other two or more subsystems. We can check the effectiveness of this particular subsystem classification by analysing information available in the system makefile3 as well as by considering knowledge about the application domain. For example, modules inter, stack and parser are included among the

dependencies for building one of the system’s executables, and it is clear that stack and parser are used by inter for interpretation of animation scripts. Similarly, modules egesp and matrix are among the dependencies for another executable, and the latter obviously provides the former with routines for matrices manipulation necessary to linear systems resolution. However, subsystems only partially match the abstractions described in the system architecture. For instance, we can associate code to components (entry subsystems) but we cannot say anything about servers, clients or message-based interactions. This mismatch between code and architectural abstractions is a clear demonstration of the conceptual gap that exists between source code models, even when alleged to be “high-level”, and software architectures. As a matter of fact, this gap is the major obstacle faced by most traditional reverse engineering tools, which rely only on structural software information, when used for software architecture recovery.

2 In a system activation, we define the entry node for a component as the node associated with the code unit that is first executed after the component is created. In the case of a system written in C or C++, for example, the entry node for a component usually corresponds to the node associated with a main function. 3 makefiles often describe the set of modules needed to build each (executable) system component, but fail to distinguish modules used only by a specific component from modules shared by two or more components.

2.2. Architectural Abstractions Abstractions used in typical architecture descriptions vary substantially from one application or system domain to another. The identification of such abstractions in the system source code therefore requires some sort of previous domain knowledge (e.g. knowledge regarding programming language, operating system, execution environment) stating how they are expected to be implemented. Two recent tools, ManSART [6] and ART [3], have started to address this issue in a fairly similar fashion [13]. Domain knowledge is expressed in the form of an architectural style library which contains information about several architectural components and interaction mechanisms. This information includes a description of program patterns or clich´es through which some architectural elements are expected to be implemented in a traditional programming language. A search engine then attempts to match a given pattern against

socket opening(SockId,CSockId) :% server socket creation fun call(SCall,"socket",[Domain,Type, ]), assign(ASock,SockId,SCall), % machine port set up member ref(MRef,ServAddr,PortMember), obj name(PortMember,"sin port"), assign(APort,MRef,PortVal), % socket binding fun call(BCall,"bind",[SockId1,ServAddr, ]), % listening for client connections fun call(LCall,"listen",[SockId2, ]), % accepting client connection fun call(ACall,"accept",[SockId3, , ]), assign(ACSock,CSockId,ACall), % pattern constraints same obj([SockId,SockId1,SockId2,SockId3]), before([ASock,APort],BCall), before([LCall],ACSock).

Figure 4. A code pattern described in Prolog.

portions of the abstract syntax tree (AST) generated from an existing system source code. In X-RAY we implemented a similar approach on top of a Prolog environment. Structural source models (i.e. definition/use relations) and the system AST are represented as Prolog facts. Clustering and search engines, as well as constructs for the pattern-description notation are implemented as Prolog predicates. More details on the implementation of our recovery prototype are given later in section 3. In the following we describe how we used this environment to try to recover architectural abstractions from the software system under analysis. 2.2.1. Channels of Communication. As in ManSART and ART, in X-RAY we express knowledge about how communication channels (sockets, in this experiment) are expected to be implemented under the C/UNIX domain by using code patterns. Furthermore, we express opening and use of a communication channel as separate patterns. The main argument in favour of this distinction is that code for the creation of a communication channel is in general more complex than code for using that channel. Treating them separately could not only help understanding the types of communication mechanisms used within the system, but also help identifying, in the code for each component, every location where the component is expected to exchange information with other components. Channel Opening Figure 4 shows an example of a code pattern for the opening of a server-side socket. 4 We initially searched for this and a similar pattern describing the opening of a client-side socket. The results were a full 4 Our

pattern-description notation follows the notation used in ART, which in turn is based on the notation originally proposed in [7]. In contrast to ART, our notation does not (yet) support constructs expressed in terms of data/control flow links between statements.

int server socket(int *port)

f ...

sock=socket(AF INET,SOCK STREAM,0); ... server.sin port=0; if (bind(sock,(struct sockaddr *)&server, sizeof(server)) < 0)f /* error ... */

g

g

... port=ntohs(server.sin port); return sock;

... int client socket(char *host, int port) f ... sock=socket(AF INET, SOCK STREAM,0); ... if (connect(sock,(struct sockaddr*)&server, sizeof(server)) < 0)f /* error ... */

g

g

return sock;

Figure 5. Code fragment containing matched constructs (in bold type).

match in the message module and two partial matches in the message and inter modules, respectively. Matches in message correspond to the creation and “binding” of a server socket (partial match) and the opening of a client socket (full match), as shown in Fig.5. The partial match in inter corresponds to a server socket waiting for and accepting connection from other client sockets. We also searched for other possible variations of those two patterns but no reference to a system call responsible for socket creation was found in any other module. If we assume that we have not recovered any other abstraction from the system source code, from those three matches alone we can at best say that two sockets (of types server and client) are created in the message module, and that a server socket is also possibly created in the inter module. Without careful inspection of matched code fragments and the rest of the source code no strong conclusion could be reached as to whether or not, for instance, sockets are opened in other modules that could be associated with the implementation of clients such as control and fred. This situation is caused by the fact that we searched for code patterns without considering knowledge specific to the system implementation domain. For example, from the subsystem view, shown in Fig.3, we know that the message module actually provides resources to other modules. Since two types of sockets are created in this module, it may be that sockets are created in the other subsystems not only by means of the socket and related calls, as it is expected when only knowledge about the C/UNIX domain is considered, but rather by calling functions responsible for socket creation defined in

the message module. In order to check this hypothesis we augmented the client and server patterns by adding references to the server socket and client socket functions provided by the message module. A second search for these “refined” patterns confirmed that all other subsystems do create sockets by calling either server socket or client socket. In this case, we can say that the message module is actually part of the implementation of the system infrastructure rather than the implementation of one of the system components. This distinction, which is only possible when knowledge specific to the implementation domain (expressed here as the result of the clustering operation) is taken into account, is important because infrastructure code generally implements routines for components instantiation and connections and is mostly platform-dependent. On the other hand, components code is where the system behaviour is actually implemented, and is (supposed to be) less dependent on platformspecific communication mechanisms. Explicitly separating components and infrastructure code could be a significant contribution towards facilitating system understanding and code reuse. Channel Use To use of a socket means to send or receive data through it. Under the C/UNIX domain this can be done using read and write calls or some variant of the send and recv calls. We defined new “socket-use” patterns containing references to these calls. A search for these patterns resulted in successful matches in several functions defined in message, inter and control. Again, no reference to socket use was found in the remaining modules. Analogously to the identification of code implementing opening of sockets, we further augmented socket-use patterns with references to the appropriate set of functions provided by the message module. As before, only after searching for these refined patterns were all points of socket use accurately identified in the other modules. 2.2.2. Components. The identification of code related to opening and use of communication channels, together with the results of the clustering operation, finally allows the representation of the software under analysis in terms of architectural (rather than source code) abstractions. The first of these abstractions are components, which we represent following a notation similar to the graphical notation of Darwin [9], an architecture description language (ADL) specifically created to describe distributed systems structure. In X-RAY, each component encapsulates an entry subsystem and has an explicitly defined interface describing services that the component provides or requires. Services correspond to communication channels opened within the subsystem encapsulated by the component. The choice

control main initialize

initial_values

duplicate

table

fred

stack

inter egesp

parser

matrix collision response

Figure 6. The component view.

of whether a service represents a requirement or provision depends entirely on the domain in which the service is used. For example, under the domain of client/server interactions implemented via sockets, a server socket can be seen as a service provision and a client socket, a service requirement. Each service is associated with the respective code fragments implementing opening or use of its underlying communication channel. The ANIMADO component view, with regards to socketbased interactions, is shown in Fig.6. In this view, components are represented by round-cornered rectangles and services by circles located at components borderlines (which in turn represent components interfaces). A filled circle represents a provision and an empty circle, a requirement. Encapsulated source code units are represented inside their respective components by straight rectangles (in the case of modules) or ellipses (in the case of functions). Arrows represent “uses” relations between source code units. A bold line between a service and a code unit indicates that the code unit contains one or more code fragments opening or using that service. This explicit association of services to code units is important in that it bridges the gap between code and architectural abstractions. The association makes it easier to visualise, at different levels of detail, how a component implements a service it provides or uses a service it requires. Although in this particular view a service is only associated with modules—or with functions inside their defining modules, in the case of the component encapsulating control, this association can be further refined to

show how the service is implemented in terms of lowerlevel abstractions. For example, the code for a module or function could be presented upon user request and code fragments matching architectural patterns could be automatically highlighted or annotated. 2.2.3. Interactions. Whenever a component creates an interaction channel, the other potential users of that channel, besides the creator itself, can only be identified—if at all— by a careful analysis of the arguments passed to (returned from) the calls implementing the channel creation. In the case of sockets, the main parameter to be analysed is the machine’s port that is assigned to every socket created.5 From Fig.5 we can see that server sockets created by the function server socket in the message module have their ports automatically defined by the system. This is because the field sin port is set to zero before the socket call. By following control/data flow links we realise that the actual port number, the one set by the call, is assigned to the incoming parameter of server socket. This parameter is used to return the socket port number to the caller of server socket. In the case of a client socket, the port number value is taken directly from one of the incoming parameters of client socket. It is then necessary to analyse the other modules which call server socket and client socket in an attempt to identify which are the values passed (received) as arguments to (from) these calls. In the inter module the port number returned by server socket is used only once, as a parameter for an fprintf call. Inspection of modules which call client socket shows that the value assigned to the port number parameter is always “atoi(argv[counter])”, which means that the actual value is provided as an argument to the shell command that invokes the client program. Since no constant value is statically assigned in the code to any socket port, we can not determine, at least using static analysis only, which components are connected to which other components. 6 Another way to track the values of the parameters used to establish socket connections would be through dynamic analysis or symbolic execution. However, as such techniques are yet not incorporated into X-RAY, we considered a third alternative. Our idea was to try to find some sort of non-code artifact that could provide additional clues about how the system connections are established. 5 The hostname parameter is also important, but we deliberately omit it here for brevity. 6 A similar problem occurs in the case that creation of a communication channel is implemented inside a loop construct. If the values for the loop-iteration variables are not statically defined in the code, static analysis alone is not sufficient to determine the actual number of communication channels that might be created by a component.

Non-Code Artifacts In view of the fact that the system sets its port number parameters using the arguments of invocation commands, one potential non-code artifact to investigate would be shell scripts. No shell script is included as part of the ANIMADO distribution package however. We raised this issue to one of the system’s original developers and we were told that, although they could have really written shell scripts to handle the initial system configuration, their original intention was to have the user him/herself manually configure the system. Their informal “script” is to first invoke INTEREXPSERV and wait for the component to automatically output the port numbers assigned to each server socket created (this explains the port number returned by the server socket call, in the inter module, being used as a parameter to a subsequent fprintf call). These port numbers should then be used as arguments to the invocation of each client. Such manual configuration procedure, albeit informal, is a crucial system artifact that confirms the accuracy of the documented architecture. Information obtained from dynamic analysis or noncode artifacts can also be used to enhance views recovered from source code. For example, we could add the informal information about component connections to the component view of Fig.6 by drawing “connection-lines” (or “bindings”, according to the Darwin terminology) between the services required by clients and the service provided by the server. A textual description of the system’s manual configuration procedure (or a shell script, had one been used for the system configuration) could then be associated with those bindings and shown upon user request.

3. Prototype Status The X-RAY recovery prototype is still under development and integrates a number of new and off-the-shelf tools, as shown in Fig.7. The prototype front-end consists of customised program analysers generated using Gen++[2], a program analyser generator for C++. The Gen++ analysers parse the given source code files and extract structural and syntactic information of interest. This information is stored as a Prolog knowledge base that may be accessed, modified or augmented by our set of Prolog tools. Thus far this set includes a clustering engine, a pattern-matching engine and a number of visualisation predicates. The latter are provided to facilitate visualisation of recovered information, as our prototype has yet no graphical user interface (GUI). Visualisation predicates may be called from the clustering and pattern-matching engines to format and output part of the engines’ results according to the graph language accepted by dot [5], an automatic layout tool. We use dot to convert graph descriptions generated this way into postscript “drawings”. Once in postscript, these drawings can be easily printed or displayed using

represented in terms of code-level interactions among clustered entities. In X-RAY, in contrast, we use the results of clustering techniques not as the final product of the recovery process, but rather as a means of complementing and improving the results of other recovery techniques.

Source Code Gen++ Analysers

Prolog Structural Model

AST

Clustering & Search Engines

Graph Description

dot Textual Results

Graphical Views

Postscript

Display Tool

Figure 7. The X-RAY recovery prototype. appropriate off-the-shelf tools. The module and subsystem views shown in this paper were both produced following this scheme. We plan to add further tools for automatic or semiautomatic layout of ADL-based recovered views, which we still perform manually, and also for quick navigation between views, source code and other related software artifacts. Ideally, these tools should all be later integrated into the prototype’s GUI.

4. Related Work Clustering Tools Those are tools that identify high-level software abstractions by clustering and filtering elements of a base source code model [8]. The base source model in general consists of source code entities (e.g. functions, global names, files) and their relationships (e.g. “defines”, “calls”, “uses”). Clustering techniques are based on a number of different heuristics such as name similarities and degree of graph connectivity. The Rigi system [14], in particular, provides an end-user extensible set of several clustering techniques. Clustering tools are limited in recovering architectural concepts because the abstractions they can extract are confined to be mere conglomerates of the base model entities. Interactions among identified clusters are still

Compliance Checkers Tools such as the Software Reflexion Model tools [15] and Pattern-Lint [19] also apply clustering operations to entities of a base source model, but with a slightly different purpose. The user provides those tools with a description of an idealised high-level model that he/she expects to find in the source code, and a map stating how entities in the base source model must be associated with entities in the given high-level model. The tools then use this map to check whether interactions in the idealised high-level model agree or disagree with interactions found in the base model. Although one can naturally think of idealised high-level models in terms of architectural abstractions, these are in general not supported by current compliance checkers tools. The problem is similar to that of clustering tools. Since most source models are restricted to code-level entities and interactions, to allow comparison the checking mechanism requires (and thus constrains) the idealised model to be described only in terms of code-level interactions as well. Architecture Recovery As described in section 2.2, X-RAY is based on and extends several techniques used in the ManSART and ART architecture recovery tools. However, in developing X-RAY we departed from those approaches in a number of aspects. First, we use clustering techniques to help separate components code from system infrastructure code. Second, we distinguish and define separate patterns for creation and use of communication channels. Third, we widen the applicability of architectural patterns by considering knowledge specific to the implementation domain. Finally, we attempt to make more explicit the association between architectural abstractions and code by representing recovered elements using an ADL-based notation. On the other hand, ManSART and ART provide other interesting features which are not— or only partially—supported in the current version of our recovery prototype. For instance, ART is capable of handling statement-level control/data flow information to improve accuracy of the pattern-matching mechanism [21], and ManSART has facilities for manipulating recovered views [22]. Adding similar features to our prototype is a natural topic for future work. ADLs Our notation for architecture description is to a large extent a subset of Darwin [9]. The main difference is in our representation of architectural components in terms of source code units. Although we do this in a similar manner to the way composite components are defined in terms of subcomponents in Darwin, the semantics of

the representation is obviously different in our case. In Darwin, a binding between a service defined for a composite component and a service defined for one of the constituent subcomponents means that the composite component in fact encapsulates a service that is actually required or provided by the more primitive subcomponent. In our notation, a binding from a component service to an encapsulated code unit is represented mainly for documentation purposes and means that the code unit possibly implements creation or use of that service. Darwin contains other features that may be of interest for the description of recovered architectures and which we have not yet used, such as support for the description of generic, replicated and dynamic structures [10]. These features are likely to be useful in view of the fact that, in many systems, components instantiation and connections are not statically defined in the source code but rather parameterised. A prominent difference between Darwin and other ADLs [11] is that in the former there is no explicit construct to represent interaction mechanisms or connectors as firstclass entities. In Darwin, connections are modelled simply as bindings between compatible services defined by the connecting components. Although the notion of connectors has been presented as playing a primary role in software architectural design, many of the interaction mechanisms used in existing software systems (e.g. streams, pipes, sockets and RPCs) are already provided by the underlying operating system or execution platform. Such mechanisms are in general available as object file libraries that can be used by calling functions defined in the libraries’ interface. We believe that abstraction from these “built-in” mechanisms makes our connector-less, Darwinbased notation particularly suitable for the representation of recovered architectures. The reason is that, except for service types and bindings, identified built-in mechanisms are not explicitly represented as first-class entities in our notation. This seems to be sensible since built-in mechanisms are in fact not implemented in the source code of any system under investigation but elsewhere in the environment or platform. In the case that a software system does implement its own higher-level connectors, these can still be represented as “connection components” in our notation and have code associated with them.

5. Conclusion We have presented the X-RAY approach for distributed software architecture recovery. The main features of the approach were illustrated through the description of a detailed recovery experiment carried out on an existing distributed software system. Although the results obtained from this experiment are far from being conclusive, we believe that the characteristics of the system analysed

provided us with some interesting insights into the recovery process as a whole. For example:

 Definition and matching of code patterns are a powerful mechanism for identifying architectural abstractions. However, the usefulness of matched code fragments may be compromised if knowledge specific to the system implementation domain is not taken into account.  Identifying and distinguishing system infrastructure code from components code can be of great help to complement and improve the results of patternmatching techniques, in addition to facilitating system understanding and reuse.  Due to the way in which component interactions are generally implemented, the identification of potential system configurations, in terms of connections that might be established between components, can be considerably more difficult than identifying components themselves.  The fact that architectural information is only partially represented in the code means that the analysis of noncode artifacts, even informal ones, play an important role during the recovery process.  Architecture description languages and notations can and should be used to improve presentation and representation of recovered elements. An ADL-based notation may also contribute towards the integration of architecture recovery tools and architectural design environments. A Further Experiment We are currently conducting another recovery experiment on the 100K LOC C source for the Field [16] distributed programming environment. Field tools exchange information through a centralised message server; communication between each tool and the message server is also implemented via UNIX sockets. The results obtained thus far confirm many of our findings regarding the first experiment. For instance, our clustering facility was instrumental in separating modules belonging to the message server, programming tools and the subsystem representing the message-exchange infrastructure. In addition, we once more verified the limitations of pure pattern-matching techniques—the server and client socket-patterns were not matched in the code specific to any tool, although it is known from the documentation that all tools communicate with the message server using sockets. Complete information about socket-based connections established within the system is again not statically defined in the code. On the other hand, the significant difference in size and complexity between Field and ANIMADO poses a set

of scalability problems for the second experiment. For example, “low-level” socket calls are masked from the tools code under several layers of the message-infrastructure subsystem. This makes the gradual refinement of architectural patterns, based on additional knowledge gathered with the clustering operation, rather complicated. We are currently addressing this issue by working on an alternative solution which complements clustering and patternmatching techniques with analysis of reachability between code units based on the system activation graph. Another scalability problem has been the performance of our Prolog tools for large knowledge bases. In the first experiment, the source base was stored entirely in the machine’s virtual memory, as traditional Prolog facts, and efficiency was not a major concern. After starting the second experiment, however, we soon realised that Field’s huge source base was degrading the performance of some of our tools considerably. To tackle this problem, we are reviewing and optimising our Prolog code. Moreover, to improve access time, the source base is now stored as an indexed relational database that is provided by (and tightly integrated with) the Prolog environment. On completion of the work with Field, we plan to conduct further recovery experiments to verify the applicability and scalability of X-RAY on a wider range of system characteristics and sizes. It is only with such wide experience that we can hope to effectively improve our techniques and tools. Acknowledgements We are grateful to Prem Devanbu for his support with Gen++, to the anonymous reviewers for their valuable comments and suggestions, and to the EU for financial support in the ARES project (Framework IV contract 20477).

References [1] A. Cimitile and G. Visaggio. Software Salvaging and the Call Dominance Tree. J. of Syst. and Software, 28:117–127, 1995. [2] P. Devanbu and L. Eaves. Gen++ — An Analyzer Generator for C++ Programs. Technical report, AT&T Bell Laboratories, Murray Hill, New Jersey, 1994. [3] R. Fiutem, P. Tonella, G. Antoniol, and E. Merlo. A Clich´e-Based Environment to Support Architectural Reverse Engineering. In Proc. Int. Conf. Softw. Maint., pages 319– 328. IEEE CS Press, Nov. 1996. [4] H. Gall, M. Jazayeri, R. Kl¨osch, W. Lugmayr, and G. Trausmuth. Architecture Recovery in ARES. In Proc. 2nd ACM SIGSOFT Int. Software Architecture Workshop, pages 111–115, San Francisco, USA, Oct. 1996. [5] E. R. Gansner, E. Koutsofios, S. C. North, and K. Vo. A Technique for Drawing Directed Graphs. IEEE Trans. Softw. Eng., 19(13):214–230, May 1993.

[6] D. R. Harris, A. S. Yeh, and H. B. Reubenstein. Extracting Architectural Features from Source Code. Aut. Softw. Eng. Journal, 3(1-2):109–138, June 1996. [7] W. Kozaczynski, J. Q. Ning, and A. Engberts. Program Concept Recognition and Transformation. IEEE Trans. Softw. Eng., 18(12):1065–1075, Dec. 1992. [8] A. Lakhotia. A Unified Framework for Expressing Software Subsystem Classification Techniques. J. of Syst. and Software, 36(3):211–231, 1997. [9] J. Magee, N. Dulay, S. Eisenbach, and J. Kramer. Specifying Distributed Software Architectures. In Proc. 5th Europ. Softw. Eng. Conf. (ESEC), LNCS 989, pages 137–153. Springer-Verlag, Sept. 1995. [10] J. Magee and J. Kramer. Dynamic Structure in Software Architectures. In Proc. 4th ACM SIGSOFT FSE, pages 3– 14, Oct. 1996. [11] N. Medvidovic and R. N. Taylor. A Framework for Classifying and Comparing Architecture Description Languages. In Proc. ESEC/FSE 97, pages 60–76, Sept. 1997. [12] N. C. Mendonc¸a and J. Kramer. Requirements for an Effective Architecture Recovery Framework. In Proc. 2nd ACM SIGSOFT Int. Software Architecture Workshop, pages 101– 105, San Francisco, USA, Oct. 1996. [13] N. C. Mendonc¸ a and J. Kramer. A Quality-Based Analysis of Architecture Recovery Environments. In Proc. 1st Euromicro Conf. Softw. Maint. Reeng., pages 54– 59, Berlin, Germany, Mar. 1997. IEEE CS Press. [14] H. A. M¨uller, M. A. Orgun, S. R. Tilley, and J. S. Uhl. A Reverse-engineering Approach to Subsystem Structure Identification. J. of Softw. Maint.: Research and Practice, 5(4):181–204, Dec. 1993. [15] G. C. Murphy, D. Notkin, and K. Sullivan. Software Reflexion Models: Bridging the Gap between Source and HigherLevel Models. In Proc. 3rd ACM SIGSOFT FSE, pages 18– 28, Oct. 1995. [16] S. P. Reiss. Connecting Tools Using Message Passing in the Field Environment. IEEE Software, 7(4):57–66, July 1990. [17] M. A. F. Rodrigues. ANIMADO: An Animation System Prototype Using Dynamics. Master’s thesis, State University of Campinas, Brazil, Department of Computing and Automation, Faculty of Electrical Engineering, July 1993. In Portuguese. [18] S. L. Schneberger. Software Maintenance in Distributed Computer Environments: System Complexity Versus Component Simplicity. In Proc. Int. Conf. Softw. Maint., pages 304–313. IEEE CS Press, Sept. 1995. [19] M. Sefika, A. Sane, and R. H. Campbell. Monitoring Compliance of a Software System with Its High-Level Design Models. In Proc. 18th ICSE, pages 387–396. IEEE CS Press, Mar. 1996. [20] M. Shaw and D. Garlan. Software Architecture — Perspectives on an Emerging Discipline. Prentice-Hall, Inc., Upper Saddle River, New Jersey 07458, 1996. [21] P. Tonella, R. Fiutem, G. Antoniol, and E. Merlo. Augmenting Pattern-Based Architectural Recovery with Flow Analysis: Mosaic - a Case Study. In Proc. 3rd Working Conf. Rev. Eng. IEEE CS Press, Nov. 1996. [22] A. S. Yeh, D. R. Harris, and M. P. Chase. Manipulating Recovered Software Architecture Views. In Proc. 19th ICSE, pages 184–194. ACM Press, May 1997.

Suggest Documents