Re-engineering Legacy Code with Design Patterns: A Case Study in Mesh Generation Software

Reengineering Legacy Code with Design Patterns: A Case Study in Mesh Generation Software Chaman Singh Verma Dept of Computer Science The College of William & Mary Williamsburg, VA Ling
of 10
Related Documents
Reengineering Legacy Code with Design Patterns: A Case Study in Mesh Generation Software Chaman Singh Verma Dept of Computer Science The College of William & Mary Williamsburg, VA Ling Liu Dept of Computer Science The College of William & Mary Williamsburg, VA Abstract Software for scientific computing, like other software, evolves over time and becomes increasingly hard to maintain In addition, much scientific software is experimental in nature, requiring a high degree of flexibility so that new algorithms can be developed and implemented quickly Design patterns have been proposed as one method for increasing the flexibility and extensibility of software in general However, to date, there has been little research to determine if design patterns can be applied effectively for scientific software In this paper, we present a case study in the application of design patterns for the reengineering of software for mesh generation We applied twelve wellknown design patterns from the literature, and evaluated these design patterns according to several criteria, including: flexibility, extensibility, maintainability, and simplicity We found that design patterns can be applied to significantly improve the design of the software, without adversely affecting the performance As a secondary practical contribution, we believe that this research can also lead to the eventual development of a flexible framework for mesh generation Keywords: Design patterns, generic programming, mesh generation I INTRODUCTION Software reuse is identified as one of the best strategies to handle complexities associated with development and maintenance of complex software Reuse has been very successful in many areas, especially in compilers, operating systems, numerical and GUI libraries for a long time Although many libraries have passed the test of time, they suffer from one big disadvantage: they have fixed interfaces and data structures There is very tight coupling between their algorithms and data; therefore these libraries are not extendable for user defined data types Today, design patterns [1] and generic programming [2] are emerging techniques which have been proposed as solutions which can alleviate this problem Design patterns stress upon decoupling the system for increasing flexibility, and generic programming allows developers to reuse the software by parameterizing the data types Many application domains (eg GUI builders, network communication libraries) have greatly benefited from using design patterns Some research has been performed in the use of these methods for the development of industrial software For example, Coplien [3] et al have provided industrial experience with design patterns However, while generic programming is a wellestablished practice in scientific software, today we lack evidence that design patterns can significantly improve such code without adversely affecting performance In this paper, we explore how design patterns can be applied to reengineer legacy code to increase the flexibility of the system We have applied twelve design patterns from the literature [1] to an existing mesh generation software system We characterized the design patterns in terms of three primary design criteria: static and dynamic extendibility, reliability, and clean design of the system We then evaluated the resulting system in terms of these criteria Our evaluation assumed that users of software implemented in an objectoriented language are willing to sacrifice some performance for other benefits As a result, our evaluation of the performance impact of the design patterns is informal, and is meant to ensure that any performance degradation is acceptable to users We characterize the use of each design pattern in our system in terms (1) the probability of being able to apply it, (2) the benefits of using it, and (3) the extent to which the code must be changed to implement it We conclude, based on our experiences, that the modified system exhibits enhanced flexibility, extensibility, maintainability and understandability, without sacrificing too much performance As a longer term goal, we hope to use design patterns to develop a flexible framework for mesh generation This framework will allow researchers to collaborate on the development of new algorithms and data structures for mesh generation, and to perform experiments to assess the quality of existing algorithms In addition, we believe that this framework will lead to the development of a webbased serviceoriented version of the software The rest of the paper is structured as follows In Section II we give background and related work, Section III describes reengineering legacy code with design patterns, in Section IV we evaluate our work and Section V concludes II BACKGROUND Parnas [4] explained some realities about software aging Developing reliable and robust software is a difficult and time consuming human activity Most legacy software evolves over a large period of time Such software is trustworthy, in its limited functionality Today, much software is still being used because the user base is very large, and because the software contains hidden and critical design decisions According to Parnas, software aging is inevitable, but efforts must to taken to delay the degradation Instead of throwing away such software, we need some solution which allows us to use it in our new system, and then to slowly change or replace it as our system evolves Reengineering, as defined by Chikofsky and Cross [5], is the examination of the existing legacy software in order to understand its specification, followed by subsequent modification or reimplementation to create a new, improved form To date, a lot of research has been done in providing tool support for software reengineering For example, Verhoef in [6] discussed the necessity for automating modifications to legacy assets Brunekreef [7] also presented a software renovation factory which is usercontrolled through a graphical user interface In this work, we attempt to manually reengineer a legacy system into a more extendable and adaptable system By using design patterns, we hope to be able to use legacy code in new software, and also reengineer it in order to improve characteristics such as the flexibility of the resulting system In this section, we first present some of the disadvantages of legacy software, and then analyze the causes of inflexibility Next we propose some explanations for the lack of use of reusable software Finally, we discuss the requirements for making software adaptive A Disadvantages of Legacy Code There are several disadvantages of using legacy code It is difficult to maintain and extend the functionality of most legacy software, especially if the software is written in functional languages such as C and FORTRAN Rewriting them requires a large investment of money and human effort Such software contains substantial duplication of code for the same functionality, where the code differs only in the data types Legacy code does not take advantage of modern processor design Most of the code was written when thread programming was in its infancy and distributed computing was nonexistent In general, most legacy code handles memory and errors poorly For example, FORTRAN does not have dynamic memory allocation and C code often has memory leak problems B Analysis of Legacy Code Inflexibility Before we begin to reengineer legacy code, we need to understand the primary causes for its inflexibility Conditional statements: Ifthenelse and switch statements are fundamental to almost all programming languages, but their use sometimes restricts extension because hardcoded constructs simply assume that the alternative conditions are finite and remain fixed throughout the lifetime of the software If the conditions or requirements change, adding new conditions require significant effort ObjectOriented programs always try to eliminate the use of switch statements Multiple inheritance: Although C++ allows multiple inheritance, in general it creates more complexities and ambiguities than the solutions it provides The problem with multiple inheritance is the famous Diamond Problem [8] Some programming languages such as JAVA have already discarded this feature in favor of simplicity and consistency, using single implementation inheritance and multiple interface inheritance Lack of abstraction: Objectorientation is a powerful technique as long as we are able to break down the systems into smaller granularity and appropriate objects There are no silver bullets in using inheritance and polymorphic features of objectoriented programming even though the features are present, it is difficult to use them to implement the proper abstractions for a given system As a result it is not uncommon to find much duplication of concepts and functions in a given system Lack of separation of concerns: Software has three basic components, namely: concept (what you want to do), algorithm (how to do it) and data management (how to manage data and resource) Parnas [4] demonstrated the importance of modularity, and gave criteria for decomposing a system into modules of autonomic concerns Unfortunately, even after 30 years since the publication of this seminal paper, most applications developed today still have tight coupling among concerns, so it is difficult to change or replace any part of the code The Standard Template Library (STL) is the first widely used software library which separates these three concerns For example, STL provides abstractions for containers, iterators for containers, and algorithms over containers Each of these is largely independent of each other Conservative assumptions: Most programmers implement the code considering only the immediate requirements, and few believe that their programs will have a very long lifetime As a result, they make certain assumptions in their implementations which become obsolete very quickly C Reluctance for Reusing Software Components Despite the enormous advantages of reusable software components in both the short and long term, incorporating them into new systems or in restructuring the existing applications have not been up to expectations [9] Reluctance could be attributed to some of the following reasons: It is hard to manually understand the behavior of the code or sideeffects which may be introduced as a result of using the software In addition, automatic or semiautomatic tools for analyzing these effects are inadequate An incremental approach to software reuse is also difficult and errorprone Sometimes, small changes are just not possible As a result, either we do not change the software, or we change the entire system There is much uncertainty on the part of software developers as to whether reuse will significantly improve the quality of the resulting system There is little evidence and few accepted metrics for success in the reuse of software in real applications Very often, there is often a trade off between performance and quality The learning curve could be steep Highly motivated software developers are tempted to rewrite code Old systems often have little or no documentation If the reusable component comes from a commercial company, there might be issues related to patents, copyrights and royalty payments For a comprehensive introduction to software components and reuse, see [10] D Adaptive Software We hope to develop new software or reengineer legacy codes into software which is adaptable Adaptive software has the following characteristics: Program for change: Although it is hard to predict the future, objects should not make assumptions which are valid for only a short duration of time Whenever possible, a good design should abstract some core concepts into a small number of functions and classes, and provide simple interfaces to access the functionality Flexible and dynamic relationships: Rarely an object exists in isolation There has to be a simple mechanism to create permanent and temporary relationships among objects Centralized authority: Programs are difficult to understand, maintain and extend when some decision or functionality is scattered throughout the code Whenever possible, there should be one place for one piece of functionality This simplifies modification and testing of the system Division of labor: A class should have a single, welldefined purpose as well as a simple interface A class should delegate other responsibilities to other suitable classes Minimization of functionality increases both the productivity, reliability and reuse Standardization: Successful software reuse requires standardization With standardization comes reliability, easy availability and large support III REENGINEERING LEGACY CODE According to Gamma et al [1] design patterns are recurring solutions to software design problems which we find repeatedly in realworld application development When we use design patterns, we do not reinvent the wheel Another way of looking at design patterns is to consider them as wellproven component integrations with a common vocabulary for the system designer and developer Buschmann [11] collected design patterns in the context of software architecture Fig 1 Surface mesh generation on pipe Gamma et al [1] identified 23 design patterns and created a catalog, which is known as the GoF (Gang of Four) book We have taken several patterns from this catalog and applied them to our application In the figure 2 we list all the GoF design patterns which we applied, and the main purpose behind using each pattern in our application To shorten our paper, we have not shown any examples of some patterns in the next section( Singleton, Reference counting, Decorator, Facade etc) Although design patterns are often written in an objectoriented language, design patterns have little to do with objectorientation ( [3]) Application: Mesh Generation Numerical simulation uses partial differential equations (PDEs) For example, the NavierStokes equations are used in Computational Fluid Dynamics The first step in numerical simulation is to discretize the geometric space into a large number of cells In 2D these cells are triangles or quadrilaterals, and in 3D they are tetrahedra, pentahedra or hexahedra Once a good quality mesh has been created, numerical discretization of the PDEs is carried out, and for each cell, governing equations are solved For complex geometries, an unstructured mesh (in which the topology is explicit) is preferred because of the engineering requirements for high quality mesh A sample mesh generated over a simple geometry is given in Figure 1 A Components in Mesh Generation Software System Mesh generation is a fairly complicated process which utilizes many external libraries, software tools, algorithms and data management tools The following are main components in mesh generation which explains the need for reusing the software: Geometric modeling: Construction of a geometric model involves designing the model with geometric primitives such as circles, lines, planes or NURBS (NonUniform Rational BSplines) curves and surfaces Highly interactive graphical display systems are needed to design complicated models, which is often done with commercial CAD systems Extendibility Reliability Cleaniness State Pattern Visitor Pattern Factory Pattern Strategy Pattern Decorator Pattern Fig 2 Design Patterns Observer Pattern Reference Counting Prototype Pattern Singleton Pattern Memento Design patterns objectives Template Pattern Iterator Pattern Bridge Pattern Adaptive or Multiprecision library: Geometric algorithms demand robustness in numerical calculation Most of the time, standard IEEE floating points are not suitable for this task, and therefore researchers either use libraries for exact arithmetic or fast adaptive multiprecision computation Geometric kernel library: A geometric library is a collection of large spatial data structures for geometric space (eg Kdtrees, quadtree, octree, BSP, etc) These libraries often provide algorithms for computing the convex hulls, 23D triangulations, Voronoi diagrams and fast proximity queries Mesh generation algorithm: These libraries include components for generating a structured or unstructured mesh in the specified geometries An unstructured mesh is mostly generated by using either Advancing Front or Delaunay Triangulation algorithms Sequential and parallel data structures: Very often, we need an extremely refined mesh containing millions of cells for finite element analysis In order to provide efficient insertion, removal or query for some elements, commercial software often uses a database (eg SQL or Oracle) Domain decomposition and object migration tools: We often use parallel processing to reduce the time and memory requirements for the execution of an application Software components are needed to decompose, distribute and control the distributed tasks Interactive visualization: Interactive graphical systems help in understanding and modifying the geometric space and mesh generation In fact, they are an integral part of the mesh generation process B Applying Design Patterns In the following section, we apply several GoF design patterns in our application and explain why they are needed using small examples 1) Adapter Pattern Sometimes there are incompatible interfaces between two software components Adapter pattern provides a clean mechanism to adapt one interface to another Adapter pattern could also be used to hide the old design with the new one without reimplementing the class from scratch The end user will perceive the class according to new design rules In our application, the geometric modeler uses NURBS curves and surface which were originally written in ANSI C Here is how we wrap the original code in the new class 1 namespace NURBS 2 class Curve 3 4 pubic: 5 Curve( NURBS_Curve_t *c ); 6 7 Point2D evaluate(double t ); 8 point_t pt = NURBS_EvalCurve( oldcurve, t); 9 Point2D result; 10 result[0] = ptx; 11 result[1] = pty; NURBS_Curve_t *oldcurve; ; In this example NURBS Curve t class is an old structure which is not consistent with the new system Old structure and old functions are kept as private member of the class The end user can use the new system which uses the old system without ever knowing inner details of the old system 2) Bridge Pattern Information hiding is fundamental to OOP Keeping class abstraction from its implementation has many advantages for the following reasons Most of end users are only interested in using the classes, and not their implementations Keeping implementation in header files results in longer compilation time and if the header file changes, the entire application has to be recompiled It makes changing implementation easy (There may be different implementation for different platforms) Many classes can use reference counting for lazy object copying The Bridge Pattern provides the solution to the problem by providing a pointer to the representative class in the original class and forwarding all the requests from the main class The only disadvantage of this approach is that it require indirection for every function call, but this is the price we are willing to pay for increasingly the flexibility //Implemented in filename MeshGen2Dh class MeshGen2D MeshGen2D() rep = new MeshGen2DImpl(); void setdata( int d) rep setdata(d); int getdata() const return rep getdata(); MeshGen2DImpl *rep; ; // Implemented in filename MeshGen2DImplh class MeshGen2DImp MeshGen2DImpl(); void setdata( int d) data = d; int getdata() const return data; int data; ; This pattern is used in the new system wherever a class implementation is lengthy and changable 3) Factory Pattern Consider the following code from legacy code void Reader:: readfile( ifstream &infile) GeoEntity *geoentity; while(infile) infile objecttype; switch( objecttype ) case 0: geoentity = new GeoVertex; case 1: geoentity = new GeoEdge; case 2: geoentity = new GeoFace; case 3: geoen
Related Search
We Need Your Support

Thanks to everyone for your continued support.

No, Thanks