 |
Design For Exceptions
Posted by dwalend on October 06, 2003 at 04:57 AM | Comments (25)
I read Bill Venner's interview with James Gosling, "Failure and Exceptions," and with Anders Hejlsberg, "The Trouble with Checked Exceptions," and was a little surprised. I thought exceptions would be in .Net since .Net has taken so many other features from Java. I've never found checked exception clauses to be much of a burden. It's one of my favorite features of Java. Anyway, given all the talk about exceptions and exception handling, I thought I'd take a minute to describe what I do and ask how others use them.
I think the problems that Dr. Hejlsberg describes, versionability and scalability, are easy to contain if the development team decides how they're going to handle trouble before they get too deep into their work. Here's what I usually do:
When I start a package, I create a handful of support classes. These classes are simple, but having them there encourages developers to use them instead of creating custom solutions. One of these classes is an abstract exception, a parent for all the concrete exceptions in that package. When developers discover they need a new exception, they extend this exception. The API for the package only throws subclasses of this exception. The abstract exception from JDigraph's net.walend.digraph package looks like this:
package net.walend.digraph;
import java.io.Serializable;
/**
An abstract parent for all caught exceptions in the net.walend.digraph package.
@author @dwalend@
*/
public abstract class DigraphException
extends Exception
implements Serializable
{
public DigraphException(String message)
{
super(message);
}
public DigraphException(Throwable wrapped)
{
super(wrapped);
}
public DigraphException(String message, Throwable wrapped)
{
super(message, wrapped);
}
}
/*
@license@
*/
Unlike the rest of the API, which gets defined during design, I've found exceptions tend to bubble up from implementation. Sometimes it's obvious that something will go wrong at design time, but often I need to create some new exceptions while implementing the code. Plus I like to create very descriptive subclasses of my abstract exception. Specific subclasses make it easier for code handling the exception to determined what failed and how to react. That's what Dr. Gosling means by, "... in a throws clause ... be as specific as possible."
If I can't handle a checked exception, I like wrapping the exception with my own subclass of my package's abstract exception, then throw the wrapping exception. My API still only throws subclasses of my abstract exception. The overhead is just constructing the new exception. Wrapping exceptions bounds the scaling problem Dr. Hejlsberg describes by limiting what can appear in the throws clause.
Here's the part that's worth discussing: I like keeping my methods' throws list very specific, limited to the concrete exceptions actually thrown. This choice means that people writing code that calls these methods will be cognizant of each exception. If they like, they can still catch the common parent exception.
The alternative is having all methods declare that they throw only the same abstract exception. If you believe your public API can never be changed, this is the way to go. This approach solves the versioning problem Dr. Hejlsberg describes by insulating code from changes in the exceptions actually thrown, but does not give great hints for how to handle the problem.
For most of my work, the versioning problem is overstated. Most of the source code developers create stays small in scope. I understand that after some point in the development life cycle of code reused across many projects the throws clause in a method can't be changed anymore. If a project becomes popular enough, these detailed throws clauses will break down and having the parent exception in the throws clause is a better decision. I think that popularity is pretty rare: Interfaces from a JSR, or public methods in a popular apache project, or from a core software team at a big company should declare that they throw a general parent exception from day one. However, most code we write has a fairly small and well-defined group of developers. Changing the throws clause doesn't wag many lines of code. That code may need to be changed to handle the new exception in a thoughtful way. If the library is becoming more popular while it is still being developed, adding the parent exception to the end of the throws clauses is easy and well-received.
If that last paragraph doesn't generate some interesting talk back, the next two will.
When I've tried to use the standard exceptions available in java or javax packages, I've discovered that very few of these exceptions have constructors that take nested exceptions. Generally, developers will use constructors that take an exception, but will not use the initCause() method on their own. I usually have to subclass them to either call the initCause() method in the constructor or override getCause() (to stay compatible with JDK 1.3. See Felipe Leme's latest blog.)
I avoid throwing my own RuntimeExceptions except at fairly high levels when it's time to stop or reset the application without cluttering the API. And I'll sometimes use them to mark states I think are impossible. If I control the main event loop, my code catches these exceptions and tries to bow out gracefully, whatever graceful means in the context. I've also used RuntimeExceptions when I was trying to make quick fixes to an existing code base. But I've regretted this shortcut in the past.
Do other people do anything radically different? Is it ever OK to use throws Exception in a declaration? Or, like .Net, to use all RuntimeExceptions no matter what?
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Hey Toto, this isn't smalltalk anymore!
First let me say I agree.
I'm afraid what is happening is some ivory tower-type folks are finding that it is indeed easier to code with unchecked exceptions. Wait until they find that they can use unchecked _types_ in languages such as VB! :) Or that some other languages allow you to escape directly to an error handler via a goto!
Seriously though, I've been reading artima.com with some interest and I have to agree that exceptions do cause a ripple affect. And sometimes I actually do want all exceptions to rise to a single handler. But it is just as often that I've thanked the compiler for telling me about an exception which I've missed. And let me tell you, the test system would have missed it too.
Anyway, that gets to the root of the issue. These ivory tower-type folks are claiming that testing eliminates the need for typed exceptions. I don't buy it. I've yet to see a _really_ complete regression suite in the wild.
On the other hand, exceptions alert me that I should add a testcase for the condition, IF it is feasible to reproduce in the test system.
Posted by: jtr on October 06, 2003 at 10:44 AM
-
The purpose of exceptions...
The difference between checked and runtime exceptions is that runtime exceptions say "Something went wrong I don't know how to fix", while checked exceptions say "Something went wrong you might know how to fix in a catch clause".
A lot of the time people simply re-throw exceptions and don't think about that meaning. This can lead to encapsulation problems (e.g. a layer thats chock full of throws IOException, and/or throws SQLException, which, when implemented over "some other" data source doesn't throw any of those things - they were broken encapsulation from the original implementations)
Checked exceptions that are created, not re-thrown , are more explicitly saying "I think you can fix this". But to quote Gosling from that interview - "When you try to open a file and it's not there, you're coping strategy is really determined by what you were going for." note - what you were going for, not how it went wrong. He's describing handling "a failure" here, rather than handling "a specific kind of failure" - because the second case is pretty rare.
An example of a justifiable checked exception would be the UnavailableException in the servlet API, which marks a servlet unavailable /temporarily/. Implementers can communicate usefulinformation to their callers using this exception, so its use is probably justified.
So generally I would use checked exceptions when there is a behavioural contract associated with the exception, and use runtime exceptions elsewhere. Rethrowing checked exceptions from a lower layer breaks encapsulation and I'd avoid it unless I'm claiming to "be" that lower layer (e.g. in an I/O utility library I'd re-throw all java's IOExceptions). Instead lower layer's exceptions are thrown wrapped in the appropriate exception for /this/ layer (ie checked if I expect the client to behave differently, runtime otherwise).
I found the Eiffel model interesting (see eg http://burks.brighton.ac.uk/burks/language/eiffel/oveiffel/exceptns.htm) . Eiffel doesn't have checked exceptions, because they explicitly have this notion of a contract being fulfilled or not. You can't "partly" fulfil a contract, throw a checked exception saying this, which would be the equivalent in java. Worth a look.
(BTW there is another similar viewpoint on artima in the interview with Elliot Rusty Harold - http://www.artima.com/intv/jdom3.html)
Posted by: ba22a on October 07, 2003 at 05:28 AM
-
The purpose of exceptions...
I think your taxonomy for checked Exceptions and RuntimeExceptions is interesting, and probably a useful one (though the JDK itself doesn't consistently follow this; consider NumberFormatException, which is a subclass of RuntimeException).
The problem is that to the developer who is not diligent about exception handling (and, in my experience, the majority are not), the intuitive tendency when creating a new exception class is to subclass Exception ("I'm writing a new exception class, therefore it extends Exception"). The issue of whether it will be, in practice, a recoverable exception or not, is not likely to be considered.
The irony is, the developer who is cognizant of whether an exception is likely to be recoverable or not is also likely to be aware of good exception handling techniques in general. They're not the type that will blindly code empty catch blocks or the other common exception handling bloopers we see too many times.
My point is, checked exceptions are an aid to the experience developer who could just as well do without them, but an dagger to the inexperienced developer, because they tempt him to write empty catch blocks or "throws Exception" declarations to get the compiler off his back.
Incidentally, had Sun chosen a different naming convention, we'd probably the opposite of the current situation: The majority of custom exceptions would be unchecked. Instead of RuntimeException and Exception, I would have liked to see Exception (unchecked) and RuntimeException. RuntimeException is a silly name anyway; show me an exception that DOESN'T occur at runtime.
But that betrays my bias towards unchecked exceptions, a bias that Sun (at least initially) does not share.
Posted by: jimothy on October 07, 2003 at 11:38 AM
-
Design For Exceptions
I was told that throwing exceptions in not a good habit. So we never throw excpetions in our application. We have methods where each method returns object to which we can say errorOccured and add the error id which is predefined.
The caller of the method will check isErrorOccured(), if yes then it will invoke a method (getErrorList()) on the return type of the method invoked.
I still cant understand why throwing exceptions is not a good habit? Anyone will help me
Posted by: javakishore on October 07, 2003 at 11:43 AM
-
Design For Exceptions
Who ever told you that is mistaken. Languages without exceptions, like C, usually indicate errors with return codes, which must be checked after calling each and every method. But checking error codes is tedious and painful, and developers get lazy and cocky ("I KNOW this call won't fail"), so they leave out error checking. And then things go break, and you can't track down where in the code things went wrong.
Exceptions are a giant step forward for error detection and handling. Rather than check for errors after every method call (as in C and your project), you can write a series of method calls you expect to run successfully ("The One True Path") in a try block, and place your exception handling code in the catch block. And where its not appropriate to handle an exception in a method, you can simply not catch it, and let it "bubble-up" to the calling method, who can then handle it as appropriate.
I'd be interested in hearing the reasons you were given that throwing exceptions are a bad habit. I suspect that your mentor read once that exceptions can be overused to represent non exceptional conditions (this is true, and should be avoided), and took it to the illogical extreme, and so believes that exceptions should NEVER be used. Your mentor is wrong, and is setting the develoment team's coding practices back a decade.
It seems you already suspect that exceptions aren't really such a bad habit after all. If you need more convincing, ask yourself (and your team) these questions: Are you REALLY calling isErrorOccured() after every method you call? Do you find doing so tedious? When isErrorOccured() returns true, how is your application supposed to respond? I suspect you'll find that you're not doing yourself any favors by following this misguided advice.
Posted by: jimothy on October 07, 2003 at 01:24 PM
-
Design For Exceptions
jimothy's advice is spot-on. You've got code that would look like this:
ErrorVaule errorValue = yourMethod();
if(errorValue.didErrorHappen()
{
List errorList = errorValue.getErrorList();
doSomething(errorList);
}
With exceptions, it'd look like this:
try
{
yourMethod();
}
catch(YourException ye)
{
doSomething(ye.getErrorList());
}
So basically someone has told you to use return codes to replace what exceptions do for you.
What do you do when you want a method to return something? How unnatural does it feel?
To be fair, you are miles ahead of projects with no thoughts about what to do when things go wrong.
Dave
Posted by: dwalend on October 07, 2003 at 06:42 PM
-
Design For Exceptions
I read that Exceptions are to be used to handle exceptional conditions. Throwing an exception is a perfomance hit.
If the code expects certain situations or results those should be coded for, not handled with the exception mechanism.
If this is not true or not really applicable in many situations, what is the real explanation?
Posted by: jbetancourt on October 07, 2003 at 06:47 PM
-
Finding the exceptions...
jimothy, ba22a,
I usually lean on the compiler to let me know what exceptions I need to worry about. RuntimeExceptions are hidden; I'm lucky to find them in the javadoc of a method. That is, if I think to look.
Do you know good way of discovering that a method in a .jar might throw a RuntimeException?
When you're creating the method, how do you tell if you're in a RuntimeException case ("Something went wrong I don't know how to fix") or a checked Exception case ("Something went wrong you might know how to fix in a catch clause")?
I generally give the next programmer down the line the benefit of the doubt, and give him a checked Exception. I try to save RuntimeExceptions for "Something terrible happened. Send me a bug report."
Dave
Posted by: dwalend on October 07, 2003 at 06:55 PM
-
Performance Hit of Exceptions
jbetancourt,
Entering a try{} block is about the same as entering an if(){} block. Throwing an exception is just a little more expensive than a jump point in C. The big hit is constructing the Exception, a new Object, usually with a new String inside it, and (high on the stack) logging the Exception in the catch clause (lots more Strings).
If you're code has several likely branches that it'll follow, then coding those branches in if() or switch blocks makes sense. If it's got "one true path," exception handling makes your code much easier to read. I usually use if() checks to discover when to add "throw" statements. If nothing is wrong, I never create or throw the Exception.
Throwing an exception makes the contract for the method much easier to write; the contract ends with "... or it has no effect on system state and throws YourException."
Your flow of control entering catch blocks should be... um.. exceptional. Once things start breaking, performace is usually less important than fixing the problem or at least recording it so it can be fixed later.
Hope that helps,
Dave
Posted by: dwalend on October 07, 2003 at 07:13 PM
-
Finding the exceptions...
Dave -
"Do you know good way of discovering that a method in a .jar might throw a RuntimeException?"
The answer is obvious if you think about it - you should /always/ expect them to throw a RuntimeException.
Think about NullPointerException, or ConcurrentModificationException. These get thrown from code when you least expect it. However you can also javadoc runtime exceptions, even if they're not in the throws clause (look at the documentation of Method.invoke()).
The fact that RuntimeExceptions can and do happen all over the place speaks volumes about the lack of value of a checked exception that simply reports failure. Since the developer should be already be choosing to handle RuntimeExceptions (or not) - because they always expect them - all the "failed" checked exception does is force them to add a second catch clause.
"When you're creating the method, how do you tell if you're in a RuntimeException case"
It's a judgment call, I'll admit. However, it is easier to spot some things that should be checked exceptions, when there is some behaviour expected of the client when you throw it. I mentioned the servlet UAE before, there are other equally obvious examples: InvocationTargetException - "unwrap me!", InterruptedException - "wake up!", PropertyVetoException - "don't do it!".
It's not entirely guesswork though. When you add an exception to a throws clause you are (usually) writing code for some client to use - code also written by yourself, as a test harness or a real application. The question you have to answer is, does your own client code ever need to know the difference between exception A and exception B? If the answer is yes, it may need a checked exception. If the answer is no, use a runtime exception.
-Baz
PS I freely admit that I don't have all the answers and rules like the ones I gave are made to be broken. The only reason we're arguing the point is because the point is arguable :)
Posted by: ba22a on October 08, 2003 at 05:50 AM
-
Design For Exceptions
You're absolutely right that exceptions are used to handle exceptional conditions, and that there is overhead involved in throwing an exception. I suspect the developers javakishore is working with heard the same thing, and took this to mean that you should NEVER use exceptions (what I called in a previous post "the illogical extreme").
Let me use an example to illustrate the difference between exceptional conditions and other "error" conditions. Let's say you have a form (web based, Swing based, etc.) for a user to fill out, and some fields on this form are required. If the user leaves a field blank, should you throw an exception?
No, because this is not an exceptional condition; your program should expect that a user will enter invalid data or neglect to provide required information. You can, and should, deal with these situations without throwing or catching an exception.
Now, say you have a method, calculateShippingCharges() which is seperate from your UI code, and which is part of the "business logic" that processes the data that was collected from the form. If the destination addess is null or invalid, it's perfectly reasonable and likely appropriate to throw an exception here (perhaps NullPointerException or a custom exception, InvalidAddressException). Because calling calculateShippingCharges() with invalid data IS an exceptional condition.
But this situation is handled best not with a try...catch structure, but to validate the user's input before calling calculateShippingCharges(). Error handling is not synonymous with exception handling; the latter is a subset of the former.
To help remember this, use this rule of thumb: Exceptions, and stack traces in particular, are excellent tools for developers, but are completely useless (and frightening) to the end user. We've probably all used an application which presents an ugly stack trace on the screen when something goes wrong; heck, I'm guilty of writing such software myself. But doing so hurts, not helps, the user experience.
Posted by: jimothy on October 08, 2003 at 07:30 AM
-
Design For Exceptions
To add to Dave's points, exceptions become ever more convenient over error return codes when you call multiple methods, because you can place multiple dependent method calls in the same try block. Error return codes don't scale well. Instead of this:
ErrorVaule errorValue = yourMethod();
if(errorValue.didErrorHappen()
{
List errorList = errorValue.getErrorList();
doSomething(errorList);
}
else
{
//the first call was successful, so continue with
//the second one
errorValue = yourSecondMethod();
if (errorValue.didErrorHappen())
{
List errorList = errorValue.getErrorList();
doSomething(errorList);
}
else
{
//continue this pattern for your third, fourth, etc. methods
}
}
You can simply do this:
try
{
yourMethod();
yourSecondMethod();
yourThirdMethod();
//etc....
}
catch (YourException e)
{
//handle exception here
}
catch (YourOtherException e)
{
//handle other exception type here
}
If yourMethod() fails, yourSecondMethod() never gets called. Or if yourSecondMethod() fails, yourThirdMethod() never gets called. And this is exactly what you want, since yourSecondMethod() depends upon the success of yourMethod(). (When method calls are dependent on each other, and there is some way to recover from a failure, use separate try...catch blocks for increased granularity).
Dealing with error return codes is so tedious and non-scaleable that eventually developers stop checking for every possible error. And then things break and are hard to track down. Contrast that with an exception, which is going to give you a stack trace of where the error occurred, and what calls lead up to it.
Posted by: jimothy on October 08, 2003 at 07:47 AM
-
Finding the exceptions...
Maybe it comes down to this question: how bulletproof do you need your code to be?
realtime: your module better be able to handle almost any exception the base layers throw and be able to recover or your rocket is going to explode. But most of us don't write code for avionics or pacemakers, but if we did we might have try/finally blocks everywhere. Ada was designed for this.
desktop apps: some stuff is likely to go wrong. The dog will chew the network wire, you'll walk out of the starbucks during an IM conversation. This is what checked exceptions are for. You'll want to recover, retry, etc.... These apps will also get NPE and OOMEs, but you can't reasonably expect to handle those, except at a highlevel try { ...} catch (Throwable t) { showError(t); }. It just isn't worth the effort considering how likely these RuntimeExceptions will occur. A failing of C/C++/C# is that they have only unchecked exception mechanisms. C has return values and setjmp/longjmp, C++ has mostly unused (and unknown by many developers) unchecked exceptions, and C# suffers from the same problem.
scripting: lightweight coding for lightweight use, almost no thinking goes into handling exception cases but it usually doesn't matter. This essentially is using the highlevel exception catcher.
So, in my apps, I
- use checked exceptions to alert me to likely problems. IOException is a typical one.
- if I can't intelligently handle it at the level I'm at, I send it one level up (possibly wrapped). But I want to keep that checked exception around until I handle it.
- I handle it by retrying, recovering, or giving up, wrapping the exception in a unchecked exception and throwing it to a central handler.
- all code which needs explicit closes uses a finally*
Geez, I should create my own blog about this. :)
Posted by: jtr on October 08, 2003 at 10:59 AM
-
The purpose of exceptions...
My gut for the two types of exceptions has always been a contract-style feel. Runtime exceptions indicate that the caller has violated the contract calling the method (IllegalArgumentException, IllegalStateException) while a checked exception indicates the method could not fullfill the contract and return correct data (IOException, SQLException).
Posted by: jreed on October 08, 2003 at 02:46 PM
-
The purpose of exceptions...
I like that...that's probably the best , and most easily followed, strategy that I've heard. The other strategies, including ones that I've used myself, have "grey areas" where it seem you could go either way, but this one seems pretty clear-cut.
I'm mentally collecting a list of "best practices" for exception handling, and I plan to eventually write them up. I hope you don't mind if use your suggestion!
Posted by: jimothy on October 08, 2003 at 04:26 PM
-
More Talkback
Thanks for all the great talk-back. Wow. And from relative beginners to people with lots of experience and strong opinions. That's what java.net is for. Thanks especially to jimothy, for the great examples and for keeping the discussion going.
I limited the scope of the blog to "I keep Exceptions in throws clauses specific," but we're covering a lot more ground. Which is great. Perhaps I should have written more.
Posted by: dwalend on October 08, 2003 at 08:16 PM
-
The purpose of exceptions...
jreed, I like your breakdown, too. However, I encounter lots of unintentional NullPointerExceptions (RuntimeExceptions) from sloppy code with poorly documented contracts. How big do you think the disconnect is between the Exceptions we throw and the ones we handle?
Posted by: dwalend on October 08, 2003 at 08:17 PM
-
RuntimeExceptions
jimothy, ba22a, jtr, one of the big challenges I have is that people keep using my code long after I've left the project and moved on. I've been remarkably bad at predicting how and where my code will be used. I think I can state the problem this way -- I don't want my code's expected use to determine the level of exception handling.
ba22a is right-- we need to decide up front that we need to handle RuntimeExceptions. However, I think that RuntimeExceptions coming up from code indicate a bug in the code somewhere. (As opposed to checked Exceptions, which usually indicate a resource problem, and Errors, which indicate a problem with the JVM.) That seems true for most of the RuntimeExceptions in the standard API. But some of the Swing classes seem out of line with it. CannotUndoException seems like it should be checked. (Or do Swing programmers just disable undo instead?)
Posted by: dwalend on October 08, 2003 at 08:19 PM
-
Performance Hit of Exceptions
Thanks for the replies. I agree with the comments. And is what I tell others.
However, I have the suspicion that Exceptions are overused and just may illustrate lazy programming; or its just vague writings on the subject.
For example, whether a file exists or not is not an exception, but interruption of I/O streaming is. Yet, I have seen the former being handled wth a throw. Sure, exception use simplifies code but then I think that people who write on this subject be more precise in what they mean.
Further, if its simplification of control structures that are desired, then that should be addressed directly. For example, Eiffel added Design-by-contract stuff to the language. Now that is clean.
Posted by: jbetancourt on October 09, 2003 at 06:30 AM
-
FileNotFoundException
You raise some interesting points, and provide another good example of when it is appropriate to use exceptions, and when other methods of error handling are appropriate.
If you are right an application that asks the user for the name of a file, and then performs some operation on that file, you could deal with missing files in two ways: First, you could just attempt to perform the operation, catch FileNotFoundException, and report to the user that the file does not exist. Or, you could check whether the file exists BEFORE trying to perform the operation, and inform the user of the problem then. The latter would be a better approach, since your identifying the problem closer to the source, and at a time when there's an appropriate way to inform the user.
On the other hand, when you write the class and methods that actually perform the operation, throwing a FileNotFoundException is probably the appropriate thing to do. If instead you were to check for the existence of the file and if it does not exist, return normally without doing anything, the client code mentioned earlier has no way of knowing that something went wrong. If you pop-up a dialog at this point, you're tying your application logic to your user interface. That same file processor could not be used in a web application or a batch process.
And calling your file processor with a non-existent file IS a violation of the class or method contract, so an exception is appropriate here. The client code should verify that its part of the contract is fullfilled before calling the file processor, but the file processor should go ahead and throw an exception is that contract is violated.
I agree that writings to date on exceptions have been rather vague, and I'd say that exception handling is probably one of the most misunderstood aspects of Java, even among some very experienced developers I have worked with. I've been meaning to write on this subject myself, and the blogs and comments on Java.net have given me some excellent ideas (and motivation). The discussion of contracts has been especially insightful. Thanks everyone, and sorry, David, if I've hijacked your blog!
Posted by: jimothy on October 09, 2003 at 07:26 AM
-
FileNotFoundException
Your response has instigated a glimmer of thought in my humble noodle.
Perhaps, the degree of exception throwing is based on the tier, level, or componentness, of something. But, since you don't know beforehand (sometimes) what that is, the try catch finally stuff is the safest way to go.
Posted by: jbetancourt on October 09, 2003 at 11:29 AM
-
FileNotFoundException
I'm glad you're all having fun and getting something out of the blog. I hadn't predicted it. The whole point of java.net is to get us to have this sort of conversation. Keep going. Jimothy. If you want to drive the conversation, that's great. I only get to check in about once a day and things have been moving faster.
Have fun,
Dave
Posted by: dwalend on October 09, 2003 at 07:43 PM
-
FileNotFoundException
jbetancourt, do you think there's some way of putting it more tightly than "conservative pragmatism" on the Exception handling side, and "following a contract" on the throwing side? Or does that say enought?
Posted by: dwalend on October 09, 2003 at 07:47 PM
-
FileNotFoundException
Hmmm. I think that is on the right track. The throwing side is pretty good. But, the handling side 'must' react to the contract, so there is no real choice in the matter, unless one treats exception handling as the game 'hot potatoes' (which newbies and old, certainly do).
I think we can coin a useful 'phrase' that addresses both sides, perhaps from the realm of the legal profession? I can't think of any (I've yet to see one of those crime dramas on T.V).
Posted by: jbetancourt on November 30, 2003 at 08:24 AM
-
Thinking alike
Hi, Dave. Just stumbled across your article here. Not surprisingly it reads very much like mine over here.
Cheers,
Laird
Posted by: ljnelson on December 17, 2003 at 01:09 PM
|