CSC/ECE 517 Fall 2009/wiki2 7 co

From Expertiza_Wiki
Jump to navigation Jump to search

Code Reuse Methods and Mechanisms

  The best way to attack the essence of building software is not to build it at all.
        - Fredrick P. Brooks, Jr.,The Mythical Man-Month

In his 1975 classic, The Mythical Man-Month, Fred Brooks claimed that the implementation of design (what he called "accidental tasks") was essentially efficient enough that improvements in that area would not result in significant gains in productivity. Twenty years later, in a follow-up edition, he refined and clarified his view. He recognized that the rise of object-oriented languages and methodologies had the potential and promise of easy reuse, although he was still skeptical of dramatic productivity claims. Fundamentally, though, he saw that overall software development productivity can be enhanced through reuse.

This page will review some of the different techniques that are available for code reuse, and then a comparison of the techniques will be presented.

Code Reuse Through Direct Code Use

There are several classic code reuse mechanisms which work at the fundamental source code level. This section will discuss this class of mechanisms.

Cut & Paste

One of the most basic reuse mechanism is the cut and paste method. Simply find the source code that performs the function that is required and copy the code into the place it is needed. This method can be implemented either by copying from some external source, such as a book or another software program, or by duplicating the same code within the same program. The latter method is generally a very discouraged practice. Additionally, using this method can lead to plagiarism.

Includes

In C and C++, the includes construct places the contents of the file specified in the include parameter in the spot where the include is placed:

  #include "time.h"
  
  class Time
  {...

In this example, the compiler effectively copies the code within time.h at the point where the #include statement is declared. Nearly all compilers will provide for specifying paths to search for the indicated file and provide rules or conventions for dealing with multiple occurrences of the same file.

Code Reuse through Subroutines

This section explores code reuse mechanisms that involve executing common blocks of code that can provide utility or common operations.

Gosub

In Vintage Basic (comparable to the Basic language dialects from the late 70s and early 80s), individual lines of code are designated by line numbers. Blocks of code can be designated as subroutines and the GOSUB command can jump to these blocks of code. Execution jumps back to the line following the GOSUB command once a RETURN is encountered in the subroutine:

  10  FOR I=1 TO 5
  20  GOSUB 100
  30  NEXT I
  40  END
 100  PRINT "Value = "; I
 110  RETURN

Note that in this construct, parameters are not passed to the subroutine, but since all variables were global, parameters could still be simulated through careful use of global variables.

Procedures

In Pascal, procedures provided a way to jump to a block of code, and parameters can be passed to that block as well:

  var j:Integer;
  
  procedure PrintValue(i: Integer);
  begin
     Write("Value = ");
     Writeln(i);
  end;
  
  begin
     for j := 1 to 5 do
        PrintValue(j)
  end.

In this scheme, in order to update parameters with actions performed within the procedure, a parameter must be passed by reference, effectively passing the memory address of the parameter so it can be effected directly.

Functions

Pascal also includes a construct called functions which allow a subroutine to return a value to the caller:

  var j:Integer;
  
  function Square(i: Integer): Integer;
  begin
     Square = i * i;
  end;
  
  begin
     for j := 1 to 5 do
        Writeln("Square of ", j, " = ", Square(j));
  end.

Although a similar construct can be created using procedures, functions allow for more concise coding.

Code Reuse through Extension

Many languages allow existing code to be reused through extensions that leave the original code unaltered. This section explores those mechanisms.

Methods

Methods (in Java and C++) are functions which are encapsulated within a class structure. The class structure can provide a level of data and implementation hiding which can facilitate better design.

  public class Square {
     int size;
     ...
     public void draw() {
        ...
     }
  }

In and of themselves, methods do not offer any additional capability over functions, but operating within a class structure, several object-oriented paradigms can make methods more effective for code reuse.

Inheritance

Inheritance is an object-oriented concept which allows a developer to create subclasses of a base class which can refine or expand the base class.

  public class Rectangle {
     int width, height;
     public Rectangle(int w, int h) {
        ...
     }
     ...
  }
  
  public class Square extends Rectangle {
     int size;
     public Square(int s) {
        super(s,s);
        ...
     }
     ...
  }

In this example, the Square class extend the Rectangle class. In the Square constructor, the Rectangle constructor is called and, thus, is reused.

Using inheritance, base classes can hold much of the common elements that are used by the subclasses. Code is reused by the subclasses through the common base class.

Polymorphism

Polymorphism is another object-oriented concept which allows a developer to reuse a defined interface in a class, but provide for an implementation that is specialized to that specific class.

  abstract public class Shape {
     abstract public void draw();
  }
  
  public class Square extends Shape {
     public draw() {
        ...
     }
  }
  
  public class Circle extends Shape {
     public draw() {
        ...
     }
  }

In this example, the draw method has been indicated in the Shape class, but implemented in the subclasses Square and Circle. This example may not seem like code reuse, but it can be argued that the concept around providing a common signature for the subclasses is a type of reuse. Further, polymorphism provides one of the key foundations for object-oriented programming.

Generics

Generics in Java and C++ provide a mechanism where a type or class (such as String, int, or Shape) can be provided to a method as a parameter. In this fashion, the generic method can accommodate multiple types, or they can be used to catch potential typing issues at compile time that might otherwise be missed.

One example of generics in Java is in the java.util class for collection classes. In the declaration for the Collection class, the type is declared in angle brackets ("<Type>") and is implicitly provided as a parameter to the class and instance methods for an object of that class. So, instead of having to write one Collection class for Integers, another for Strings, another for Shapes, etc., using generics allows a single class to support multiple types, which enables the class to be utilized to a much greater extent.

Mixins/Modules

In Ruby, classes and modules can be combined to provide a "mixin". Modules are code blocks which define methods which are not directly associate with a particular class:

  module Greeter
     def sayHi(name)
        puts "Hi $#{@name}"
     end
  
  module Shouter
     def yellHello(name)
        puts "HELLO $#{@name}"
     end

As you can see, sayHi and YellHello are methods with implementations, but they are not associated with a specific class. These modules can be associated with a class in one of two ways. Through the class definition:

  class Talker
     include Greeter
     extend Shouter
     ...
  end
  
  class TalkerToo
     ...
  end

...or an instance of the class can be mixed at runtime:

  ttoo = TalkerToo.new
  ttoo.extend Shouter
  ttoo.include Greeter

Extending a class or instance with a module adds the methods as class methods. Including a class or instance with a module adds the methods as instance methods.

The Ruby language provides some specific modules that can be used to extend classes such as Comparable and Enumerable.

Aspect

Aspect-oriented programming is a methodology which supports the separation of concerns for a program feature which would normally have to intrude into several different modules which are not related to that feature. A common example of a cross-cutting concern is logging program execution, where every method entry and exit may warrant a message added to a log file. By using an aspect, the logging function can be defined outside of the implementation of the rest of the program, leaving the rest of the implementation decoupled from the logging implementation.

Aspect mechanisms are available for both Ruby and Java:

  • For Ruby there is AspectR and Aquarium. Both are provided as modules which are included within the target implementation.
  • For Java, one of the leading mechanisms is AspectJ which is an Eclipse-homed implementation. Additional tooling, called AJDT, is provided as well.
  • Several AOP Frameworks for Java are available.

Although touted as a method for separation of concerns, aspects also provide a way for programmers to provide a set of code which interacts broadly across a system, and thus reuse of the either the aspect or the functions within a system is achieved, similarly to how function or method calls achieve code reuse.

Comparing the Various Mechanisms

Reuse Mechanism Simplicity/Complexity Versatility Performance/Efficiency
Cut & Paste Extremely easy to implement Not very versatile; requires careful attention in editing; changes in function require understanding of implementation Highly inefficient; potential to have multiple copies of the same code throughout system
Include Very easy to implement Not versatile; potentially requires detail knowledge of the entity being included Poor performance; the include is effectively cut and paste without the cognitive baggage
Gosub Very easy to implement Can be versatile through use of global variables as switches, however subroutine loose cohesion Somewhat more efficient than cut and paste and include in terms of memory and program size; variables are "passed" via global means
Procedures Easy to implement Fairly versatile though use of parameters More efficient than gosub: Parameters provide local variables (on the stack) to the procedure, and potentially save global memory.
Functions Easy to implement Slightly more versatile with addition of return value in addition to parameters Similar to procedures
Methods Fairly easy to implement Can be highly versatile, especially if well designed and associate class and method are self-documenting, providing ease-of-use Although there may be several instances of an object being used, the methods associated with those objects generally occupy a single space in memory providing for efficient performance.
Inheritance Takes some thought to do well Somewhat versatile, but to be well-designed, the subclasses must relate well to the superclass Should provide an additional layer of efficiency since some methods can be shared by multiple classes
Polymorphism Somewhat difficult to implement well If well designed, polymorphism adds a layer of versatility over just plain inheritance: The consumer has less concern about the specifics being implemented by the method being called Polymorphism requires run-time identification of objects to determine what method should actually be executed, and this factor can reduce performance, however depending on the compiler, it cannot be assumed that polymorphism always affects performance negatively.
Generics Moderately difficult to use and takes careful design to implement well Can be highly versatile; When designed correctly, source code that is agnostic to object types can be reused by multiple objects Sun's Java implements generics via parameter passing of the generic type whereas C++ uses a macro-based mechanism to replace the type in the original generic source. In this fashion, the Java version of generics produces a smaller footprint than C++.
Mixins/Modules Not difficult to implement, but careful thought is needed to make highly reusable Can be highly versatile, providing common methods to a few classes or instances, or providing a broader capability akin to aspects an interpretive late-binding language and duck typing] don't bode well for performance compared to other methods
Aspects Can be difficult to implement well Not very versatile, focused primarily on cross-system features From a coding perspective, can be highly efficient; From a system execution perspective, performance is dependent on implementation of aspect-capability

Encouraging Reuse

With all the various techniques available, code reuse is not universally embraced, either academically or commercially. CIO Strategy Center recommends 4 key actions to encourage code reuse in a company:

  • Build a business case to get buy-in from the organization's business leaders.
  • Establish repeatable, practical processes which reduce the potential roadblocks in developing reusable code.
  • Focus on ways the design process can facilitate code reuse.
  • Shift the business culture to a reuse mindset.

From an academic perspective, encouraging code reuse can be tricky, especially given concerns about maintaining academic integrity. One way education institutions are doing both is through encouraging students to participate in open source projects.

Conclusion

Software reuse can take many forms, with those forms offering many levels of complexity and utility. With careful planning and thoughtful design, software reuse has the potential to speed development time and improve software reliability.

References