Writing Code Based on the Two Best Practices


If you inspect the Jakarta sources, basically all of the code falls into these two best practices. This is a very sweeping statement, because the watchful reader will wonder about serialization, persistence, and instance creation. Well, those algorithm constructs do exist as well, and they will be covered in later chapters. However, the two best practices are used even in those algorithm constructs. It is amazing that the Jakarta team writes code so consistently. It proves the point that to create high-quality , stable code, you do not need to know a large array of programming techniques, just a few techniques and their associated variations.

Of course, there are other types of coding techniques beyond the ones you use with the best practices. Such coding is called logic coding, which is when the program is coded to do something like add a user to the database. The best practices defined are skeleton programming constructs. Consider using the best practices to be like building a house. The skeleton of the house is the individual walls, which are made of wood or concrete. The logic of the house is the color of the walls, type of flooring, and type of finishing. A skeleton of a house is essentially identical in every house on this planet. The difference lies in the logic as far as how they are placed in relation to each other and how the interior appears. Of course, the skeleton might be optimized to use more modern building constructs, just like you might use more modern best-practice constructs.

The rest of this chapter outlines the individual common sense-based programming logic constructs. We call these constructs Commons Sense because they are part of the common sense found in the Commons projects and other Jakarta sources.

Commons Sense: Package Names

With literally millions of lines of source code in the Jakarta project, it would seem futile to attempt to figure out what the sources do. In fact, though, it is relatively simple to do because the coding standard is more or less consistent. There are small exceptions to this rule, but it is very easy to figure out what those classes or interfaces mean. When you write your own code, it is absolutely imperative that you use a good naming standard. In fact, ideally you would adopt the Jakarta naming technique. Doing so would make it easier to integrate your sources and the Jakarta sources. Package names have three major sections. Let's take apart the package name in Listing 2.7. Here are the three sections of the package name :

  • org.apache : This gives the identity of the entity that distributes the application. This could be a corporation or organization. Typically, the identity of the entity is a two-token identifier, which is typically the domain name of the entity. In Listing 2.7, it is the non-profit organization Apache.

  • commons : This is the main identifier of the project. Typically, the identifier of the project is the overall project name or an important part of a project. In Listing of 2.7, the name of the project is Jakarta Commons.

  • cache : This is the subsystem project identifier. Any tokens used beyond the main identifier of the project represent a subsystem.

Typically in Apache Jakarta projects, the naming of the package is consistent and each section has a significant purpose. For example, in some projects there is no sub-system identifier. In those cases, it means that the root-level classes or identifiers are fairly important and used throughout the rest of the project. The concept of a root and dependencies is used when a project has subsystems. For example, the package name com.companyname. project.sub-system is used to define classes and interfaces for the implementation package com.companyname.project.sub-system.implementation.

The implementation package is a child of the definition package name. The parent-child relationship modularizes the application or component in an organized manner.

Commons Sense: Identifying Class Types

Using consistent identifiers to identify various pieces of your architecture makes it simpler to figure out what the individual pieces do. Following is a list of class types in use in the Jakarta sources. Each class identifier will be presented with the token [identifier] prefixed or followed by a specific identifier. The specific identifier identifies the role of the class. The following list contains examples of how the Jakarta group identifies its classes, packages, or interfaces (note that the identifiers presented may only be part of an identifier; e.g., AttributeException , and the important part is Exception ):

Type: Exception - [identifier] Exception

This type of class is an exception class used to throw an exception. Exceptions are thrown to signal that something went wrong. The typical structure of an exception class from the Jakarta Commons sources is shown in Listing 2.20.

Listing 2.20
start example
 public class BufferOverflowException extends RuntimeException { private final Throwable m_throwable; public BufferOverflowException() { super(); m_throwable = null; } public BufferOverflowException(String message) { this(message, null); } public BufferOverflowException(String message, Throwable exception) { super(message); m_throwable = exception; } public final Throwable getCause() { return m_throwable; } } 
end example
 

Shown in Listing 2.20 is an exception that subclasses the Java standard exception RuntimeException . Also used as exception classes are the classes Exception and IOException . Using a quick inspection, the different examples of exceptions within the Jakarta Source do not seem to follow any pattern on when to use a specific exception type.

A closer inspection reveals the rule that an exception is subclassed from the class RuntimeException if the exception is related to a component error. It is supposed to indicate a fundamental error in program state, such as reading corrupted data. The corrupted data cannot be read no matter how intelligent the routines are.

When an exception is subclassed from the class Exception , then the exception is related to a problem in the program. For example, a component exposes five elements to be read, and the consumer wants to read the sixth element. The component did everything correct and the consumer insisted on reading an element that does not exist, which means that the consumer is doing something wrong.

A general note is that when you subclass an exception, it is advisable to see if there already exists a similar exception in the Java 2 Standard Edition (J2SE) runtime.

You saw the essentials of an exception class in Listing 2.20. The class BufferOverflowException has three constructors and the method getCause . The different constructors are used to specify the type of error:

  • The constructor without any parameters is the default error generator, which generally should not be used since that exception will not generate any meaningful information.

  • The constructor with only one parameter is an exception generator intended for one system. More about this in a moment.

  • The last constructor is intended for an exception handler that redirects another exception.

Redirecting exceptions is a useful mechanism if the subsystem has other independent subsystems. Consider Listing 2.21.

Listing 2.21
start example
 public void anotherSubSystem() { throw new RuntimeException( "error in subsystem"); } public void mySubSystem() throws RuntimeException { try { anotherSubSystem(); throw new MyException( "oops"); } catch( MyException ex) { throw ex; } } 
end example
 

In Listing 2.21, the method anotherSubSystem represents some other independent subsystem used for illustration purposes. The method mySubSystem represents the local subsystem. Both subsystems will generate exceptions. The consumer of the local subsystem will implicitly use the subsystem created for illustration purposes. The problem arises when there is an exception. When the other subsystem generates an error, the consumer of the local subsystem will put the fault at the local subsystem. The truth is that the local subsystem did everything right, but it is being blamed because the consumer cannot distinguish between the two exceptions and who is responsible.

One solution to distinguish the exceptions is shown in Listing 2.21, where the local subsystem generates the exception MyException to indicate that the error occurred locally. If the error occurred in the other subsystem, then that error will be thrown to the consumer directly. The problem with the solution is that the consumer knows the error occurred in the other subsystem but does not know why. There is no context on which step in the local subsystem caused the error in the other subsystem. Listing 2.22 is an attempt to distinguish the steps.

Listing 2.22
start example
 public void mySubSystemComplex() { try { anotherSubSystem(); } catch( RuntimeException ex) { throw ex; } try { anotherSubSystem(); } catch( RuntimeException ex) { throw ex; } } 
end example
 

In Listing 2.22, the method mySubSystemComplex represents a complex subsystem. In addition, the method anotherSubSystem is called in two places. The two different places are different line numbers , but one could imagine that in a larger system, different classes or methods would be used. In each case of the method call of another subsystem, an exception is caught and the context can be determined. The conditions for the first method call to the method anotherSubSystem may be very different than those for the second method call to the method anotherSubSystem .

The problem with the solution provided in Listing 2.22 is that the code in the local subsystem has become hard to read because the different places where an exception block is typed out and lost are the context of the error in the local subsystem. A compromise solution that improves readability and attempts to pinpoint the error is shown in Listing 2.23.

Listing 2.23
start example
 public void mySubSystemComplex() { try { anotherSubSystem(); } catch( MyException ex) { throw ex; } catch( RuntimeException ex) { throw new MyException( "Place1", ex); } } 
end example
 

This time, in Listing 2.23, the two-parameter constructor is used to indicate where the error occurred, and the original exception is stored as a reference within the newly generated exception. Then, at the higher level, the main exception handling mechanism can decipher what errors occurred. The details of how to log these errors are explained Chapter 8.

Type: Test - [identifier] Test [identifier] TestSuite [identifier] TestClass [identifier] TestCase

The classes or interfaces with the identifier Test indicate that these classes are responsible for testing specific classes or interfaces in the package, subsystem, component, or application. The Test class is part of the JUnit test suites, discussed in Chapter 11. For now, we'll explain that the identifiers Test and TestClass indicate a unique test. In addition, the identifiers TestSuit and TestCase indicate a full test scenario that will call multiple unique tests.

Type: Default - [identifier] Default

A class will typically use the Default identifier when the class is defined to be a default implementation of an interface. For example, when you're implementing an interface, there are defaults that could be used in several places. Instead of repeating the same code, a default class is used. If you relate this back to the Commons Composite best practice, discussed earlier, you will see that you might create a default class for an action item. Consider, for example, Listing 2.24, which is a default implementation for the Commons Project attributes , which is a project used to read metadata from Java classes.

Listing 2.24
start example
 package org.apache.commons.attributes.impl; import org.apache.commons.attributes.Attribute; public class DefaultAttribute implements Attribute { private String name; private String value; public DefaultAttribute(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public String getValue() { return value; } } 
end example
 

In Listing 2.24, the class DefaultAttribute is a simple implementation of the interface Attribute . The constructor DefaultAttribute assigns the internal data members , and the getters (methods that support the Java Bean property notation) getName and getValue retrieve the set values. The class DefaultAttribute is not fancy. It carries out only the minimum of operations, which is the intent of a class' default implementation. A default class is not a structural class because a structural class is not a complete class. The default class is a complete class with a minimum of implementation details and can be extended or directly instantiated .

Type: Algorithm: [identifier] Builder [identifier] Compiler [identifier] Parser , etc.

A class or interface with the keyword Builder, Compiler, Parser, or any word that is a verb and has an "er" ending, processes data in a more sophisticated computer algorithm than the default Java SDK implementations . Let's explain the concept of a more sophisticated computer algorithm a bit. A simple algorithm is the assignment of data from one object to another, or the testing of an object to see if the object validates according to some conditions. A more complicated algorithm is when the data is not simply assigned, but collected and then analyzed . The analysis could involve tokenization or ordering of the data in a tree. These types of algorithms tend to be computer science-oriented and easy to isolate in their own package.

In the Apache Jakarta sources, the computer science routines are distinctly marked by the verbs with an "er" ending. Notice that these routines are encapsulated within a class or a package. This is a good coding practice because it means that when bugs or major changes have to be carried out, the changes are localized to a single class or package. Discussing the various computer science routines is beyond the scope of this chapter. However, in later chapters of this book, we will discuss how to use these classes in your own development.

Type: Handler - [identifier] Handler

The class type with a Handler identifier would seem to fall into an Algorithm -type class type due to the purpose and the identifier used. That is correct; the Handler class type is a specific type of class that deserves its own class-type identification. A Handler class type is used as a callback mechanism. More about this class type will be discussed in Chapter 6.

Type: Source -[identifier] Provider

It would seem that the Provider class type, like the Handler class type, should be an Algorithm class type due to the naming convention. However, a Provider class type is very special because it may or may not be related to computer science. A Provider class type is a class or interface that provides a bridge to another resource. It's a source of data, which is consumed by the user of the provider. Database access classes are a common example of a Provider class type. More about this class type will be discussed in Chapter 7.

Type: Navigation - [identifier] Child [identifier] Parent [identifier] Manager , etc.

The notion of a navigation identifier is very common in various component structures. The navigation identifier is not limited to Child , Parent , or Manager , but could include Sibling , Descendent , and so on. In addition, the navigation identifiers do not need to be restricted to class or interface identifiers. They present an unusual situation where methods can be used as identifiers. Navigation identifiers allow you to construct a complex data structure that encompasses many different objects. Typically, when a class or interface is a navigation type, it indicates that the class or interface is a bridge that combines several references into a useful structure. Navigation identifiers allow you to move from one part of the data structure to another. This idea is used extensively in the XML Document Object Model.

Listing 2.25 shows how to use a class or interface to manage a complex structure. The listing is from the Commons Project Jelly , which is a scripting infrastructure. (Note that the class has been abbreviated for clarity.)

Listing 2.25
start example
 package org.apache.commons.jelly.tags.jface; public class MenuManagerTag extends UseBeanTag { private String text; private MenuManager mm; public Window getParentWindow() { } public void doTag(XMLOutput output) throws MissingAttributeException, JellyTagException { } public MenuManager getMenuManager() { } } 
end example
 

In Listing 2.25, the class MenuManagerTag manages the class MenuManager as well as manages a place in the window hierarchy. The class MenuManagerTag , which is navigational, has navigational methods, as demonstrated by the method getParentWindow . This sort of class arrangement is very common.

Listing 2.26 shows an example of exposing an interface with many navigational methods. The code comes from the Jakarta Commons Project vfs .

Listing 2.26
start example
 package org.apache.commons.vfs; public interface FileName { char SEPARATOR_CHAR = '/'; String SEPARATOR = "/"; String ROOT_PATH = "/"; String getBaseName(); String getPath(); String getExtension(); int getDepth(); String getScheme(); String getURI(); String getRootURI(); FileName getParent(); FileName resolveName( String name ) throws FileSystemException; FileName resolveName( String name, NameScope scope ) throws FileSystemException; String getRelativeName( FileName name ) throws FileSystemException; boolean isAncestor( FileName ancestor ); boolean isDescendent( FileName descendent ); boolean isDescendent( FileName descendent, NameScope nameScope ); } 
end example
 

In Listing 2.26, the interface FileName has multiple methods that allow access to various parts to a file type object. The classical accessor to retrieve a higher-level interface FileName instance is the method getParent . However, what is new, and also very common, are the test functions isAncestor and isDescendent . These functions test if the input parameter is either an ancestor or descendent of the current interface FileName instance, respectively.

The interface FileName, which is common, is primarily composed of accessors, meaning that the methods are used to retrieve specific attributes or to test specific attributes. Using the interface FileName, you can't alter the data structure. Consider the situation where a file structure is read from the Internet base on a Web site. In most cases, it would not be possible to modify this structure and, therefore, exposing writable methods would be silly. If you want to be able to read/write, the most common solution is to subclass the read-only interface and then add the necessary write methods, as shown in Listing 2.27.

Listing 2.27
start example
 interface FileNameWritable extends FileName{ void setParent( FileName parent); void addChild( FileName child); } 
end example
 

Type: Bean - [identifier] Bean

The Bean class type may seem to be a silly identifier, because if a Java class supports a certain method, it is considered a Java Bean by default. However, in the Jakarta sources, when a class is identified as a Bean , then that class is really a full-fledged Java Bean or works with Java Beans. To understand what is meant , consider the context. Imagine building a subsystem that dynamically loads and saves Java classes. To be able to do this, you need to use reflection and metadata. These tasks do not relate to application programming. A developer would not want to add the Java Bean support classes, and he relies on the infrastructure to provide those extra routines, which are marked as Bean in the Common sources.

Letting the Commons provide the support routines is a big advantage of the Commons approach. Sure, it is possible to build all of the support routines, but having the Commons create the infrastructure makes it simpler to software engineer better application. The responsibility of the developer is to know which routines are available. This is why it is necessary to know the class naming convention. Knowing the convention means being able to use the naming convention and make any consumer code consistent with the Commons naming standard.

Type: Utility - [identifier] Utils

The Utility class type is a common type used to provide basic runtime support. For example, in the Commons Project lang you'll see the Factory interface, as shown in Listing 2.28.

Listing 2.28
start example
 package org.apache.commons.lang.functor; public interface Factory { public Object create(); } 
end example
 

The interface Factory in Listing 2.28 is used to instantiate specific objects. Using this interface, you can delegate the object instantiation process to something generic and reusable. The problem, though, is that an object may be instantiated in multiple ways, such as fresh creation or the cloning of an already existing object. In any case, those types of factory instantiations have to be managed by some implementation.

One solution would be to create individual implementations for each instantiation type. Then, the developer would have to memorize the individual instantiation classes and use them appropriately. The problem with this approach is that the developer needs to remember many classes.

A simpler approach to solve the memorization problem is to introduce a single class that encapsulates all of the instantiation types. Then, each instantiation type is exposed as a single method call. This is the approach taken in the lang project and is shown in Listing 2.29. (Note the class has been abbreviated for clarity.)

Listing 2.29
start example
 package org.apache.commons.lang.functor; public class FactoryUtils { private static final Factory EXCEPTION_FACTORY = new ExceptionFactory(); private static final Factory NULL_FACTORY = new ConstantFactory(null); protected FactoryUtils() { } public static Factory exceptionFactory() { } public static Factory nullFactory() { } public static Factory constantFactory(  Object constantToReturn) { } public static Factory prototypeFactory(Object prototype) { } public static Factory reflectionFactory(  Class classToInstantiate) { } public static Factory reflectionFactory( Class classToInstantiate, Class[] paramTypes, Object[] args) { } } 
end example
 

In Listing 2.29, the class FactoryUtils provides five functions to instantiate the interface Factory based on the choice of instantiation process. Within the implementation of the class FactoryUtils there is quite a bit of source code to perform the operation. However, the class FactoryUtils hides the complexity, and is the preferred approach because the client should not be aware of how to instantiate the object. The client only wants to be able to determine the algorithm to use and wants a neat wrapper to the functionality. (For those readers versed in patterns, this is often called a faade pattern .)

Type: Structural - [identifier] Support [identifier] Base

We have already discussed the Structural class type in this chapter. However, we do need to note that the structural class often is labeled with the identifiers Support or Base .

Type: Iteration - [identifier] Iterator

The Iteration class type specifies a class that explicitly loops through a collection of items and retrieves each individual item. While the user of the Iterator class type could do this manually, sometimes it would be tedious to constantly re-implement certain special algorithms. More about this class type will be discussed in Chapter 7.

Type: Collection - [identifier] List [identifier] Map [identifier] Bag [identifier] Set

The Collection class type is a class that is used to manage a collection of objects. The objects can be associated or arranged using different techniques. As a result, many identifiers are used to identify specific types of collections. More about this class type will be discussed in Chapter 7.

Type: Constants - [identifier] Constants

In the C and C++ languages, you can define constants using global variables. In Java, however, you can't because global variables do not exist in the language. To create constants, the Jakarta sources use the concept of Constant class types. Constant class types serve a single purpose: to define variables that will be used throughout the subsystem, application, or component. Listing 2.30 shows an example of a Constant class type.

Listing 2.30
start example
 package org.apache.commons.jxpath.servlet; public final class Constants { public static final String APPLICATION_SCOPE = "application"; public static final String SESSION_SCOPE = "session"; public static final String REQUEST_SCOPE = "request"; public static final String PAGE_SCOPE = "page"; public static final String JXPATH_CONTEXT = "org.apache.commons.jxpath.JXPATH_CONTEXT"; } 
end example
 

In Listing 2.30, the constant class is called Constants . This is typical, but you don't have to name it this. You could call it JXPathConstants . In either case, the class Constants is a good example of a Constants- type class because it is a public class with a final declaration. This means that no one can subclass the class. You would not want anyone to subclass the class Constants because that would confuse the developer and make a simple concept complicated. Declared within the class Constants are a number of string variables declared to be public , final , and static . The technique used to declare a constant string is consistently used in multiple places and is a good idea because the string cannot change and is stored only once in the running application.

The assignment of the variable in Listing 2.25 is acceptable, but you could have also used a static constructor if the declaration had been more complicated. The Jakarta sources contain some examples of more complicated operations in the class Constants . As outlined when we discussed the class Constants , the way to instantiate a constant value is a preferred practice.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net