Type-Check Elimination

A reengineering pattern describes how to go from an existing legacy solution to a new refactored solution. In this paper we discuss the role of reengineering patterns and contrast them with design patterns and antipatterns. We then highlight the structure of a reengineering pattern and present two simple, related patterns for type-check elimination. 1 Reengineering Patterns When important legacy software can no longer gracefully evolve to meet changing requirements it is often reengineered. Reengineering patterns codify and record knowledge about modifying legacy software: they help in diagnosing problems and identifying weaknesses which hinder further development of the system and aid in finding solutions which are more appropriate to the new requirements. Reengineering patterns differ from Design Patterns [GHJV95] in their emphasis on the process of moving from an existing legacy solution to a new refactored solution. Whereas a design pattern presents a solution for a recurring design problem, a reengineering pattern presents a refactored solution for a recurring legacy solution which is no longer appropriate, and describes how to move from the legacy solution to the refactored solution. The mark of a good reengineering pattern is (a) the clarity with which it exposes the advantages, the cost and the consequences of the target solution with respect to the existing solution, and not how elegant the target solution is, (b) the description of the change process: how to get from one state of the system to another. We also contrast reengineering patterns with AntiPatterns [BMMM98]. Antipatterns, as exposed by Brown et al., are presented as “bad” solutions to design and management issues in software projects. Many of the problems discussed are managerial concerns that are outside the direct control of developers. Moreover, the emphasis in antipatterns is on prevention: how to avoid making the mistakes which lead to the antipatterns. Consequently, antipatterns may be of interest when starting a project or during development but are no longer helpful when we are confronted with a legacy system. In contrast, in reengineering we prefer to withhold judgement and use the term “legacy solution” or “legacy pattern” for a solution which at the time, and under the contraints given, seemed appropriate. In reengineering it is too late for prevention, and reengineering patterns therefore concentrate on the cure: how to detect problems and move to more appropriate solutions. In dealing with legacy systems it is neither cost effective nor prudent to refactor the code without a clear goal. The task of determining which parts of the legacy software are significantly obstructing further development is, however, a difficult one. Reengineering patterns try to guide developers by discussing issues such as the applicability of the pattern, the cost of transformation and any particular implementation problems. Note, however, that the question of whether a pattern should be applied cannot be wholly addressed in this discussion and remains ultimately a question of judgment. In the context of a project developing a methodology for reengineering object-oriented legacy systems to frameworks, we are working on a pattern language for reengineering. The patterns presented in this paper address typical legacy solutions found in object-oriented code. We expect, however, that some reengineering patterns will describe more overall strategies for dealing with legacy systems, and thus be of a less technical nature than the patterns presented here. We continue with a brief overview of the format used for writing reengineering patterns. This is followed by a pair of sample patterns dealing with the problem of missing polymorphism. 2 Form of a reengineering pattern Pattern Name. We use a short sentence with a verb that emphasizes the kind of reengineering transformation. Intent. A description of the process, together with the result and why it is desirable. Applicability. When is the pattern applicable? When is it not applicable? This section includes a list of symptoms, a list of reengineering goals and a list of related patterns. Symptoms are those experienced when reusing, maintaining or changing the system. Reengineering goals present the qualities improved through the application of this pattern. Motivation. This section presents an example: it must acquaint the reader with a concrete example so the reader can better understand the more abstract presentation of the problem which follows in the structure and process sections. The example clearly describes the structure of the existing legacy system, the structure of the reengineered system, and the relation between the two. The state of the system before and after the application of the pattern are described. Structure. It describes the structure of the system before and after reengineering. Each structure section is similar to the structure section in the Gang of Four pattern book. The participants and their collaborations are identified. Consequences discuss the advantages and disadvantages of the target structure in comparison to the initial structure. Process. The process section is subdivided into three sections: the detection, the recipe and the difficulties. It is well-known that the code often tells the reengineer where the problem is and that the reengineering process must be goal driven to avoid reengineering code that is not obstructing further development. However, there are cases where it may be interesting to automatically detect where different patterns can be applied. The detection section describes methods and tools to guide and detect when the code is indeed suffering from the serious problems and that the process described can help to alleviate this problem. The recipe states how to perform the reengineering operation and its possible variants. The difficulties section discusses situations where the reengineering operation is infeasible or its application is compromised by other existing problems. Discussion. In this section cost and benefit tradeoffs of applying the pattern are discussed. The legacy solution is commented to show why such a solution was good at the period of time but insufficient or unadapted to the current problem. What is the cost of detecting this problem? What is the magnitude of the problem? What is the benefit gained by applying the pattern? This discussion should aid an engineer in deciding (once he knows the pattern is applicable to the code) whether or not it is, in this specific case, worth applying the pattern. Language Specific Issues. This section lists what must be specifically resolved for each language. What makes it more difficult? More easy? 3 Type Check Elimination The introduction of polymorphism is an important and frequent reengineering operation in objectoriented legacy systems. By replacing hand coded polymorphism with the support built into the language both simplifies the software and makes it more flexible. Even in the presence of polymorphism it is our experience that developers continue to implement functionality that would be best handled through polymorphism by other means. Here we present two patterns: Type Check Elimination within a Provider Hierarchy and Type Check Elimination in Clients. The essential distinction between these two patterns is the Location of the type check: in Type Check Elimination within a Provider Hierarchy the type check is encapsulated within the provider class while in Type Check Elimination in Clients the type check is performed in client classes and tightly couples the clients to the provider hierarchy. Note that when one class depends on another we call this class a client class and the class it depends on is a provider class. This is a general terminology and is not specific to these patterns. Each reengineering pattern is self contained as we have found that depending on our goals when reengineering a software system we may be interested in one and not the other. For example, if we wish to extract a subsystem then Type Check Elimination in Clients is very important. On the other hand if we wish to add functionality then Type Check Elimination within a Provider Hierarchy is often more relevant. Type Check Elimination in a Provider Hierarchy Intent Transform a single provider class being used to implement what are conceptually a set of related types into a hierarchy of classes. Decision structures, such as case statements or if-then-elses, over type information are replaced by polymorphism. This results in increased modularity and facilitates the extension of functionality through the addition of new subclasses.