- Organizational Standards and Conventions
- Putting the "Engineering" in Software Engineering
- Working with Logging and Tracing
- Project and Packaging File Structure
- Unit Testing Requirements
- Code Completion and Review Process Requirements
- Communicating the Vision the Wiki Way
- Conclusion
- Links to developerWorks Articles
- Reference
Putting the “Engineering” in Software Engineering
Sometime last year, I heard a well-respected colleague, Tom Alcott, use the phrase, “the use of rigorous software engineering principles” during a presentation of his I was attending. This struck a chord with me because it succinctly described a concept that I had been struggling with for a long time. It seems that day after day, I and others keep chanting this mantra of, “rigorous software engineering principles,” looking for the nirvana of software engineering, when the reality is that reaching perfection in application development is as hard as it is in the metaphysical world.
So how do you reach it? You reach it the same way that other engineering disciplines ensure that things are built and perform correctly—by putting in place standards, codes, and conventions and ensuring that people follow those rules and guidelines as much as possible. Then you physically inspect to be sure things are done right, making changes in the standards as necessary.
You might assume that the recommendation is for architects to actually look at the code that the development team has delivered. If you are thinking this then you are absolutely right. Later in this chapter I talk about the code review process; however, for now understand that this process is not as intimidating as you might expect, but it is something that is non-negotiable. Think of it this way: You wouldn’t buy a house without an inspection would you? Then why accept mission-critical software without an inspection?
More often than not, the software you are building costs more than your house, or at least more than mine cost, yet it is almost always accepted on faith that it will run reliably. Maybe it’s because the developer’s own money isn’t the money being spent. I often think that if they did consider it as their own money, IT shops would run quite differently.
Many reasons exist for promoting this type of standardization within your development team. Working on large or even small project teams, we’ve all been in situations where looking over or even modifying the code of another team member has been necessary. Testing, reviewing, and debugging code that doesn’t follow any standards (or your standards) can be a daunting task, even for the best of programmers. How many times has a piece of code been scrapped and rewritten for lack of understanding? I have to admit that I have done this more often than not on projects or with particularity complex APIs.
Because each project is different with widely varying requirements, separating standards from environmental factors and creating a separate document for the development environment setup and configuration is necessary. The types of documents of the most interest are
- Coding standards and conventions: This is the core of the discussion within this chapter—to list out organizational standards and guidelines that can be followed across multiple projects.
- Development environment and setup: Setting up the developer’s desktop is usually separate from the initial standards. Each project may have its own set of rules so this document may be specialized for larger projects. Usually there will be a standard set of processes for the setup and use of things like version control.
- Component completion and review process: This area may be part of the initial standards document; however, there is a lot of room here for customization on some projects. Review processes may be more rigorous on those projects that have higher visibility or that have more riding on their success. The team may make a conscious decision to reduce or increase flexibility within the review process.
- Specialized documents: Specialized documents may be created by different teams within the organization. For example, the security team should create a set of documents that outlines good and bad security programming practices. They may want you to test and review for cross-side scripting attacks within your web pages. Accessibility and usability standards may be put into place by HR or UI teams. Ensuring that all web-based applications are useable by screen readers and other tools is important in today’s working environment.
Many organizations will have additional documentation standards for their projects. These additional requirements usually revolve around how the project fits within the physical environment, so they are beyond the scope of this book; however, you should make an attempt to fit these recommendations into your own environmental standards.
Standards Documentation
Standards documentation should consist of enough information that developers don’t have to struggle to understand how to use the standard; however, the documentation cannot be so rigid that it tries to encompass every possible situation. Although there is much that can be included, a simple list should include the following topics:
- Naming standards and conventions
- Internal documentation
- Logging and tracing
- Exception and error handling
- Project and packaging file structure
- Use of external libraries
- General design guidelines
- Unit testing requirements
- Code completion and review process requirements
This chapter focuses on these topics and provides some guidance on how to handle them. Notice that this list should be geared toward your specific project or organization. Many industry- and language-specific standards should be incorporated into your projects. These can be listed or referenced into your standards whenever necessary, but you don’t need to reinvent the wheel. Basic Java best practices and conventions such as the use of string concatenation, or putting more than one command on a single line, are tasks that should be handled during the code review process. They can be documented in an extension document or better yet on a Wiki so that they can continue to evolve with your organization. Take care however, this area could potentially grow to be unwieldy if not managed appropriately and any documented standards then lose their potential do to good.
Naming Standards and Conventions
I don’t like to go overboard on any standards, but naming standards are something that can be well defined across an entire organization. For the most part these standards follow the Java standards issued by Sun for Java development. Many of the examples in this book follow the usual Java standard conventions. Common Java naming standards should be followed wherever possible. Of all these types of standards, I think that package names are the most important and will have the most impact on your development effort later in the project. Naming standards are an example of a convention, where some guidance is given, but flexibility is also required in how the convention is put into use within the project.
Packages
Package naming is something that should be well defined across your entire organization. If you have never seen a project where no standards were put in place, then you are lucky. Usually you will just end up with naming structures all over the place, with many developers using the defaults that were provided by the tooling or whatever code they copied their initial program from. When naming a package for classes within your application you might begin with the following structure:
Model: com.<domain>.<department | team>.<appname>.model Data Access: com.<domain>.<department | team>.<appname>.dao Business Function: com.<domain>.<department|team>.<appname>.<function area> Servlets: com. <domain>.<department | team>.<appname>.servlets Portlets: com. <domain>.<department | team>.<appname>.portlets Test Classes: com. <domain>.<department | team>.<appname>.test
You can really use whatever structure makes sense for your app. Additional objects may follow a similar naming structure when necessary:
com. <domain>.<department | team>.<appname>.beans com. <domain>.<department | team>.<appname>.util com. <domain>.<department | team>.<appname>.objectmanager
JSPs should also follow a package or folder structure within the Web Archive file. This structure would usually be something similar to
com. <domain>.<department | team>.<appname>.jsp
I think it is easy to go either too short or too long on package names, so keep your eye on what developers are trying to do within their structure. You can also add subpackages as you deem necessary. For example, in some of my code I may add a subpackage to determine what type of presentation this object is representing, such as
com. <domain>.<department | team>.<web | portal | mobile><appname>
The main point here is to put some guidelines in place before any code starts to be developed. After a package structure is laid down it is very difficult to refactor your code libraries to adhere to a new structure. A well defined package naming structure also helps in monitoring and trace components at runtime.
Many of these standards are a mixture of what I have found works in projects and the classic Sun Java standards available at http://java.sun.com/docs/codeconv/. Most standards do not go into this level of detail with identifying package names, but specifically for your organizational standards you should for the sake of consistency across teams and projects.
Classes and Interfaces
Class and interface names should be nouns, in mixed case with the first letter of each word capitalized. For example
class MyUniqueClass
Some general guidelines for class naming include the following:
- Keep class names simple and descriptive.
- Use whole words.
- Avoid acronyms and abbreviations (unless the abbreviation is much more widely used than the long form, such as URL or HTML).
Functions and Methods
Methods names usually describe some action taking place. As such they should be written as verbs describing what action they are going to perform. Methods are written in mixed case with the first letter of the first word in lowercase and the first letter of each additional word capitalized.
calculateInterest(); registerAccount(); getCustomerList();
Variables and Constants
Like methods, all variables should be in mixed case with a lowercase first letter.
int startPosition; char middleInitial; String accountNumber;
Variable names should not start with underscore or dollar sign characters in most cases. Sometimes underscores are allowed for internal uses, or if a code generator is used, such as the JET engine (sometimes known as JET2), within the eclipse framework.
Constants should be written as all uppercase with the words separated by an underscore.
static final String LDAP_HOST_NAME = "myldap.ibm.com"; static final String LDAP_HOST_PORT = 389;
Variable names should be as short as possible yet still be meaningful to the use of the variable. One character variables, except for simple counters or very temporary variables, may be considered, but even then a more descriptive name of what the counter is for may be necessary.
Java Server Pages
JSP page names should follow the same standard as class names using a mixed-case approach with the first letter of each word capitalized.
ViewCustomerList.jsp UpdateCustomer.jsp TransferAccountBalance.jsp
You should adapt a strategy for the initial JSP for any particular application if it is necessary. For example, if your application has a specific JSP that is always shown first, then that JSP should be named Main.jsp or Index.jsp, or something equally descriptive to assist with identification. JSP names should be kept as simple as possible, but again keeping the description to something that is recognizable to the casual observer.
Internal Documentation
Internal documentation refers to comments that are actually embedded within your code. This type of documentation serves several purposes. It typifies good programming practices. Being forced to write comments means you have to think about what your code is doing and how to describe it to your fellow programmers or consumers of your code components. It also helps in code reviews or when others have to make a change to code that you originally developed.
Internal documentation is almost always an afterthought, right alongside removing all the System.out lines from your code. Often what comes to mind toward the end of the project is to add comments for the generation of Javadoc for your application or system layer. But internal documentation is way more than just an abstract set of HTML documentation files. Many of the tools available today can leverage these comments within the environment to assist other developers who may be using your components or middleware libraries Figure 2.1 shows how your documentation can be displayed within Rational Application Developer.
Figure 2.1 Internal documentation
You can imagine the benefit this could bring to a layered architecture where the upper layers are fully dependent upon lower layers to access data and systems. Being able to access the API documentation would be of great benefit.
The information published by Sun about using Javadoc can be extensive, outlining how to place your comments within the code. There may be a need to ensure that some development teams go to an extreme especially when a team is creating an API that will be used by other projects and teams. Many tools are available that can do an analysis on comments and recommend changes; as programmers are forced to redo some of their comments they will become more experienced in inserting documentation on the fly. For example, class specifications should include an executive summary, state information, OS hardware dependencies, allowed implementation variances, and security constraints.
Other sections may also be useful to users of this class. For example, when a method can accept null values, it should be explicitly documented so that others understand how to use it effectively. This level of detail would definitely be useful to any teams using this class.
For our purposes we are sticking to some of the basics. Rigid software engineering standards aside, I think honestly if we can get this level of detail we should consider ourselves lucky.
Class Header Information
Class headers are at the beginning of your class file. They should also include any include sections listed within your class. The comments should outline the use of the class without being too verbose.
/** * Servlet implementation class for Servlet: JavaLogging * * @web.servlet * name="JavaLogging" * display-name="JavaLogging" * description="This servlet displays some sample data and * illustrates the Java Logging API use within WebSphere" * <p> * @web.servlet-mapping * url-pattern="/JavaLogging" * <p> * @author Joey Bernal * @version 1.0 **/
Including the @author directive can allow some static analysis tools to break down the errors that are found down by author, but it may also tell people new to the project who to ask if they have questions about some piece of code. Many frameworks need to provide information on how to instantiate a class within the framework. For example, through the use of some factory method that is provided which manages class instances. This seemingly trivial piece of information can make or break how others use your library classes or if they avoid them all together.
Class Methods
For methods, a minimum set of information should be included in the documentation. This includes
- A description of the method
- What parameters the method takes
- Any exceptions that are thrown
- What the method returns
/** * Returns an array of Customer objects representing the rows in the * result set from the list of customers in the database. * <p> * @param none * @return Customer[] */ public Customer[] getCustomerList() {
You might also include additional information like known bugs, or any pre- or post-conditions that may be necessary for this method.
Getters and Setters
Getters and setters are specific, single-use functions that allow you to store and access data within your value objects or domain classes. As such they require similar documentation to what you would provide for functions and methods.
/** * Returns the value of column CUSTOMERS_CONTACTLASTNAME. * in the row represented by this object. * * @return Object * @throws SQLException */ public Object getCUSTOMERS_CONTACTLASTNAME() throws SQLException { return select.getCacheValueAt(rowNumber, 3); }
Inline Comments
The mention of inline comments probably brings to mind commenting all the constants and variables within the class file, which is correct. Every variable declaration within your code should have a comment.
Guidelines are available for using inline comments (//) as opposed to using C-style comments.
/* * This is what I mean by C style comments * */
You should research these guidelines and determine how important keeping to them is to your effort and how much detail is necessary.
You can use inline comments to document the why as well as the how certain things are done a specific way. In development you can make an infinite number of choices. Understanding why a specific choice is made is very important in understanding the whole component or application. Of particular importance are areas that would be tricky for another reader to understand. This scenario is often difficult for the developer to judge, but think about times you have gone back to your own code and tried to understand what you were trying to do. Anytime you use a control structure such as a loop, if, or try statement is a good time to consider putting in a comment about that structure and its use.
Remember that for commenting to work you have to remove some ego from the situation. During a code review, discussion may occur about a particular approach. This should be an honest discussion with possible debate, but developers should not feel that their every move is being questioned. This will cause more problems later in the project as developers become more resistant to the code review process.
Another school of thought proposes that when inline comments are used to explain some tricky piece of code, that the code itself should be simplified or refactored in some way. This is a valid concern during code review, especially when a particularly nasty set of code is uncovered and becomes indecipherable, even with embedded comments. Decisions have to be made regarding the manageability of that section of code versus the delivery of the overall project.
Logging and Tracing
Logging and tracing is an area of great debate in today’s applications. Many different approaches and flavors of logging are available and even more opinions exist as to which approach is the best. Again you should take a one-size-does-not-fit-all approach and understand that different projects require different approaches.
I believe that simpler is better; here are a few key items to look for in a logging approach:
- The approach should be as easy as possible. Developers need to be as verbose as possible with their trace messages, so ease of use is of primary importance.
- You need a good set of different logging levels. Twenty different levels aren’t needed; a half dozen will probably do. Again, the goal is to minimize confusion or indecision with any of the developers.
- There needs to be an easy way to change logging levels at runtime. This factor is important because in case of a problem you don’t want to have to restart the server, which may make the problem go away.
Wrappers have always been the popular approach when putting together a logging strategy, even to the extent that development teams try and wrapper industry standard logging packages to simplify the approach for their team. I haven’t understood this logic completely; our developers are expected to understand complex libraries, such as Spring, Hibernate, JavaServer Faces, and so on, yet people think they might struggle with log4J? I make light of the situation but you can see how misguided the effort can become.
I have to admit that I have changed approaches over the years and have looked at the pros and cons of log4J, WebSphere’s JRAS, Apache Commons Logging, and others. Logging in the WebSphere environment can get complicated, with class loading conflicts being at the forefront of problems that people encounter. Luckily logging can be simplified to a short set of guidelines that can be distributed and enforced across all your WebSphere projects. Starting with WebSphere version 6, the JDK 1.4 version of the logging API is being used within the environment. This approach makes use of the java.util.logging package, which allows consistency and, of course, simplicity in your applications. Another benefit is that there are no additional jar files that are usually sprinkled throughout your packages. Interestingly enough, I sometimes review code that has not one, but two different logging libraries sitting in the lib directory. Often neither is used very effectively.
System.out
I cannot stress enough that System.out messages are not to be tolerated within any code deployed to WebSphere Application server. I would like to be more open minded about its use, but have found that there is no good place to draw the line as to when it would be appropriate or not to use. Developers will always use the System.out log just to get some quick information about a running application; however, the logging approach recommended here is just as quick and can help avoid many of the problems that sometimes creep into production.
I have to admit that as a developer I am guilty of using and leaving System.out messages within my code. When I put on my administrator hat, however, I am merciless about making developers remove any last vestige of this type of logging and instead opt for clean logs that are more easily read by operations. System.out logs are not easy to manage. You can’t remove items from the log to make them easier to read, without just turning off the entire log. Also, the System.out log may not be buffered, which could impact performance if you are doing a lot of logging within your application.
Using the Logger
Accessing the logger within your class is simple. Importing the java.util.logging packages provides the access you need to create and use a logger within your class.
... import java.util.logging.Level; import java.util.logging.Logger; ... public class CustomerListServlet extends javax.servlet.http.HttpServlet { ... private Logger logger = Logger.getLogger("com.cm.web.sample.logging.CustomerListServlet"); ...
In this case I use the package and class name of the class where I am using the logger. I prefer this approach across my entire application. The reason behind using this approach will become apparent later when I talk about the admin console and show how to turn on logging.
You can log using the logp method. Multiple ways exist to log messages, including several convenience methods; however, logp is the approach recommended by WebSphere.
public void logp( Level level, String sourceClass, String sourceMethod, String msg, Object param1)
Log a message, specifying source class and method, with a single object parameter to the log message. If the logger is currently enabled for the given message level then a corresponding LogRecord is created and forwarded to all the registered output Handler objects.
The parameters are as follow:
level |
One of the message-level identifiers; for example, SEVERE |
sourceClass |
Name of class that issued the logging request |
sourceMethod |
Name of method that issued the logging request |
msg |
The string message (or a key in the message catalog) |
param1 |
Parameter to the log message |
The primary advantage of using logp is that it allows the use of WSLevel logging levels within your application. It also allows message substitution parameters, and class and method names in the log entry, which are important for knowing where the problems may be occurring within your code. You can actually log a message with the following:
if (logger.isLoggable(Level.SEVERE)) { String str = "This is a SERVRE message"; logger.logp(Level.SEVERE, getClass().getName(), "doGet", str); }
Performing a check to see whether the logging level is enabled using the isLoggable method is also recommended. Doing so helps to avoid generating logging data that will not be used. In this case you avoid generating a String object that will never be used, but that will have to be garbage collected, possibly degrading system performance.
Logging Levels
The Java Logging API provides several levels for outputting different types of information to your logs. WebSphere Application Server provides a couple of additional levels on top of these levels. In total there are 10 logging levels in addition to the management levels OFF and ALL. These management levels would not normally be levels that the development team would have to worry about. The WebSphere InfoCenter gives some definitions for each of these levels and what they would be used for:
OFF |
No events are logged. |
By default the following log levels are output to the System.out file:
Level |
Type |
Description |
FATAL |
WsLevel |
Task cannot continue and component cannot function |
SEVERE |
Level |
Task cannot continue, but component can still function |
WARNING |
Level |
Potential error or impending error |
AUDIT |
WsLevel |
Significant event affecting server state or resources |
INFO |
Level |
General information outlining overall task progress |
CONFIG |
Level |
Configuration change or status |
DETAIL |
WsLevel |
General information detailing subtask progress |
By default the following log levels are output to the trace.log file:
Level |
Type |
Description |
FINE |
Level |
Trace information: General trace plus method entry / exit / return values |
FINER |
Level |
Trace information: Detailed trace |
FINEST |
Level |
Trace information: An even more detailed trace; includes all the detail that is needed to debug problems |
ALL |
All events are logged. If you create custom levels, ALL includes your custom levels and can provide a more detailed trace than FINEST. |
You can decide for yourself which levels make the most sense for your team or project. The preceding list may include too many levels for most developers to follow appropriately without either their having a lot of experience, or having several examples of different logging levels available so they can choose the right level to use. I suggest narrowing down the levels to something more reasonable. The following list defines a reasonable set of logging levels that can be described with concrete examples:
Level.SEVERE Level.WARNING Level.INFO Level.CONFIG Level.FINE Level.FINER Level.FINEST
By default WebSphere usually sets all loggers to the INFO setting. Because any level includes messages that are at a higher level this will also include messages of type WARNING and SEVERE. Generally you want this type of information to display anyway so you can be informed of something bad happening within your system.
Logging of type SEVERE is reserved for big problems within the application. These would generally be exceptions that are thrown within the application. Exception handling is covered in the next section, but the actual logging of exceptions is one of the most common uses of a logging framework.
}catch(SQLException SQLex) { if (logger.isLoggable(Level.SEVERE)) { logger.logp(Level.SEVERE, getClass().getName(), "doGet", "Servlet failed: " + SQLex.getMessage()); logger.logp(Level.SEVERE, getClass().getName(), "doGet", SQLex.getStackTrace().toString()); } //something bad happened, so display error jsp. nextJSP = "WEB-INF/com.cm.web.sample.log.jsp/Error.jsp"; }
The preceding example not only logs the exception but also provides the end user with an error page that allows the user to continue with the application or try again later.
Logging of type INFO is generally simple. As an example it can be used to tell you that an object has been instantiated or initialized. The INFO level could also be used to show progress on long-running tasks or processes. This level does not generally need to be a type of problem, but can track progress within the application or system.
if (logger.isLoggable(Level.INFO)) { logger.logp(Level.INFO, getClass().getName(), "init", "initalized"); }
You can specifically set the CONFIG logging level to see whether any parameters are set incorrectly within your application. Usually external systems such as databases or data sources, LDAP, or web services are different for each environment. It is not unheard of for parameters to not be changed as an application is moved through the deployment chain from development to QA to Stage and finally to production.
if (logger.isLoggable(Level.CONFIG)) { logger.logp(Level.CONFIG, getClass().getName(), "initializer", "DataSource Name", dsName); }
Providing specific logging for these parameters can be very useful in situations where you are not sure that something is set correctly.
Method-Level Timers
Trace-level logging should include some level of timing data, at least at the method level. Complex parsing or calls to remote or external data sources should also include their own timing data. Is this extra work? You bet! Is it worth it in the long run? Absolutely! I can’t count the number of times I wish I had this type of data. Take a little care to ensure that this data is only calculated when a very detailed level of tracing is turned on, otherwise you are building slow production performance into your system.
Generally you should use a tracing level of FINE for this level of logging. You can make good use of the tracing levels FINE, FINER, and FINEST, depending on the problem that you are facing. Timing traces are an example of FINE level tracing. Timing traces are used to see how fast requests are processing through the application. At the start of each method you can initiate some values and output the fact that the process has entered the method.
long start = 0; if (logger.isLoggable(Level.FINE)) { start = java.lang.System.currentTimeMillis(); logger.logp(Level.FINE, getClass().getName(), "init", "Entering: " + NumberFormat.getNumberInstance().format( start )); }
This trace would be output to the trace.log and would look something like the following:
[12/16/07 15:16:39:515 EST] 0000002b CustomerListS 1 com.cm.web.sample.log.CustomerListServlet doGet Entering: 1,197,836,199,515
At the end of each method a follow-up entry would calculate and display the result of timing data.
if (logger.isLoggable(Level.FINE)) { long finish = java.lang.System.currentTimeMillis(); long total = finish - start; logger.logp(Level.FINE, getClass().getName(), "init", "Exited: " + NumberFormat.getNumberInstance().format( finish )); logger.logp(Level.FINE, getClass().getName(), "init", "Time spent: " + NumberFormat.getNumberInstance().format( total ) + "milliseconds"); }
This code would display data in the trace.log similar to the following:
[12/16/07 15:16:39:750 EST] 0000002b CustomerListS 1 com.cm.web.sample.log.CustomerListServlet doGet Exited: 1,197,836,199,750 [12/16/07 15:16:39:750 EST] 0000002b CustomerListS 1 com.cm.web.sample.log.CustomerListServlet doGet Time spent: 235 milliseconds
You have lots of opportunities to improve the set of standards your organization requires. For example, you may want to build auditing functionality into your applications. This feature may provide for an AUDIT level message to output to the logs whenever a process or method takes longer than some set threshold. This is especially important when you are dependent on a back-end system such as a database or web service. You can log the event along with the query itself to understand when and where some problem took place. I don’t recommend using this approach everywhere because just generating the time values and performing the comparisons could become expensive if used too extensively. However, this approach may be good for potential hot-spots within the application where you may expect some performance issues, and will certainly help when your project moves into performance testing.