Patterns: One Way to Solve the Reuse Problem by Linda Rising Introduction There has been a sense in the software engineering community that we are continually reinventing the wheel. In companies around the globe, projects go their own way, solving problems that are similar or identical to problems being solved by other projects, sometimes down adjacent corridors in the same building. It's easy to point at a lack of communication and the classic "not invented here" syndrome but the fact is, there hasn't been an appropriate communication medium for transferring this knowledge. The widely applied code libraries did not even begin to tackle the problem. The notion of a pattern is based on the work of architect, Christopher Alexander [Alexander+77], who captured solutions to recurring problems in the building of towns, neighborhoods, schools, and homes. When expertise is captured and shared with others, it also brings improved communication through a vocabulary of pattern names that reflect the successful solutions. There is a significant effort in the software community to apply this idea to problems and solutions in software development. Patterns provide a way to reuse expertise that can be used across domains at all levels of development. A pattern is, on the surface, simply a form of documentation. Pattern authors document patterns they observe across many software projects. Experienced designers read these patterns and remark, "Sure, I've done that — many times!" The power of this kind of documentation is that knowledge, previously found only in the heads of experts, is captured in a form that is easily shared. Patterns are not theoretical constructs created in an ivory tower; they are artifacts that have been discovered in multiple systems. The solution is one that has been applied more than twice. This "Rule of Three" ensures that the pattern captures tried and true applications, not just a good idea without real use behind it. The approach calls to mind the notions of cohesion and coupling developed by Yourdon and Constantine [Yourdon+78] during late afternoon postmortem sessions, discussing lessons learned from past projects. The cohesion and coupling ideas captured qualities of real systems. The guidelines were not theoretical musings but were based on observations of capabilities of systems that made life easier for developers and maintainers. Patterns Overview Members of the software community were introduced to patterns by a book [Gamma+95] co-authored by four individuals, often called the Gang of Four or GOF. The book documents 23 patterns recognized as providing accepted solutions for recurring problems in object-oriented design. Even though no quantifiable results are available, teams familiar with these patterns indicate that design and code in projects seems to have a new character. Design decisions are made more quickly, with trade-offs naturally included. Since most of the activity in the patterns community has been tied to object-oriented development, most of the publications and pattern discoveries have been directed toward solving problems in that paradigm. However, the notion of patterns is not tied to any methodology or language. Jim Coplien and Bob Hanmer were involved in an interesting patterns experiment at AT&T (now Lucent Technologies). Management was concerned about the future retirement or transfer of experts who had been part of the 4ESS™ switch, one of AT&T's large telecommunications switches. The directive to Coplien and Hanmer was to extract this expertise and capture it as patterns. When Coplien and Hanmer shared patterns from their 4ESS switch experience [Adams+96], many of them were already known by developers at other companies. Reports like this demonstrate that patterns are much more than the artifacts of object-oriented design. The 4ESS switch patterns are certainly not object-oriented; that is, they don't involve inheritance or polymorphism. Patterns have also been documented in customer interaction [Rising00] and system test [DeLano+97]. Jim Coplien has created a body of patterns literature [Coplien95] concerned with organizational and process patterns. Alistair Cockburn [Cockburn98] has written extensively in the area of risk management patterns. Perhaps these organizational patterns will be the most beneficial patterns of all and might, in the long run, have the greatest impact on our technology. Pattern Form There are several pattern forms for documenting patterns. These differ but share some elements. The Façade pattern, from the Design Patterns text [3], will be a running example to explain some of the elements of the pattern form. Name—a word or short phrase that describes the pattern. The pattern name, like the name of an algorithm or data structure, carries a lot of information, not because it is meaningful in itself but because of the underlying concept it represents. Those who understand this underlying concept can use the name as a short hand with others who, because of training or experience, have a similar understanding of the concept. For example, the name Binary Search carries information on the appropriateness of the use of this algorithm (only for sorted arrays) and performance (faster than a linear search). All developers assume this information. Everyone might not readily recall the implementation of a Binary Search but everyone should know where to find this information and be able to use it. The name of a pattern is a powerful mechanism for evoking ideas in the mind of the hearer. Among the benefits of discovering and using patterns, the impact on communication is the most powerful. The time spent in design discussions is reduced and product quality is improved as a result of incorporating solutions with proven records of success. Communication improvement takes place not only within teams but across teams as well. Moving from one team to another should not involve re-learning design vocabulary. Everyone knows Binary Search. Everyone knows stacks and queues. Everyone should also know design patterns. Problem to be solved. For Facade, the problem is: How can clients of a subsystem be shielded from the complexity of the interfaces of all the classes in the subsystem? Context—the setting where the problem is found. For Facade, the context is: A system that contains subsystems where dependencies exist between subsystems. Forces—considerations that must be weighed to reach the best solution. The forces answer the question, "Why is this a hard problem?" For Facade, the forces are:
As the reader encounters the forces, there is a build-up of tension
as the reader admits, Solution—where the essence of the solution is described, the components, their relationships and responsibilities. Other patterns can be referenced to provide solutions for sub-problems. In a pattern the implementation is not specified. Just as software design is separate from details such as programming language, operating system, or hardware considerations, patterns are removed from particular settings, and can be used over and over. Often an Example section is included for sample code. This is to help the user understand the pattern, not to provide reusable code. The code can be reused, of course, but a design pattern is not restricted to a particular implementation, programming language or domain. For Facade, the solution is:
This pattern is not limited to applications in object-oriented development. Many of us have used Facade in structured design. It is simply a module that enables simpler communication with a subsystem. Resulting Context—contains a description of the state of the world after the pattern has been applied. This description should not be trivial; that is, it should not simply say that the problem has been solved but should describe the impact on the user of applying the pattern. Potential users of the pattern can study this section to weigh the costs and benefits. In a pattern language, the resulting context of one pattern becomes the context for successive patterns that address new issues. The resulting context for Facade is:
Conclusion The idea behind patterns is simple, yet profound. Patterns capture knowledge that experts apply in solving recurring problems. What's reused when a pattern is applied is the knowledge of experienced designers. In a recent report of knowledge sharing [Davenport+98: xiv]:
Metrics like this show the benefit of sharing expertise. Documenting solutions helps all of us improve. As Weinberg has wisely noted, "None of us is as smart as all of us!" He was talking about the review process but his observation is certainly appropriate here. When we share our knowledge, we can all build on that knowledge and improve instead of re-inventing solutions over and over again. Patterns are not a "silver bullet." They will not address all reuse issues or single-handedly solve the software crisis. They will not transform novices into experts but they provide a simple, intuitively appealing approach for tackling problems that appear over and over again. Patterns — an approach to reuse that has been used as long as novices have been learning from experts. Information on the several patterns listservers, patterns conferences, published works, and other patterns home pages can be found on the web. Here are some good places to start: The Hillside Group, the founding fathers of patterns! References [Adams+96] Adams, M., J. Coplien, R. Gamoke, R. Hanmer, F. Keeve, and K. Nicodemus, "Fault-Tolerant Telecommunication Patterns," Pattern Languages of Program Design 2, Addison-Wesley, 1996, J. M. Vlissides, N. L. Kerth, and J. O. Coplien, eds, pp. 549-562. [Alexander+77] Alexander, C., et al., A Pattern Language, Oxford University Press, 1977. [Cockburn98] Cockburn, A. Surviving Object-Oriented Projects, Addison-Wesley, 1998 and http://members.aol.com/acockburn/riskcata/risktoc.htm [Coplien95] Coplien, J.O., "A Generative Development-Process Pattern Language," Pattern Languages of Program Design, Addison-Wesley, 1995, J. O. Coplien and D. C. Schmidt, eds, pp. 183-238. [Davenport+98] Davenport, T.H. and L. Prusak, Working Knowledge: How Organizations Manage What They Know, Harvard Business School Press, 1998. [DeLano+97] DeLano, D. and L. Rising, "A Pattern Language for System Test," Pattern Languages of Program Design 3, Addison-Wesley, 1997, R. Martin, D. Riehle, and F. Buschmann, eds, pp. 503-525. [Gamma+95] Gamma, E., R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995. [Rising00] Rising, L., "Customer Interaction Patterns," Pattern Languages of Program Design 4, Addison-Wesley, 2000, N. Harrison, B. Foote, and H. Rohnert, eds, pp. 585-609. [Yourdon+78] Yourdon, E. and L. L. Constantine, Structured Design, Prentice-Hall, 1978. Support for IDE Hard-disks from the DACS-80x86 Cross Compiler System DACS-80x86 now supports IDE Hard-disks. The IDE Hard-disk driver is a hardware independent interface system providing basic fundamental functionality for file manipulation such as open, close, read and write. by Poul Munch General System Description The Ada compiler which supports the IDE Hard-Disk is the DDC-I Ada Compiler System - DACS-80x86FM (486 & Pentium, Flat Mode) and the target is a Pentium PC equipped with an IDE Hard disk. The Hard-disk (HD) interface operates on a Intel Pentium PC board that includes BIOS, RAM and the corresponding IDE Hard-disk. The Hard-disk shall be FAT 16 formatted under MS-DOS installation. If the HD is larger than 500 Mb or partitioned, then the interface operates in the first partition. Handling the Hard-Disk - About the Approach A sector is the smallest data representation and describes a block of 512 bytes. On startup, the interface reads the master boot record, (the first sector on the HD). This sector contains the Partition Table. This table is used to check the location of the partition. The Partition Table describes the location of the Boot Record for that partition. It also derives the geometry of the HD (given by sectors per head, heads per cylinder and the total number of cylinders - in that partition). That Boot Record is read to derive the location of the File Allocation Table (FAT), Root Directory and Cluster information used in that partition. All files consist of an integral number of clusters each being a partition-defined number of sectors. The interface then performs checks to ensure that the HD is of correct format (including FAT 16 format). From the FAT, the Cluster Number(s) for a file in that partition may be derived and their locations found. Once a file is found, read or write operations may be performed on it. Read and write operations may be specified in either of two types of views of the overall HD system. These two views are the logical view and the physical view of the HD. The logical view of the HD system supports the file abstraction concept. Files are entities that may be opened and closed and they possess other attributes such as a name and size. Files are contained within data sections on the HD called clusters. The DOS file format splits a partition of the Hard-disk into a number of clusters. Files then possess a logical, incremental index of data sections (starting from position 1 to max. size for that file) called sectors and each of these sectors may be searched for and then read from and/or written to. The physical view of the HD system does not support file abstraction. The HD is seen as a collection of physical entities, namely cylinders, heads and sectors. The sector concept is the same in both the physical view and the logical view; both represent the lowest level at which the data is actually stored. The HD is then comprised of a number of cylinders were each cylinder contains a number of heads and each head has a number of sectors. A read or write operation from a physical view is issued with the coordinates of the cylinder, head and sector to be read or written. If a read or write operation fails, the operation is attempted a number of times again but if it continues to fail, it will generate an error and an exception shall be raised. An overview of some key Hard-Disk Operations Listed below is a number of key features regarding access of the IDE Hard-Disk file. Open File Opens a previously DOS-allocated file. After a successful open, the sectors of the file can be freely accessed. If the file does not exist, IDE_NAME_ERROR is raised. If the file is already open, IDE_STATUS_ERROR is raised. Position After locating a file from the root directory and following the FAT, the disk head may be positioned at an indexed sector number in the cluster areas of that file. The range may be set outside the file but any subsequent read or write operation will raise the IDE_END_ERROR exception. Read File From an opened file, reads data in 512 byte blocks. After a read, the index/head is advanced a further position such that consecutive reads scan along a file. The exception IDE_STATUS_ERROR is raised if the file is not open. If the read is beyond the end of the file, this will raise the IDE_END_ERROR exception. The exception IDE_DATA_ERROR is raised if the HD-controller reports an error. Write File From an opened file, writes data in 512 byte blocks. After a write operation, the index/head is advanced a further position such that consecutive writes advance along a file. The interface shall provide write ability to any sector belonging to the data file. The exception IDE_STATUS_ERROR is raised if the file is not open. If the write goes beyond the end of the file, this will raise the IDE_END_ERROR exception. The exception IDE_DATA_ERROR is raised if the HD-controller reports an error. Close File Disassociates a file from the interface, closing it. If the file is not open when a close operation is performed, then the exception IDE_STATUS_ERROR is raised. Unhandled Exception Tracebacks in DACS by Richard Frost In DACS 80x86, the Run-Time System (RTS) can produce a traceback for unhandled exceptions. The amount of information contained in the traceback is controlled by compiler and linker options. The compiler option trace_back defaults to 2 including both subprogram names and line numbers. Setting this option to a value of 1 excludes the line numbers, and a value of 0 removes all symbolic traceback information. Allowing the compiler to include traceback information will make the generated code size larger, since additional tables must be stored in the application for the RTS to lookup when an unhandled exception occurs. For cross 80x86 targets, the linker supports selective linking via the selective_link option. This will force the linker to remove all subprograms that are not referenced by any calls in the program. This option additionally has the capability at link time of removing traceback information generated at compile time. Specifying a value of no_traceback will remove all traceback information, while a value of low_traceback removes line numbers, and the value high_traceback retains all traceback information generated by the compiler. The default is not to do selective linking, therefore all traceback information is preserved. In a multi-tasking application, the default is for the RTS to display a traceback only for an unhandled exception in the main task. The linker has an option enable_task_trace which will force the RTS to display a traceback for each task that completes due to an unhandled exception. Interpreting a tracebackThe traceback includes valuable information to diagnose an unhandled exception, even when symbolic information is not generated for the RTS to display. Following is sample source code which raises an exception that is not handled. Assuming the code is compiled/linked to not include any symbolic information in the traceback, the resulting traceback is displayed.
Unhandled exception raised at 00401420H Exception : "EXCEPTION_PACKAGE.SOME_EXCEPTION" [4120,1] Trace-back follows: Frame Ptr Block type Entry point Called from Ada Unit + Offset --------------------------------------------------------------------------- 0004EF40H Subprogram 0040140AH 0040147BH A_04118_0+00000012H 0004EF58H Subprogram 0040145BH 00401482H A_04121_0+00000023H 0004EF6CH Subprogram 00401441H 004011CEH A_04121_0+00000009H 0004EF74H Subprogram D3FF085DH *** Corrupted Stack *** The first line of the traceback includes the address where the exception occurred, which in this case is 00401420H. The second line indicates which exception occurred EXCEPTION_PACKAGE.SOME_EXCEPTION and its identification including unit number in the library and exception id within that unit. Following the header is the actual traceback showing the call stack from the point of the exception back to the main procedure. The first column Frame Ptr specifies the value of the block pointer register (E)BP for that frame. The second column Block Type describes the type of frame, typically Subprogram or Inner Block. Inner Block indicates that a declare block or a compiler generated block was used, providing an extra frame within the same routine. The last three columns are what are mainly used to interpret the traceback. The third column Entry Point is the start address of the routine. The fourth column Called From indicates the address from which Entry Point was called. The last column specifies the unit number and offset in a listing to the Entry Point. To interpret the traceback, you will need a disassembly listing of each of the Ada units specified. For example, to disassemble unit 4118 the following command may be used: %disassembler% /lib=%library% /log=u04118.dis /body /unit=4118. The relevant portions of the disassembly are included below.
...
a_04118_00000 :
00000004 66B80100 MOV AX,1
...
Program Unit: EXCEPTION_PACKAGE.RAISES_EXCEPTION:
e_04120_00001 :
00000016 B810000000 MOV EAX,16
...
------------------------------------------------------------------------------
10: begin
11: raise some_exception;
------------------------------------------------------------------------------
00000027 E800000000 CALL r1ehrc_raiseexceptioncode
0000002C 18100100 Exception. Module: 4120 Id: 1
...
In the disassembly log you will need to find the label A_unit_00000 and get its offset in the log and add the offset from the traceback to get the offset of the Entry Point. For the first line of the traceback above, this results in A_04118_00000 (00000004H) + 00000012H = 16H, which is the start of the routine e_04120_00001. To find the exact location within the routine we need to take the location of the unhandled exception and subtract the Entry Point to get the offset within the routine. This offset may be added to the offset in the disassembly of the start of the routine. Therefore the formula for the first line of the traceback is (offset of A_Unit_00000 in disassembly) + (offset in traceback) + (Previous Called From - Entry Point) = (offset in disassembly of unhandled exception). For the traceback above, this results in 00401420H - 0040140AH + 16H = 2CH. Therefore, in the disassembly offset 2C is location that the exception was raised, and from the disassembly log we can see that a call to r1ehrc_raise_exception_code with exception 1 of unit 4120 is the location of the unhandled exception. To continue with subsequent levels, the same approach is used, but instead of the location of the unhandled exception, the Called From location on the previous line is used to determine the offset within the routine. To interpret the second frame of the traceback, portions of unit 4121's disassembly are provided below:
...
a_04121_00000 :
00000004 E94C000000 JMP 00000055H
...
Program Unit: TRACEBACK:
e_04121_00001 :
0000000D B810000000 MOV EAX,16
...
Program Unit: TRACEBACK.INNER_SCOPE:
e_04121_00002 :
00000027 B814000000 MOV EAX,20
...
21: exception_package.raises_exception;
------------------------------------------------------------------------------
...
00000047 E800000000 CALL e_04120_00001
...
22: end inner_scope;
24: begin
26: inner_scope;
------------------------------------------------------------------------------
0000004E E800000000 CALL e_04121_00002
...
The remaining two lines of the traceback can then be interpretted as follows: (offset of A_Unit_00000 in disassembly) + (offset in traceback) + (Previous Called From - Entry Point) = offset in disassembly of call to previous frame. This results in 4+23+(0040147BH-0040145BH ) = 47 and 4+9+(00401482H-00401441H) = 4E. Therefore, after interpreting the traceback, the following symbolic call chain was determined: Exception_Package.Raises_Exception line 11 raised Some_Exception Traceback.Inner_Scope line 21 called Exception_Package.Raises_Exception Traceback line 26 called Traceback.Inner_Scope
|