Over the years, it has amazed me how people respond when talking about standards...
Some people love them. They see the standards as guidance from the experts.
They help focus the efforts of many different people so everyone is working in
harmony. Standards help us follow the best practices of experience without
having to go through the pain of discovery ourselves.
The people who love standards are usually organized team players. They are
looking for direction, efficiency and predictability. They want to cut to the
quick and get the job done. They would rather not spend a lot of time
reinventing the wheel.
On the other side of the fence are the people who hate them. The people who
hate standards see them as oppressive. They stifle creativity. They tell us
what someone else thinks is best and restrict us from doing what we know is
better. Standards hold us back.
The people who hate standards are typically intelligent and independent or
creative thinkers. They see standards as an obstacle in the way of them doing
what they know is right. Many times these opponents have years of experience
behind them and draw from that experience when making their arguments against
standards.
Who is right and who is wrong?
The answer to this question is not cut and dry. Standards do help guide us and
make it easier for less experienced people to do very complicated tasks.
We live with many standards such as what side of the road to drive on, how
we pay for goods and services (cash, credit or debit) and proper ways to greet
someone new. Some of these standards are very strict and, if not followed, can
result in people being hurt or killed. Others are very loose and need to be
altered depending on your current situation.
In my experience, I find standards are a great place to start. In most cases
(roughly 80% - 90% of the time) it is much easier and practical to follow the
standards. When this is not the case, the standards quickly fall out of fashion
and disappear in obscurity.
When you are put into a situation that you have never been in, the only guidance
you have are the standards set by those before you. Why not start there? Then
alter your approach only when you find alternatives that are superior. Often
times, when you find a new and better alternative, it may eventually become a
new standard.
One day, when I was going through one of my mailing lists, I found a post by
a user new to to subject. They were asking about the best way to perform a
common task. They had heard about the standard way to achieve their
goal but they were unsure about why. Was this standard really the
best approach?
A great deal of discussion ensued, including a bit of frustration from the more
experienced members of the mailing list. One person said that this
new user should go with the standard - it's the way everyone else does it. In
this example I agreed, the standard approach was truly the easiest and most
practical. But I disagreed with the reason.
A standard gives us guidance and it is where we should start when we are new.
But it is the people who question the standards that help us improve upon them
and open our eyes when the standard is deluding us from the real truth.
An example:
In the late 19th century scientists believed in a
luminiferous aether
and used the concept to explain many properties of light they did not understand.
Unfortunately, this belief held back scientific advancement. It wasn't until
advancements in scientific theories like Albert Einstein's special theory of
relativity came about that the concept of an aether became obsolete.
It is the people who question the standards that help us make the big advancements.
But we must not forget the value of the standards in our lives. They give us a
starting point. The more standards we have to begin with, the better off we
will be when starting something new. We can do more, faster, better and cheaper
when we use standards.
At the same time we must not forget to question what we are told. I am not
advocating rebellion in every aspect of our lives. Instead, I prefer to follow
as many standards (and rules) as possible and then pick out those standards that
I feel are holding me back from greater achievements. I focus my efforts on the
standards that, if broken or changed, provide me with the greatest benefit.
For those people who feel it is their prerogative to rebel against all standards
imposed upon them, they will find an endless supply of oppression and exhaust
themselves with their struggle. Like a person who takes a giant piece of plywood
and stands it up against a river to block the flow as they cross to the other
side, they will find themselves in a loosing battle. The river will continually
press down on them when all they had to do was turn the sheet of plywood with
the current and let the water effortlessly flow on by - or better yet, hold it
above their head.
Where do we stand to gain from standards?
The list below contains many of the benefits you and your company can gain by
using standards.
- No reinventing the wheel:
-
By using good standards you only need one person to create and determine
how to implement a great idea. Then everyone can use it over and
over with little to no additional effort.
- Better communication between colleagues:
-
If everyone is using the same standards, they all know how other team
members operate. Standardization of language is just an example. If you
standardize on operating procedures, it enables a person to just say a
few words and others immediately understand the context of the situation
and can immediately jump in and help.
- The ability for people to switch between projects quickly and easily:
-
This helps the business and its employees. The business benefits by
having the flexibility to move their employees to where they are needed
without high costs in time and money. The employees gain further job
security since they can do more for the business and, if their job
becomes obsolete, they can move to other positions.
- Quicker training and set up of new employees:
-
When you standardize on the tools people use and how they use them, it
becomes much easier to bring in new people and have them working
productively right away.
I recall one job I had at a local marketing firm. They believed that
their developers should have the freedom to choose whatever tools they
wanted to use and install software on their computer however they wanted.
I also remember that when I started, they paid me for two weeks to wrestle
with an old machine set up by a previous employee. I got nothing productive
done for 2 weeks and I estimate they spent over $6000 for ... nothing.
To top it off, I left in frustration (just about everything there was as
wasteful as this) after working for them for only 2 months so they had
to do it again with someone else.
-
Easier to consolidate the knowledge for best practices and ways to
avoid or fix problems:
-
If everyone is using the same tools and following the same procedures,
when people have a problem it is highly likely someone has experienced
the same problem before and your colleagues can help each other. And if
you are continually updating your standards you can implement changes
that prevent the problem from occurring again in the future.
- Systems are easier to connect:
-
With standards it becomes easier for other departments to communicate
and easier for their systems (either physical or procedural) to
interoperate efficiently.
- Less of a need for highly skilled and expensive labor:
-
If your business is growing, this growth typically increases the
complexity of the inner workings of the business. Such complexity
requires more highly skilled labor. If you set good standards you can
reduce the complexity and lower the skill sets required to grow. This
doesn't have to lead to employees loosing their jobs. If the few highly
skilled employees you have are responsible for creating the standards,
you want to keep them around. But the new people you hire need not be
as skilled or as expensive.
- New products and services or efficiencies are easier to realize:
-
Once you have everything organized, you will find it easier to make
changes and communicate those changes throughout your organization.
If a new product or service is very similar to another it is a lot easier
to develop the product or service by simply copying an already well
organized idea. It is like a factory where creating large number of
similar but highly complex parts is easy to do, costs very little money
and can be done with high reliability. It is also easier to estimate
time lines and costs for the new products and services.
What problems should we watch out for?
The following is a list of the potential problems, either with the perceptions
people have of standards or with the way they are implemented and maintained.
To get the most out of the standards you and your company abides by, you need
to be aware of the pitfalls.
- Standards are the bane of the creative mind:
-
Your most creative employees will often resist standards. Creativity is
thinking out of the box while standards are working within the box. You
need to find a way to encourage your employees to abide by the standards
but always be thinking of ways to improve upon them. Also, everyone
should realize that you cannot follow 100% of the standards 100% of the
time. I like to use the 80/20 rule: if you stick to about 80% of your
standards, it will account for about 20% of your time. And the other 20%
that you deviate from the standards will take up 80% of your time. This
demonstrates how expensive it is to go against the standards but to stay
competitive, it is necessary. If everything you do is standard, you will
find it hard to distinguish yourself from your competitors.
- Standards prevent us from doing what needs to be done:
-
It is true that standards can sometimes get in the way. If you find that
employees are not following a standard, it is time to review the standard.
You may need to alter or remove the standard all together. Don't get
into the trap of using a standard just because "it has always been that
way".
- Standards become stale and no longer relevant:
-
Periodically go back and review your standards. Times change. Technology
changes. Business changes. Consumers want something new. You need to keep
your company fresh and on the cutting edge. Upgrading your standards
is one way to ensure your business stays on top.
- I don't want anyone telling me what to do!
-
When I hear something like this from an employee, I immediately see someone
who is NOT a team player. It takes people who work together to create
truly great products and services. If an employee is going to be rebellious
like this, you should question whether they belong on the team.
|
|
|
|
| Posted by Aaron Bono at | | | |
|
One day, several years back, I went to a meeting with a number of other programmers
to learn more about how software was developed and tools used at our company. It
was a rather large company having hundreds of developers devoted just to the
company's web site. I was eager to find out how I could develop software better
and spend less time on the annoying and mundane things like logging and connecting
to databases.
What I found at the meeting was that the company was standardizing on a language
and the tools we use, but they really had no idea where they were going. They had
no standard code libraries, no standard machine configurations and no advice for
me on how I could do my job better. The IT department lacked leadership. Everyone
wanted to do things their way with little regard to what was best for the company.
And there was a distinct lack of standards everywhere - even the mention of the
word standards raised people's ire.
After the meeting I was frustrated. Two hours spent and nothing to take back to
help me do a better job. I complained to one developer that I wanted to know the
standard way the company recommends you connect to the database. After all,
everyone was supposed to be using Java, WebLogic, EJBs and Oracle. I should be
able to get a boiler plate file from SOMEONE that simply required me to change
the database name, schema name and password and be ready to go.
The response from this developer: I don't want the company telling me how to
program! That takes the fun out of it!
And here in lies the problem. Developers love a puzzle. They want things to be
hard. It is fun. It makes them feel knowledgeable and capable. They feel they
are among the brightest in the world. The difficulty of the job justifies their
high pay and job security while giving them the challenge they seek in life.
But this is not necessary...
It is a matter of time before businesses grow weary of spending so much money on
IT staff when there is a better way. Everyone has been brainwashed into thinking
technology is hard. Software is hard. Computers are hard. Well, the things that
are IT are only hard because we make them that way.
It is time for an IT revolution!
In the book The Inmates are Running the Asylum by Alan Cooper
(which is a very good book that I highly recommend any IT manager read), the
author states that "No company can treat programmers the same as a factory.
Programmers demand continuous attention and support well above that of any
factory." The author is very effectively teaching us how IT is backwards and
structured all wrong but shows that he is still brainwashed into thinking software
is hard to build. This is the one and only point I have read in this book where
I disagree. You can turn your software development into a software factory. It
is not necessary for software to be difficult.
By the way, Alan Cooper does a wonderful job showing the reader where software
development is dominated by the programmer and how this damages the end product.
He explains how to break out of this and add what he calls the "interaction
designer" to make highly superior user interfaces.
There is a way to make IT simpler. I believe, in the near future, we will find
out how to organize and manage IT, in many ways, just like a construction
company handles the design and construction of a building. When you build a
large building, you start by designing with an architect and an interior and
exterior designer. These highly paid experts decide how the building will look
and be built. After this is done, you bring in the lower paid technicians to do
the work.
So what does this mean for the programmers? It means they will become the
technicians. It means they will no longer be able to demand the high salaries.
And it means the business owner will begin to regain control over their business
and not be such a slave to the developer.
NO! you cry. You cannot belittle my role, my intelligence or my importance in
the building of software.
You are correct...
The best and brightest software developers will slowly become (or maybe even have
become) software engineers. They will employ strong standards much like aerospace
and mechanical engineers. They will be the architects and the creators of the new
standards. They will create a simple foundation so that the software developers
(or technicians) can do their jobs simply and easily. The software developers
will be the construction workers and the software engineers will be the architects.
And the business will need very few engineers and many developers.
Why is this approach inevitable? Because it is more reliable and cost effective.
Today, all too many software projects are failures. This costs businesses lots of
money. And developers are expensive. This costs businesses even more money. The
first business that figures out how to cut the costs, increase the likelihood of
success and improve the reliability of their products will propel themselves well
ahead of their competition.
Over the coming months I will expound on this topic and demonstrate how IT
organizations can realize cost savings through this new IT Revolution. And I
will show developers how they can stay on top by adopting standards, using the
best tools and elevating themselves to the level of software engineer.
May your journey be prosperous.
|
|
|
|
| Posted by Aaron Bono at | | | |
|
|
Disclaimer: Note that throughout this article I am use System.out and/or System.err
to send error messages to the user. In a real application you should NOT use
System.out or System.err unless you are working with a command line application
and even then you may not want to use them. Instead you should use a logging utility.
See my previous article on
Java Logging Made Easy
for information about how to replace your System.err and System.out with good logging.
Throughout my years of college, taking programming training courses and tutoring,
I have found a never ending deluge of bad programming practices. Most of these
bad practices stem from the fact that the instructor feels they don't have the
time to teach everything so they teach what they need to. Unfortunately,
every programming course I have taken or tutored for glosses over or simply
ignores a very critical element: error handling.
I once heard that error handling can make up as much as 50% of the code in an application.
Personally I have found this estimate to be a bit high but I would not be surprised
if at least 25% of my code is devoted to error checking and handling. I can
definitely agree that about half of the user interface code is dedicated to
error checking and handling.
For this article I am not going to go into error checking. Error checking is the
code that makes sure required fields are filled in, valid data is typed in by
the user and severe errors do not occur. Error checking is proactive
programming, where the programmer anticipates what could go wrong and
either prevents the actions from being performed or warns the user of their
invalid actions. I see this taught, to a certain degree, in classes and the
methods for doing error checking vary depending on the user interface. I will
show some nice error checking code in later articles when I cover Swing, Struts
or other GUI focused topics.
Error handling is a more reactive approach to dealing with errors in your system.
In a perfect world, you never have to be reactive. All errors are checked and
dealt with properly and the system runs smoothly.
We don't live in a perfect world. Moreover, your boss is probably never going
to give you the time to make the perfect system.
So, even though we want to be proactive and never have to mess with error
handling, it is a necessity. Good error handling helps us uncover bugs we never
thought could occur, discover their source and fix them quickly.
What is an Exception
Exceptions are ways for the code to tell you, "Hey, there is something wrong here!"
In the old C days, functions would return error codes to let you know if something
is wrong. If the error code indicated everything ran smoothly, you could continue
without pause. Otherwise you had to compare the error code with a list of possible
problems to see what really happened. There are a few problems with this approach.
The first problem is that, since you can only return one thing from your function,
you cannot return something useful when errors don't occur (which should be the
majority of the time). If I called a function like openFile it would
return an error code. But what I really wanted was the file handle so I could start
reading the contents of the file. So the function had an additional argument
where the file pointer could be stored. This is more of a hack or work around.
The second problem is that developers often ignored the error. They call the
functions and don't even grab the error code or ask if the function call even
succeeded. It took self discipline to write code with proper error handling.
The third problem was the fact that you had to have good documentation of all the
possible things that could go wrong. Then, in your program, you would have a
case statement of if/else blocks to test each possibility. If you forgot one of
the error codes, you didn't find out until later when everything went awry.
However, with exceptions you list out the things that can go wrong in the method
signature. For example:
public User logInUser(String userName, String password)
throws InvalidUserException, InvalidPasswordException,
LoginNotAvailableException;
Immediately with this syntax you can see the three things that can go wrong when
attempting to log in AND you the programmer are forced to deal with it. The method
also returns the user object which is what you were wanting in the first place.
In Java, you have two options when dealing with exceptions: you can handle it or
you can pass it on. Here are two examples:
// Handling an exception
public void logIn() {
User user = null;
try {
user = logInUser("admin", "password");
System.out.println("Logged in as " + user.getFirstName());
} catch (InvalidUserException e) {
System.err.println("Invalid User");
} catch (InvalidPasswordException e) {
System.err.println("Invalid Password");
} catch (LoginNotAvailableException e) {
System.err.println("Unable to log in");
} finally {
System.out.println("Done");
}
}
// Passing an exception on
public void logIn() throws InvalidUserException, InvalidPasswordException,
LoginNotAvailableException {
User user = logInUser("admin", "password");
System.out.println("Logged in as " + user.getFirstName());
}
In the first you see that we have to catch each exception. We could have also
just done a single catch (Exception e) to catch them all. Note that every
try needs one catch or one finally. Each
try can have multiple catch blocks, each catch for a different
exception, and can only have one finally.
Note also that if anything goes wrong in the try block, execution of the try ceases
immediately and any code that remains unexecuted in the try is skipped. If the
exception is caught, the catch block is run. The finally is run regardless of
whether an exception is thrown or not. As you will see in the examples later on,
the finally block is an ideal place to put code that closes IO streams and database
connections. Consider it your clean up spot.
In the second logIn() we just declare the exceptions in the throws
clause and make anyone who uses the logIn() method deal with the problem.
In Java, there are several classes you should be familiar with when it comes to
error handling. These classes are Throwable, Error, Exception and RuntimeException.
The class diagram for this is shown below.
At the top is the Throwable. You can only throw objects that are subclasses of
Throwable. The throwable provides two things that are highly useful to the
programmer: a message and the stack trace. The message tells us, if the code
throwing the exception was well written, what the problem is. The stack trace
lists out the stack of code that was running when the exception was thrown with
the names of the classes, methods and line numbers. It is with these two items
that I fix about 99% of the errors in my applications that cause exceptions to be
thrown.
The two main subclasses of Throwable are Error and Exception. Errors are abnormal
conditions and indicate serious problems. The Java Docs state that Errors do not
need to be declared in throws clauses or be caught. In fact, the Java Docs go as
far as to state that you SHOULD NOT attempt to catch errors. Typically errors are
fatal to the application and are most often caused by someone killing a Java process.
You will also see errors if you use JNI and your C++ code has errors.
The Exception class is what we will focus on here. It is what you, the programmer
will see and have to deal with. The exception has many subclasses but the one
you should be most familiar with is the RuntimeException. Anything that extends
Exception but does NOT extend RuntimeException must be either caught or declared
in the throws clause of the method where the exception appears. Anything that
extends RuntimeException does not need to be caught or declared in the throws
clause (this is a good and a bad thing as you will see below).
The Typical (and Bad) Error Handling
Most new and, unfortunately, some experienced developers go about handling errors
the wrong way. Here are the three ways I have seen errors dealt with and an
explanation of why they will cause you grief. I call them the Ignore, Hide
or Run Away approaches.
Ignore (aka Bite You In The Ass)
In Java the RuntimeException is one that can be ignored. It includes exceptions
like NullPointerException, ArrayOutOfBoundsException and IllegalArgumentException.
You can find many RuntimeExceptions in the java.lang package.
RuntimeExceptions can be thrown in a method without the method declaring it
in the throws clause. For example:
// This is an example of how NOT to use exceptions
public void checkIntString(String args) {
for (int i = 0; i < args.length(); ++i) {
if (! Character.isDigit(args.charAt(i))) {
throw new IllegalArgumentException("Character at position " + i + " is not a digit");
}
}
}
The reason for having RuntimeExceptions is that, without them, you would need
try/catch or throws almost everywhere. Every time you use the . operator a
NullPointerException can be thrown. Every time you access an element of an array
an ArrayOutOfBoundsException can be thrown. The RuntimeException makes sure you
don't have to put try/catch blocks everywhere. You can say that the try/catch
is the exception and not the rule... ok, enough of the puns.
The problem with RuntimeExceptions is that anyone who calls our checkData(args)
method above doesn't know they need to deal with possible errors. It is convenient
for the programmer because they don't have to add error handling code but problems
will begin to pop up once the system is put into use. At that time, the users
have to deal with the errors. In many cases the application begins to act erratic
or even crash. Convenient for the programmer, frustrating for the end user.
Instead, you should use non-RuntimeExceptions whenever possible. This forces
the programmer to address potential errors during the coding process and does not
drop the bomb into the user's lap.
// This is an example of how to use exceptions
import java.text.ParseException;
...
public void checkIntString(String args) throws ParseException {
for (int i = 0; i < args.length(); ++i) {
if (! Character.isDigit(args.charAt(i))) {
throw new ParseException("Character at position " + i + " is not a digit", i);
}
}
}
When .NET first came out I purchased a book about C#. I was appalled to find
that every exception in .NET is a RuntimeException (or at least the C# equivalent).
What this means to me is that we want to make life easier for the programmer and
force the testers, or worse the users, to find the errors. If you use
non-RuntimeExceptions, you will be forced to deal with error handling just to
get the program to compile. The compiler will tell you where to put the error
handling, you won't have to go searching all over for potential problems. Wait
a minute... this is being proactive... good for us Java programmers!
I didn't finish exploring this .NET "feature" so there may be more to the story
so any of you .NOT developers out there, let me know if I missed something.
Hide
The most common way I have seen inexperienced developers handle exceptions is via
the hide approach. This is a very simple and potentially disastrous way to deal
with exceptions.
All non-RuntimeExceptions must be either declared in the throws of the method
signature (as seen in the second checkIntString example above) or caught.
To hide an exception you just wrap the offending code with a try/catch and leave
the catch empty. For example:
// This is an example of how NOT to handle exceptions
import java.sql.Connection;
import java.sql.DriverManager;
...
public Connection getConnection(String url, String user, String password) {
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
}
return conn;
}
I ran into this one once in a production environment. A developer had written
an application in Java and was no longer with the company (after reviewing their
code I felt this was a good thing). Many months later, the database was moved to a
new server and suddenly the application stopped working. It was giving an odd
NullPointerException in a completely different part of the application and none
of the people maintaining the application could figure out why. After several
hours of agony they brought me in. They got me a copy of the code and I spent
an hour or two digging around and trying to understand what was written. I finally
found the problem was that the IP address of the database had changed and the
getConnection method was failing. Of course the exception was ignored as if
everything was OK when in fact everything was NOT OK. If this exception had not
been ignored, the problem would have been immediately identifiable and fixed in
minutes. Once everyone's time was added up, the error cost about $1500 in labor
to fix and who knows how much money to rewrite the code so it handled errors
properly. If the developer had coded it properly at the beginning it would have
taken him minutes to write something that would have saved the company thousands
of dollars.
Run Away
In the second checkIntString example above I showed you how you can
declare the exception in the throws part of the method signature. I consider
this good practice when writing utility classes or APIs that will be used in many
places or by many developers. But you don't want to get carried away with it.
For example:
// This is an example of how NOT to use exceptions
public abstract class Task {
public abstract void setArgs(String[] args) throws Exception;
public abstract void checkUsage() throws Exception;
public abstract void setUp() throws Exception;
public abstract void doTask() throws Exception;
public abstract void shutDown() throws Exception;
public static void main(String[] args) throws Exception {
Task task = (Task) Class.forName(args[0]).newInstance();
task.setArgs(args);
task.checkUsage();
task.setUp();
task.doTask();
task.shutDown();
}
}
This is the opposite extreme from the hide method. Everything just throws Exception.
I have seen developers put throws Exception in every method
just like this, even if there is no need for the exception. It certainly makes
it easier to code but notice the problem - no one is handling the exception. It
just bubbles on up to the top and crashes the program. You should throw meaningful
exceptions like this:
public class TaskException extends Exception {
public TaskException(String message) {
super(message);
}
public TaskException(String message, Throwable cause) {
super(message, cause);
}
}
public abstract class Task {
public abstract void setArgs(String[] args);
public abstract boolean isUsageCorrect();
public abstract String getUsageMessage();
public abstract void setUp() throws TaskException;
public abstract void doTask() throws TaskException;
public abstract void shutDown() throws TaskException;
public static void main(String[] args) {
Task task = null;
try {
task = (Task) Class.forName(args[0]).newInstance();
} catch (Exception e) {
System.err.println(e.getClass().getName() + " creating task");
e.printStackTrace();
System.exit(-1);
}
task.setArgs(args);
if (! task.isUsageCorrect()) {
System.err.println(task.getUsageMessage());
System.exit(-2);
} else {
try {
task.setUp();
} catch (TaskException e) {
System.err.println("Error setting up task");
e.printStackTrace();
System.exit(-3);
}
try {
task.doTask();
} catch (TaskException e) {
System.err.println("Error running task");
e.printStackTrace();
System.exit(-4);
}
try {
task.shutDown();
} catch (TaskException e) {
System.err.println("Error shutting task down");
e.printStackTrace();
System.exit(-5);
}
}
System.exit(0);
}
}
Yes it is a lot more code but when it is run by a user, they will get a meaningful
error message if there is a problem. Note that the main method does not throw
an exception. I recommend that your main method should never throw an
exception, you should always handle the error and then exit if necessary.
The Right Way To Handle Errors
Now that we know how NOT to handle exceptions, what are some rules on the proper ways
to handle them. Here's how we really should do it...
try/catch or throws?
The first thing you need to decide when dealing with an exception is whether to
use a try/catch or declare it in the throws clause. The answer to the question
is going to be rather subjective but I have some guidelines for helping you decide
which approach to use.
If you are working with an API to be used by other developers, you should almost
definitely declare the exception in the throws clause. You may want to catch
any exception and wrap it with your own custom exception (see below for how this
works) and this custom exception will then appear in the throws clause. Regardless,
throwing an exception is a great way to communicate to other developers that they
need to be aware that problems can occur and they need to be ready to deal with
them and inform the user.
As with an API, if it is not clear what will need to be done with the exception
when it arises, you will probably want to declare it in the throws clause. However,
make sure that somewhere, eventually, the exception will be handled.
If the exception pops up at a place where you know how you want to handle it,
that is the perfect place for a try/catch. Catch the exception, handle it appropriately
and move on.
For example, let's say you have a web site that consists of JSPs, Servlets,
Business Logic objects and DAOs. The JSPs render HTML. The user then clicks on
links or submits forms to the Servlets. The Servlets tell the Business Logic
what to do, the Business Logic tells the DAOs to do inserts, updates or deletes
and the DAO runs the SQL against the database. This is common in 3-tiered web
applications. If there is an error on the database, perhaps you are trying to
add a user but that user name is already taken, the DAO will receive an SQLException.
What I do in this situation is have the DAO pass the exception on up to the
Business Logic. The Business Logic then wraps the exception with its own
BusinessLogicException (or maybe even a UserExistsException) and throws that to
the Servlet. The Servlet then catches the exception, prepares a nice error message
for the user and sends the message to the JSP which then displays it to the user.
In this example, the DAO had no idea what to do with the exception. It did not
know if the caller was a web site or a desktop application. So, it dumbly passed
the exception on up the chain.
The Business Logic also didn't know what the caller was. Keep in mind that, in
order to maximize reuse, we want to design the Business Logic to be usable by
a desktop application, by another web site or maybe even a web service. The web
site doesn't need to know we are using a database. We could be using Active
Directory, a SOAP service or a simple file. So the Business Logic needs to
transform the SQLException into some generic business error - so we catch the
SQLException, wrap it in a BusinessLogicException and throw the BusinessLogicException.
Once we get to the Servlet level, we now know what we want to do with the exception.
we extract the message from the exception, log the error and send the user back
to the page they were on and display the error message.
In general I attempt to throw the exception (or the wrapped exception) up to the
user interface level. Ultimately the error needs to be presented to the user so,
in most cases that is the most logical place to catch the exception. On a desktop
application you can then display an error dialog to the user.
You should be specific when declaring an exception in your throws clause.
Don't throw Exception unless you have to. Some good examples of
where you may consider using throw Exception are
in interface methods where you really don't know what may go wrong in specific
implementations - even then you may want to create your own special exceptions).
What to Put Where
The throws clause is pretty straight forward. You simply declare each exception
thrown within the method declaration.
With the try/catch/finally there are many choices and quite often inexperienced
developers choose poorly.
Before the try block you should declare any variables you may need in your catch
or finally blocks. Initialize their values to null in the declarations. If you are
doing file IO, you should declare your IO streams. If you are connecting to a
database, you should declare your connection, statements and result sets.
Next, put the rest of the code that is used if there is no exception thrown into
the try block. If any of the code at the end of your try block will need to run
regardless of whether an exception is thrown or not, you should move that code
to the finally.
Last you should add your catch blocks and add the code that handles each exception
caught.
Example: here is a utility class that checks the contents of a text file to see
if a specific string is present on any one line in the file. The method will notify
the user about any errors but will just return false if it has problems reading
the file. In many situations you would declare the IOException in the throws
clause and remove the catch block.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
...
public boolean fileContains(File file, String str) {
// Declare the variable needed in the try and finally
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
String line = null;
while ((line = reader.readLine()) != null) {
if (line.indexOf(str) >= 0) {
return true;
}
}
return false;
} catch (IOException e) {
// Let the user know there was a problem.
System.out.println("Error reading file:");
e.printStackTrace();
return false;
} finally {
// No matter whether the try succeeds or fails,
// we should close the file - for this reason we
// close the BufferedReader in the finally.
// Note that even though the try and catch blocks
// have a return statement, the finally will run
// BEFORE the value is actually returned.
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
System.err.println("Error closing file read");
}
}
}
If you are putting a throws into your method declaration, you should still
consider using a try/finally. For example:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
...
private String dbUrl;
private String dbUser;
private String dbPassword;
// Attempt to save the user to the database.
public void saveUser(String userName, String password, String firstName, String lastName)
throws SQLException {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
stmt = conn.prepareStatement(
"INSERT INTO app_user (" +
" user_name, " +
" pass, " +
" first_name, " +
" last_name" +
") VALUES (" +
" ?, ?, ?, ? " +
") "
);
int index = 1;
stmt.setString(index++, userName);
stmt.setString(index++, password);
stmt.setString(index++, firstName);
stmt.setString(index++, lastName);
stmt.execute();
} finally {
// Close the statement and connection. Note that
// we close the statement separately from the
// connection so that, if there is a problem closing
// the statement, it doesn't stop us from closing
// the connection.
try {
stmt.close();
} catch (Exception e) {
System.err.println("Error closing statement");
}
try {
conn.close();
} catch (Exception e) {
System.err.println("Error closing connection");
}
}
}
Also make sure you put your catch blocks to work. At the very minimum, most if
not all of your catch blocks should have some kind of logging. If the exception
caught is rather benign, you can log at a debug or info level (see my
Java Logging Made Easy
article for details on logging). If it is important that we take notice of and
deal with an exception we should log at the error or at warning level.
Example: Here is a utility that attempts to delete a temp file. Since, if the
delete fails, it really doesn't create an immediate problem, we just want to log
it and continue on without stopping the normal operations of the program. Someone
will check the logs later and find that the temp file was not deleted properly
and fix the problem then.
import java.io.File;
...
public void deleteTempFile(File file) {
try {
file.delete();
} catch (Exception e) {
System.err.println(
"Unable to delete temp file " + file.getAbsolutePath() + ": " + e.getMessage()
);
}
}
A user interface will want to collect errors and display them to the user. Sometimes
one error is fatal enough to stop execution and display the error right away but
at other times you may want a list of errors to show the user.
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
...
private List errors = new ArrayList();
private String formatPhoneNumber(String phone) throws ParseException {
StringBuffer numbers = new StringBuffer();
for (int i = 0; i < phone.length(); ++i) {
char c = phone.charAt(i);
if (Character.isDigit(c)) {
numbers.append(c);
}
}
if (numbers.length() != 10) {
throw new ParseException("Phone number has only " + numbers.length() + " digits", phone.length() - 1);
}
numbers.insert(0, '(');
numbers.insert(4, ')');
numbers.insert(8, '-');
return numbers.toString();
}
public String[] formatPhoneNumbers(String[] phoneNumbers) {
errors.clear();
List formattedNumbers = new ArrayList();
for (int i = 0; i < phoneNumbers.length; ++i) {
try {
formattedNumbers.add(formatPhoneNumber(phoneNumbers[i]));
} catch (ParseException e) {
errors.add(e.getMessage());
}
}
return (String[]) formattedNumbers.toArray(new String[formattedNumbers.size()]);
}
public String[] getErrors() {
return (String[]) errors.toArray(new String[errors.size()]);
}
Some software has built in ways to accumulate and display errors to the users.
For example, Struts allows the web application to build up a list of errors and
even associate those errors to particular fields on the web page. This allows
you to display error along side the field where the error is occurring making it
easier for the user to discern what is wrong and where and quickly fix it.
Then there are times you need to wrap an exception. If we go back to the Task
and TaskException in an earlier example, let's say we want to create a Task that
starts at a given directory and determines the size of all the files in that
directory and all the directories beneath it and prints the total size to the user.
If there is an error, a TaskException will be thrown. Although the
SecurityException is a RuntimeException and we could just let it go, we really
should catch it and wrap it in a TaskException so the Task library will handle
the problem properly.
import java.io.File;
...
public class DiskUsage extends Task {
private String[] args;
private File dir;
public void setArgs(String[] args) {
this.args = args;
}
public boolean isUsageCorrect() {
return (args != null) && (args.length == 2);
}
public String getUsageMessage() {
return "Usage: java Task MyTask [directory]";
}
public void setUp() throws TaskException {
dir = new File(args[1]);
}
public void doTask() throws TaskException {
long size = getSize(dir);
System.out.println(
"The directory " + dir.getAbsolutePath() +
" and its subdirectories contain " + size + " bytes"
);
}
private long getSize(File directory) throws TaskException {
long size = 0;
try {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; ++i) {
if (files[i].isDirectory()) {
size += getSize(files[i]);
} else {
size += files[i].length();
}
}
} catch (SecurityException e) {
// Note we cannot catch Exception as that will grab the
// TaskException if thrown by our recursive call to getSize.
// The Java Docs for java.io.File tells us that the methods
// listFiles(), isDirectory() and length() can throw
// SecurityException which is actually a RuntimeException
throw new TaskException(
"Error attempting to get size of directory " + directory.getAbsolutePath(),
e
);
// Note also that we put the SecurityException INSIDE of
// the TaskException so when we print out the stack trace
// we get the stack trace from both exceptions and can
// see where the problem started.
}
return size;
}
public void shutDown() throws TaskException {
// Nothing to do
}
}
Why wrap exceptions? Why not rethrow the same exception or, as we could have done
in the last example, just let the RuntimeException go?
First of all, when you are writing code that extends or implements code from another
API, you should always follow their exception handling. If you do not, you can get
unpredictable results. What would have happened if we had let SecurityException
go without wrapping it with TaskException? After all, Task was expecting a
TaskException, not a SecurityException. In our case, Task would NOT have handled
the exception properly and the program would have unexpectedly terminated. You
should try to throw exceptions that the caller expects.
You should also NEVER rethrow the exception you caught:
// NEVER do this
import java.io.FileInputStream;
...
try {
FileInputStream in = new FileInputStream("test.txt");
// More code here...
} catch (Exception e) {
System.err.println("Error creating input stream...");
e.printStackTrace();
throw e;
}
Here the problem is that, as soon as you say throw e your exception's old stack
trace is replaced with a new stack trace. The result is that you have no way to
recover the exact location where the original error occurred. Without that information
your attempt to find and fix the bug may turn into a crap shoot. Instead, WRAP your exceptions.
import java.io.FileInputStream;
...
public class MyException extends Exception {
public MyException(String message, Throwable t) {
super(message, t);
}
}
...
try {
FileInputStream in = new FileInputStream("test.txt");
// More code here...
} catch (Exception e) {
System.err.println("Error creating input stream...");
e.printStackTrace();
throw new MyException("Error creating input stream", e);
}
Now when the MyException is finally caught and a stack trace is produced, you will
see not only the stack trace for MyException but you will also see the stack trace
for Exception e stored inside it.
I hope this has provided you with an arsenal of techniques to help you do your
exception handling like an expert. With the proper exception handling, you will
experience fewer support issues and have much more stable applications.
|
|
|
|
| Posted by Aaron Bono at | | | |
|
Not long ago I joined a friend of mine for lunch to discuss what we were both
doing in our new jobs. My friend had recently started a job with a local IT
training company teaching several topics including a few Java courses.
"So what kind of logging are you using?" I asked. After all, you need some kind
of output to do quick and easy debugging. The debugger is nice but is sometimes
more cumbersome than it is worth.
"We are just printing to standard out."
I quickly inquired why they couldn't use a standard logging utility like Log4J
or the built in java.util.logging. I have always been a stickler for teaching
students how to do things right the first time. So why teach them to log using
System.out when that is the last thing you do in a real world
application? To my friend it was a matter
of time. They just didn't have the time to teach the students how to do proper
logging and cover all the other topics in the class.
But I had a solution - a simple approach to logging that would be easy for
novices to learn and would be easy to convert to more sophisticated logging in
the future.
What I suggested was to use a logging wrapper. Have the students start out by
creating a XYZAppLogger class that all their code calls when they need to log.
Then have the XYZAppLogger dump everything to System.out or System.err.
What are the advantages of doing this?
- The XYZAppLogger, as you will see below, is a very simple class so it
helps new students get started by teaching them to type in, compile and run
a program. This is a trivial task for experienced Java programmers and a
nice intro for beginners.
- By having all their code call XYZAppLogger, when a student is ready to
start using a sophisticated logging utility, they only change one file - the
XYZAppLogger.
- If a student decides they don't like the logging utility they choose,
they can easily switch to something else. I had a programmer tell me once
that they thought it was a waste of time to use a logging wrapper. "Just
call the logging utility directly. You will never change your logging." Two
months later I sat at my computer coverting all my code from java.util.logging
to Log4J. I knew better - so why did I listen to that other programmer?
I use a logging wrapper for all my logging now, just in case.
- Turning logging off throughout the whole application is easy - you just
comment out the System.out.println and System.err.println statements in the
XYZAppLogger
- With a simple if/else in the XYZAppLogger you can turn logging on or off
for certain classes. We will leave this one as an exercise for the
instructor - yes we are giving you an exercise!
- You teach students that the logging is worth keeping in your
application. Without this logging mechanism, students add the System.out,
do their debugging, delete it (or comment it out), and then have to re-add
(uncomment) it later if they need to do further debugging. Good God man!
Don't delete it - that logging is valuable!
Below is a detailed approach I would recommend using to teach logging to a class
learning Java, whether they are new to the language or experienced developers.
It starts out simple with the intention of setting up a foundation for good
logging without taking much classroom time.
After showing how to set up basic logging, I show you how to convert it into
Log4J. This includes not only how to replace the System.out and System.err but
also how to load your logging settings dynamically from your own custom properties
files.
Logging for the Classroom
The essence of logging in your applications is that you want to give the
programmer the power to send messages to the user when the user wants to
see them. In order to give the user the power to dictate what messages they do
and do not want to see, the experts have come up with concept of the logging level.
In Log4J, one of the most common logging utilities for Java, there are 6 logging
levels:
- Fatal
- Error
- Warn
- Info
- Debug
- Trace
If you count ALL and OFF you get 8, but 6 is more than enough for beginners,
maybe even too much. The order is important. If I turn Info logging on, I
automatically turn on Warn, Error and Fatal. Anything at or above the setting I
choose is on and anything below is off.
When I train programmers how to
log, I first explain the 6 levels. When they ask, as the inevitably do, "How do
I know which one to use?" I simply say, it's up to you, use your best judgement.
The names of the logging levels are fairly self-explainatory. Beyond that,
experience is going to be the best teacher. If they really want to know, I
suggest digging through some of those open source projects like you find at
apache.org.
OK, we spent one minute of the class, let's move forward, get the logging
discussion done so we can cover the topics the students paid us to teach
them.
The next level of control for the user over what is and is not logged revolves
on what part of the program is generating the output. Since Java is loaded with
lots of small class files (we won't debate whether that is a good or bad thing
here), the class is a great way to control logging. Log4J also allows package
level control but let's not burden the students with that for now.
So what does our XYZAppLogger look like? First it will need a method for each
logging level:
public void fatal(String message)
public void error(String message)
public void warn(String message)
public void info(String message)
public void debug(String message)
public void trace(String message)
Then I throw in a few more so we can print out the stack trace of our errors:
public void fatal(String message, Throwable t)
public void error(String message, Throwable t)
public void warn(String message, Throwable t)
Second we need a way to identify what class the logger is logging message for:
public static XYZAppLogger getLogger(Class c)
We put all this together and get this:
public class XYZAppLogger {
private String name;
private XYZAppLogger(String name) {
this.name = name;
}
public static XYZAppLogger getLogger(Class c) {
return new XYZAppLogger(c.getName());
}
public void fatal(String message) {
System.err.println(name + ": " + message);
System.err.flush();
}
public void fatal(String message, Throwable t) {
System.err.println(name + ": " + message);
t.printStackTrace();
System.err.flush();
}
public void error(String message) {
System.err.println(name + ": " + message);
System.err.flush();
}
public void error(String message, Throwable t) {
System.err.println(name + ": " + message);
t.printStackTrace();
System.err.flush();
}
public void warn(String message) {
System.err.println(name + ": " + message);
System.err.flush();
}
public void warn(String message, Throwable t) {
System.err.println(name + ": " + message);
t.printStackTrace();
System.err.flush();
}
public void info(String message) {
System.out.println(name + ": " + message);
System.out.flush();
}
public void debug(String message) {
System.out.println(name + ": " + message);
System.out.flush();
}
public void trace(String message) {
System.out.println(name + ": " + message);
System.out.flush();
}
public static void main(String[] args) {
XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
logger.fatal("Test fatal");
logger.error("Test error");
logger.warn("Test warn");
logger.info("Test info");
logger.debug("Test debug");
logger.trace("Test trace");
}
}
If you think this is too much for your students, no problem. Start with just the
error(String message, Throwable t) and debug(String message)
methods. They can add the others later. You can also ommit the flush() if all
the methods use only System.out (or only System.err) but if you mix System.out
and System.err, your logging may look odd because System.out and System.err tend
to flush their output asyncronously causing your messages to be mixed togther
and hard to read.
Now your students can build, compile, run and test their new logging utility.
And they see an example of how to use the logging in the main method.
Excellent!
Switching to Log4J
When converting your logging over to Log4J, all you really need to do is replace
the System.err and System.out statements, right? Well, almost. We will want to
add a caching mechanism so we create fewer XYZAppLogger objects. We will also
add methods to ask the logger if logging for that level is enabled or not.
First lets get the Log4J logger added. To do that we replace
private String name;
with
private Logger logger = null;
Oh, yea, what am I thinking! You should download the Log4J library from
http://logging.apache.org/log4j/docs/download.html.
Whew, can't forget that. Now where was I...
The constructor becomes
private XYZAppLogger(Class c) {
this.logger = Logger.getLogger(c);
}
Now get rid of the ".getName()" from the getLogger method. With that we also
need to replace all the System.out and System.err statements.
Next we want to add caching. We want to make sure that if the same class is
passed into getLogger that the same XYZAppLogger is returned and that we do NOT
create another XYZAppLogger. I added private static final Map loggers = new HashMap();
and modified the getLogger method (see below) to get good synchronized management
of the loggers.
Now sprinkle in some isFatalLoggable(), isErrorLoggable(), etc methods (again,
see below).
And one last thing... you need to set the Log4J settings. In the next section
I show you how to load the properties from an external file.
The result...
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class XYZAppLogger {
private Logger logger = null;
private static final Map loggers = new HashMap();
private XYZAppLogger(Class c) {
this.logger = Logger.getLogger(c);
}
public static XYZAppLogger getLogger(Class c) {
String className = c.getName();
XYZAppLogger logger = (XYZAppLogger) loggers.get(className);
if (logger == null) {
synchronized (XYZAppLogger.class) {
logger = (XYZAppLogger) loggers.get(className);
if (logger == null) {
logger = new XYZAppLogger(c);
loggers.put(className, logger);
}
}
}
return logger;
}
public boolean isFatalLoggable() {
return logger.isEnabledFor(Level.FATAL);
}
public boolean isErrorLoggable() {
return logger.isEnabledFor(Level.ERROR);
}
public boolean isWarnLoggable() {
return logger.isEnabledFor(Level.WARN);
}
public boolean isInfoLoggable() {
return logger.isEnabledFor(Level.INFO);
}
public boolean isDebugLoggable() {
return logger.isEnabledFor(Level.DEBUG);
}
public void fatal(String message) {
logger.fatal(message);
}
public void fatal(String message, Throwable t) {
logger.fatal(message, t);
}
public void error(String message) {
logger.error(message);
}
public void error(String message, Throwable t) {
logger.error(message, t);
}
public void warn(String message) {
logger.warn(message);
}
public void warn(String message, Throwable t) {
logger.warn(message, t);
}
public void info(String message) {
logger.info(message);
}
public void debug(String message) {
logger.debug(message);
}
private static void setUpLogger() {
Properties properties = new Properties();
properties.put("log4j.rootLogger", "INFO, Console");
properties.put("log4j.appender.Console", "org.apache.log4j.ConsoleAppender");
properties.put("log4j.appender.Console.layout", "org.apache.log4j.PatternLayout");
properties.put("log4j.appender.Console.layout.ConversionPattern", "%-4r [%t] %-5p %c %x - %m%n");
PropertyConfigurator.configure(properties);
}
public static void main(String[] args) {
setUpLogger();
XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
Exception testException = new RuntimeException("This is a test exception");
logger.fatal("Test fatal");
logger.fatal("Test fatal", testException);
logger.error("Test error");
logger.error("Test error", testException);
logger.warn("Test warn");
logger.warn("Test warn", testException);
logger.info("Test info");
logger.debug("Test debug");
}
}
Loading Log4J Configuration Files
Next I am going to show you how to load your logging configuration from an
external file. We will call this file LogConfig.properties.
OK, I know what some people who are familiar with Log4J are going to say: you
are supposed to call the file log4j.properties and put it in the classpath so
it is loaded on startup. Well, you can do that, but I run multiple web sites
that each have their own log settings. Each needs to load the settings into its
own classloader separate from the others. I can't put the logging into a global
logging file - it would be unmanagable. And what if you are on a shared server
and have no control over the main log4j.properties file?
*steps off soap box*
Put this LogConfig.properties file into your classpath. For web apps, that would
be the WEB-INF/classes directory. Here is what the contents of the file looks like:
log4j.rootLogger=INFO, Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Finally change the setUpLogger method to read this properties file:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class XYZAppLogger {
private Logger logger = null;
private static final Map loggers = new HashMap();
private XYZAppLogger(Class c) {
this.logger = Logger.getLogger(c);
}
public static XYZAppLogger getLogger(Class c) {
String className = c.getName();
XYZAppLogger logger = (XYZAppLogger) loggers.get(className);
if (logger == null) {
synchronized (XYZAppLogger.class) {
logger = (XYZAppLogger) loggers.get(className);
if (logger == null) {
logger = new XYZAppLogger(c);
loggers.put(className, logger);
}
}
}
return logger;
}
public boolean isFatalLoggable() {
return logger.isEnabledFor(Level.FATAL);
}
public boolean isErrorLoggable() {
return logger.isEnabledFor(Level.ERROR);
}
public boolean isWarnLoggable() {
return logger.isEnabledFor(Level.WARN);
}
public boolean isInfoLoggable() {
return logger.isEnabledFor(Level.INFO);
}
public boolean isDebugLoggable() {
return logger.isEnabledFor(Level.DEBUG);
}
public void fatal(String message) {
logger.fatal(message);
}
public void fatal(String message, Throwable t) {
logger.fatal(message, t);
}
public void error(String message) {
logger.error(message);
}
public void error(String message, Throwable t) {
logger.error(message, t);
}
public void warn(String message) {
logger.warn(message);
}
public void warn(String message, Throwable t) {
logger.warn(message, t);
}
public void info(String message) {
logger.info(message);
}
public void debug(String message) {
logger.debug(message);
}
private static void setUpLogger() throws IOException {
String path = "LogConfig.properties";
Properties properties = new Properties();
properties.load(XYZAppLogger.class.getClassLoader().getResourceAsStream(path));
synchronized (XYZAppLogger.class) {
PropertyConfigurator.configure(properties);
loggers.clear();
}
XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
logger.info("Properties loaded: " + path);
}
public static void main(String[] args) throws Exception {
setUpLogger();
XYZAppLogger logger = XYZAppLogger.getLogger(XYZAppLogger.class);
Exception testException = new RuntimeException("This is a test exception");
logger.fatal("Test fatal");
logger.fatal("Test fatal", testException);
logger.error("Test error");
logger.error("Test error", testException);
logger.warn("Test warn");
logger.warn("Test warn", testException);
logger.info("Test info");
logger.debug("Test debug");
}
}
This is evolving quite nicely...
So why Log4J when Sun has recently added java.util.logging to the core JDK? Why
should you use Log4J when that requires downloading and configuring additional
libraries?
First of all, a lot of open source products use Log4J so it is good to understand
how it works and how to configure it. That also means there are a lot of discussions
and resources out there that help you learn the best way to use it.
Secondly, I have found the java.util.logging to be less than perfect.
For example, the following code will be a problem if this class is loaded by the
classloader before the logging configuration is read from your logging
configuration file:
package com.mysite.test;
import XYZAppLogger;
public class MyClass {
private static XYZAppLogger logger = XYZAppLogger.getLogger(MyClass.class);
}
Log4J handles this well. However, java.util.logging falls short. If I configure
Log4J to set the com.mysite log level (a setting for everything under the com.mysite package)
the MyClass logger is updated properly. But java.util.logging will NOT alter the log level
for MyClass's logger because the logger has already been initialized.
The problem with java.util.logging is that, when you ask for MyClass's logger,
java.util.logging creates a log configuration search chain that searches
up the package and puts that search chain into the newly created logger. It
will first check com.mysite.test.MyClass, then com.mysite.test,
then com.mysite, then com and finally the root log settings. The first log settings
it finds it uses. The problem is, in order to use less memory, if there is no
log configuration for com.mysite when the logger is obtained (before the logging
properties are loaded) the com.mysite is not included in the search chain. So when
the log configuration is loaded, your logger has already been created and it is
missing the com.mysite check point so those settings are skipped. Let me tell
you, it took days of digging through source code to get to the bottom of this bug.
Of course I found this in JDK 1.4 and never checked it in JDK 1.5. It may be
fixed, if you try it out, I would love to know.
All in all, I hope you spread the word about logging. I believe, no matter what
level a programmer is at, there is no excuse for using System.out or System.err
for their logging. Frankly, unless you are writing a console application,
I would recommend acting like System.out and System.err don't even exist.
|
|
|
|
| Posted by Aaron Bono at | | | |
|
|
|
|
|
|
|
|