Stop Catching Exceptions!

Motivation

It’s clear that a lot of programmers are uncomfortable with exceptions [1] [2]; in the feedback of an article I wrote about casting, it seemed that many programmers saw the throwing of a NullReferenceException at a cast to be an incredible catastrophe.

In this article, I’ll share a philosophy that I hope will help programmers overcome the widespread fear of exceptions. It’s motivated by five goals:

  1. Do no harm
  2. To write as little error handling code as possible,
  3. To think about error handling as little as possible
  4. To handle errors should handled correctly when possible,
  5. Otherwise errors should be handled sanely

To do that, I

  1. Use finally to stabilize program state when exceptions are thrown
  2. Catch and handle exceptions locally when the effects of the error are local and completely understood
  3. Wrap independent units of work in try-catch blocks to handle errors that have global impact

This isn’t the last word on error handling, but it avoids many of the pitfalls that people fall into with exceptions. By building upon this strategy, I believe it’s possible to develop an effective error handling strategy for most applications: future articles will build on this topic, so keep posted by subscribing to the Generation 5 RSS Feed.

The Tragedy of Checked Exceptions

Java’s done a lot of good, but checked exceptions are probably the worst legacy that Java has left us. Java has influenced Python, PHP, Javascript, C# and many of the popular languages that we use today. Unfortunately, checked exceptions taught Java programmers to catch exceptions prematurely, a habit that Java programmers carried into other languages, and has result in a code base that sets bad examples.

Most exceptions in Java are checked, which means that the compiler will give you an error if you write

[01] public void myMethod() {
[02]    throw new ItDidntWorkException()
[03] };

unless you either catch the exception inside myMethod or you replace line [01] with

[04] public void myMethod() throws ItDidntWorkException {

The compiler is also aware of any checked exceptions that are thrown by methods underneath myMethod, and forces you to either catch them inside myMethod or to declare them in the throws clause of myMethod.

I thought that this was a great idea when I started programming Java in 1995. With the hindsight of a decade, we can see that it’s a disaster. The trouble is that every time you call a method that throws an exception, you create an immediate crisis: you break the build. Rather than conciously planning an error handling strategy, programmers do something, anything, to make the compiler shut up. Very often you see people bypass exceptions entirely, like this:

[05] public void someMethod() {
[06]    try {
[07]       objectA.anotherMethod();
[08]    } catch(SubsystemAScrewedUpException ex) { };
[09] }

Often you get this instead:

[10]    try {
[11]       objectA.anotherMethod();
[12]    } catch(SubsystemAScrewedUp ex) {
[13]        // something ill-conceived to keep the compiler happy
[14]    }
[15]    // Meanwhile,  the programmer makes a mistake here because
[16]    // writing an exception handler broke his concentration

This violates the first principle, to “do no harm.” It’s simple, and often correct, to pass the exception up to the calling function,

[17] public int someMethod() throws SubsystemAScrewedUp {

But, this still breaks the build, because now every function that calls someMethod() needs to do something about the exception. Imagine a program of some complexity that’s maintained by a few programmers, in which method A() calls method B() which calls method C() all the way to method F().

The programmer who works on method F() can change the signature of that method, but he may not have the authority to change the signature of the methods above. Depending on the culture of the shop, he might be able to do it himself, he might talk about it with the other programmers over IM, he might need to get the signatures of three managers, or he might need to wait until the next group meeting. If they keep doing this, however, A() might end up with a signature like

[18] public String A() throws SubsystemAScrewedUp, IOException, SubsystemBScrewedUp,
[19]   WhateverExceptionVendorZCreatedToWrapANullPointerException, ...

This is getting out of hand, and they realize they can save themselvesa lot of suffering by just writing

[20] public int someMethod() throws Exception {

all the time, which is essentially disabling the checked exception mechanism. Let’s not dismiss the behavior of the compiler out of hand, however, because it’s teaching us an important lesson: error handling is a holistic property of a program: an error handled in method F() can have implications for methods A()…E(). Errors often break the assumption of encapsulation, and require a global strategy that’s applied consistently throughout the code.

PHP, C# and other post-Java languages tend to not support checked exceptions. Unfortunately, checked exception thinking has warped other langages, so you’ll find that catch statements are used neurotically everywhere.

Exception To The Rule: Handling Exceptions Locally

Sometimes you can anticipate an exception, and know what exact action to take. Consider the case of a compiler, or a program that processes a web form, which might find more than one error in user input. Imagine something like (in C#):

[21] List<string> errors=new List<string>();
[22] uint quantity=0;
[23] ... other data fields ...
[24]
[25] try {
[26]   quantity=UInt32.Parse(Params["Quantity"]);
[27] } catch(Exception ex) {
[28]   errors.Add("You must enter a valid quantity");
[29] }
[30]
[31] ... other parsers/validators ...
[32]
[33] if (errors.Empty()) {
[34]    ... update database,  display success page ...
[35] } else {
[36]    ... redraw form with error messages ...
[37] }

Here it makes sense to catch the exception locally, because the exceptions that can happen on line [22] are completely handled, and don’t have an effect on other parts of the application. The one complaint you might make is that I should be catching something more specific than Exception. Well, that would bulk the code up considerably and violate the DRY (don’t repeat yourself) principle: UInt32.Parse can throw three different exceptions: ArgumentNullException, FormatException, and OverflowException. On paper, the process of looking up the “Quantity” key in Params could throw an ArgumentNullException or a KeyNotFoundException.

I don’t think either ArgumentNullException can really happen, and I think the KeyNotFoundException would only occur in development, or if somebody was trying to submit the HTML form with an unauthorized program. Probably the best thing to do in either case would be to abort the script with a 500 error and log the details, but the error handling on line [24] is sane in that it prevents corruption of the database.

The handling of FormatException and OverflowException, in the other case, is fully correct. The user gets an error message that tells them what they need to do to fix the situation.

This example demonstrates a bit of why error handling is so difficult and why the perfect can be the enemy of the good: the real cause of an IOException could be a microscopic scratch on the surface of a hard drive, and operating system error, or the fact that somebody spilled a coke on a router in Detroit — diagnosing the problem and offering the right solution is an insoluble problem.

Fixing it up with finally

The first exception handling construct that should be on your fingertips is finally, not catch. Unfortunately, finally is a bit obscure: the pattern in most languages is

[38] try {
[39]    ... do something that might throw an exception ...
[40] } finally {
[41]    ... clean up ...
[42] }

The code in the finally clause get runs whether or not an exception is thrown in the try block. Finally is a great place to release resources, roll back transactions, and otherwise protect the state of the application by enforcing invariants. Let’s think back to the chain of methods A() through F(): with finally, the maintainer of B() can implement a local solution to a global problem that starts in F(): no matter what goes wrong downstream, B() can repair invariants and repair the damage. For instance, if B()’s job is to write something into a transactional data store, B() can do something like:

[43] Transaction tx=new Transaction();
[44] try {
[45]    ...
[46]    C();
[47]    ...
[48]    tx.Commit();
[49] } finally {
[50]    if (tx.Open)
[51]        tx.Rollback();
[52] }

This lets the maintainer of B() act defensively, and offer the guarantee that the persistent data store won’t get corrupted because of an exception that was thrown in the try block. Because B() isn’t catching the exception, it can do this without depriving upstream methods, such as A() from doing the same.

C# gets extra points because it has syntactic sugar that makes a simple case simple: The using directive accepts an IDisposable as an argument and wraps the block after it with a finally clause that calls the Dispose() method of the IDisposable. ASP.NET applications can fail catastrophically if you don’t Dispose() database connections and result sets, so

[53] using (var reader=sqlCommand.ExecuteReader()) {
[54]   ... scroll through result set ...
[55] }

is a widespread and effective pattern.

PHP loses points because it doesn’t support finally. Granted, finally isn’t as important in PHP, because all resources are released when a PHP script ends. The absense of finally, however, encourages PHP programmers to overuse catch, which perpetuates exception phobia. The PHP developers are adding great features to PHP 5.3, such as late static binding, so we can hope that they’ll change their mind and bring us a finally clause.

Where should you catch exceptions?

At high levels of your code, you should wrap units of work in a try-catch block. A unit of work is something that makes sense to either give up on or retry. Let’s work out a few simple examples:

Scripty Command line program: This program is going to be used predominantly by the person who wrote it and close associates, so it’s acceptable for the program to print a stack trace if it fails. The “unit of work” is the whole program.

Command line script that processes a million records: It’s likely that some records are corrupted or may trigger bugs in the program. Here it’s reasonable for the “unit of work” to be the processing of a single record. Records that cause exceptions should be logged, together with a stack trace of the exception.

Web application: For a typical web application in PHP, JSP or ASP.NET, the “unit of work” is the web request. Ideally the application returns a “500 Internal Error”, displays a message to the user (that’s useful but not overly revealing) and logs the stack trace (and possibly other information) so the problem can be investigated. If the application is in debugging mode, it’s sane to display the stack trace to the web browser.

GUI application: The “unit of work” is most often an event handler that’s called by the GUI framework. You push a button, something does wrong, then what? Unlike server-side web applications, which tend to assume that exceptions don’t involve corruption of static memory or of a database, GUI applications tend to shut down when they experience unexpected exceptions. [3] As a result, GUI applications tend to need infrastructure to convert common and predictable exceptions (such as network timeouts) into human readable error messages.

Mail server: A mail server stores messages in a queue and delivers them over a unreliable network. Exceptions occur because of full disks (locally or remote), network failures, DNS misconfigurations, remote server falures, and an occasionaly cosmic ray. The “unit of work” is the delivery of a single message. If an exception is thrown during delivery of the message, it stays in the queue: the mail server attempts to resend on a schedule, discarding it if it is unable to deliver after seven days.

What should you do when you’ve caught one?

That’s the subject of another article. Subscribe to my RSS feed if you want to read it when it’s ready. For now, I’ll enumerate a few questions to think about:

  1. What do tell the end user?
  2. What do you tell the developer?
  3. What do you tell the sysadmin?
  4. Will the error clear if up if we try to repeat this unit of work again?
  5. How long would we need to wait?
  6. Could we do something else instead?
  7. Did the error happen because the state of the application is corrupted?
  8. Did the error cause the state of the application to get corrupted?

Conclusion

Error handling is tough. Because errors come from many sources such as software defects, bad user input, configuration mistakes, and both permanent and transient hardware failures, it’s impossible for a developer to anticipate and perfectly handle everything that can go wrong. Exceptions are an excellent method of separating error handling logic from the normal flow of programs, but many programmers are too eager to catch exceptions: this either causes errors to be ignores, or entangles error handling with mainline logic, complicating both. The long term impact is that many programmers are afraid of exceptions and turn to return values as an error signals, which is a step backwards.

A strategy that (i) uses finally as the first resort for containing corrupting and maintaining invariants, (ii) uses catch locally when the exceptions thrown in an area are completely understood, and (iii) surrounds independent units of work with try-catch blocks is an effective basis for using exceptions that can be built upon to develop an exception handling policy for a particular application.

Error handling is a topic that I spend entirely too much time thinking about, so I’ll be writing about it more. Subscribe to my RSS Feed if you think I’ve got something worthwhile to say.

kick it on DotNetKicks.com

Paul Houle on July 31st 2008 in Dot Net, Exceptions, Java, PHP

17 Responses to “Stop Catching Exceptions!”

  1. Matt responded on 05 Aug 2008 at 9:45 am #

    You’ve made a very common mistake in this post - you’ve confused Error handling and exception handling.

    Your C# example here:

    [code]
    [21] List errors=new List();
    [22] uint quantity=0;
    [23] … other data fields …
    [24]
    [25] try {
    [26] quantity=UInt32.Parse(Params["Quantity"]);
    [27] } catch(Exception ex) {
    [28] errors.Add(”You must enter a valid quantity”);
    [29] }
    [30]
    [31] … other parsers/validators …
    [32]
    [33] if (errors.Empty()) {
    [34] … update database, display success page …
    [35] } else {
    [36] … redraw form with error messages …
    [37] }
    [/code]

    Is using Exception handling to do Error handling. If you can anticipate an Error, it’s not an appropriate case for Exception handling. Exceptions are, by definition, unexpected. People entering a wrong type of value is pretty expected.

    You can (and should) replace the Try/Catch block with a reasonable If statement that more clearly describes what you’re trying to do.

    In pseudocode:
    [code]
    if (IsInteger(Params(”quantity”)) {
    Assign variable
    } else {
    Add exception to error array/object
    }
    [/code]

    And the rest works as is.

    If you save Exceptions for handling big problems (what do you mean, the file is missing?) and use realistic, sane error handling for run of the mill problems, your code will be easier to read, easier to maintain, and you’ll be saving yourself a ton of problems later on.

  2. Tech Per responded on 05 Aug 2008 at 12:40 pm #

    Not that long ago, I would have agreed with you on more points, than I do today. I, as you, thought checked exceptions were great, back in the day when Java came to. And, like you, I also felt burned by this, over the years.

    But, I have come to the conclusion, that there is a need for checked exceptions too, and as such, I think the .Net platform is wrong, in only providing support for runtime exceptions.

    What frustrates me, is two-fold:

    1) That programmers often misuse the concept of checked exceptions, for something that should have been a runtime exception.

    2) That, when coding APIs, it is hard, damned hard, to have the foresight into all possible uses of your API. And as such, it is often hard/impossible, to know if an exception should be checked or runtime.

    Ad 1) Given exceptions like IOException, UnsupportedEncodingException and SQLException in the Java library are checked, was a big mistake. In my eyes, these are prime candidates for runtime exceptions.

    So, if more exceptions were runtime exceptions, I still think, that there is a use for checked exceptions. I could ask, what would you do if they were not there?

    Checked exceptions are for a) exceptional conditions that b) you think the caller is capable of handling and doing something sane about, except log and terminate. By making them checked, you force the caller, to take action from the exceptional conditions. Back in the days of C, an error often was indicated with a special return code value. And often, programmers forgot to check for error conditions, leading to hard-to-find errors, at runtime, somewhere long from where the actual error occurred in the program.

    You give an example, where you show people using empty catch-blocks, to shut-up the compiler. This is just plain stupid coding from these people, and misuse of the language, and should be punished. It is no case against checked exceptions.

  3. Steve responded on 05 Aug 2008 at 9:48 pm #

    Very good read :-)

  4. Wolter responded on 05 Aug 2008 at 11:05 pm #

    An excellent article. I couldn’t have put it better myself.

  5. Sergej Andrejev responded on 06 Aug 2008 at 12:50 am #

    In my oppinion Libraries shouldn’t catch exceptions at all in most cases except when this exception is expected. But then again if an exception was expected maybe you should write some if instead.

    Going back to where exception must be cought, I catch them as near to the main function as possible. Most often this is main function itself. When I get exception I decide whether to display it to user or to handle it silently if it won’t break the program (in loop for example). But wherether I do I log all exceptions withing the main method, so I can always review what have happened later.

  6. Wolter responded on 06 Aug 2008 at 8:38 am #

    It is a common misconception to assume that exceptions must be unexpected.

    An exception means an exceptional situation has occurred. i.e. something that doesn’t happen when things are running according to plan.

    Something like ConnectionFailedException or FileNotFoundException or BufferNotReadyException and such are not in the least bit unexpected. Things may not be ready when you want them to be. Services may be down or too busy to accept requests. A file that is supposed to be made available by another process may be delayed.

    The whole point is that you write your unit of work under the assumption that things will go according to plan, and then you tell it what to do when something exceptional occurs. This keeps the intent of the unit of work clean and readable, and reduces repetition (a similar though inferior trick is accomplished in C using a forward goto). The alternative is a bunch of if statements after almost every method call with error handling code mixed in.

    If an exception is thrown due to an unexpected situation, how could you possibly expect to cope with it? You don’t even know why it happened! The best you could do in such a situation is bail out, and that pretty much defeats the purpose of catching exceptions in the first place.

    @Tech Per:

    Your argument for checked exceptions stems from a “nanny” approach to software development. You assume that the programmer is incapable of taking care of himself, and as such you must force him to do what you think is best for him. This is precisely the thought process that led to IOException and friends in the first place.

    You are not psychic. You don’t know how people will use your library, so what right do you have to force them to jump through hoops for your checked exceptions? All you’re doing is preventing your library from functioning cleanly in middleware frameworks, which have to use ugly wrap/unwrap hacks to propagate errors.

    Bad programmers will do bad things in the code no matter how much railing you put around the crib. Why not make things easy for GOOD programmers? They’re the ones who need tools that help them become more productive.

  7. Kit Peters responded on 06 Aug 2008 at 10:44 am #

    It’s a good read, and food for thought. The only issue I have is in suggesting that in a command line script to insert a million records, you make your “unit of work” a single record. While I can see the usefulness of this idea, making the UOW a single record means that the try/catch evaluation (or whatever mechanism you use in the language you write in) must be evaluated for *every* record, thus leading to a lot of extra overhead.

    Better, I think, to break the dataset up into manageable blocks, in this case perhaps 100,000 records or smaller. You can still have your exception handling, but you reduce your overhead. If the dataset contains exception-causing data, however, this leads to a condition where you know the error lies *somewhere* in a given block, but not its exact location. In that case, perhaps you could hand it off to a process that breaks it up into smaller chunks.

  8. Bob responded on 06 Aug 2008 at 4:02 pm #

    All you “checked exceptions are bad” people are like people who think distributed systems should behave just like local systems. The network is always up right? When it goes down we don’t know what will happen!

    When there’s a checked exception in an API it’s basically saying that something is likely to go wrong here. IOException happen when the disk fills up, SQLExceptions happen when the database goes down, etc. Just because that doesn’t happen most of the time doesn’t mean your program should just merrilly pretend it couldn’t happen and fail in an unexpected way when these conditions occur.

  9. Wolter responded on 06 Aug 2008 at 5:48 pm #

    @Bob:

    No, all us “checked exceptions are bad” people believe it should be up to the programmer to decide when, where, and if he deals with a particular exception, and that he shouldn’t be hindered with extra boilerplate by people who think it necessary to force awareness of a particular exception.

    Unchecked exceptions can handle distributed system issues perfectly well. This is irrelevant to the discussion.

  10. Przemek Klosowski responded on 06 Aug 2008 at 6:39 pm #

    A module’s encapsulation should include the exceptions it throws. In your argument about ‘breaking the build’, the problem is that the bottom-most module
    breaks encapsulation by generating new “uncontracted’ exceptions.

    A proper module should catch all exceptions in its own methods, and throw just the few (or one) exception, ModuleXyzzyBarfed. After all, if properly abstracted, it shouldn’t matter what specifically happened in this specific implementation—all the higher-level modules need to know is that this module failed.

  11. paan responded on 07 Aug 2008 at 10:46 pm #

    I don’t know.. I might be totally wrong here but..
    If “programmers often misuse the concept of checked exceptions” , doesn’t that mean that it is the programmer that should learn the concept of checked exception handling.

    And if the programmer is have a hard time with his project because his programming language is a language that uses checked exception then either a)the language of choice for the project is not really suitable for the project or b)the programmer is not suitable/capable for the project

    It’s like a php programmer that is complaining about the disadvantages of a loosely typed language.

  12. Paul Houle responded on 12 Aug 2008 at 12:48 pm #

    I’m sorry it took so long for me to reply to the excellent comments this post received — this post received so much attention that it crashed my VPS, so I spent the weekend setting up a new server.

    @Matt’s got an interesting point. The Try-* pattern is appropriate for parsing text. Error handling based on return values gets into trouble quickly, however, if the effect of an error needs to propagate back up the call stack. I used the example I did because it was a simple example where catching an exception locally is appropriate — it’s not accidental that this coincides where cases where return values work.

    @Kit has probably built bulk loaders that batch together groups of records into transactions for speed. I’ve seen this increase the speed of batch loads by factors of ten or more — that matters when loads take several hours! What he suggests is a premature optimization for many people, but necessary for large jobs.

    I disagree with @Bob. In many cases, the right answer for many networking-based exceptions is to retry the “unit of work,” often with a delay. This is how e-mail delivery and large-scale webcrawlers work.

    @Klosowski, I think encapsulation is a useful idea, but it’s also a partial truth, particularly when things go wrong. A “SubsystemAFailed” exception ~could~ be designed to give the caller useful semantics about what to do (answers for the eight questions) but it usually doesn’t. Reusing system-defined exceptions “does no harm” by avoiding information loss.

    @paan, I think it’s more complicated than the effect that a language feature has on a particular programmer, but more the effect that a feature has on the ecosystem around a language. In particular, error handling is a place where local decisions have global consequences. Even if you were an absolute master at using checked exceptions, you’ll (i) be calling code written by people who are bad at it, and (ii) having your code called by people who are bad it. Since your code will have to deal with other people’s exceptions, and will throw exceptions into other people’s call stacks, you can’t escape from the problems checked exceptions create.

  13. the real deal responded on 12 Aug 2008 at 3:56 pm #

    Good article, but still kinda misses the real problem: abuse of exception syntax for domain logic. First comment (by ‘Matt’) addresses this to some extent.

    The real tragedy of exceptions, checked or otherwise, is that a generation of programmers uses a convoluted syntactic construction for many situations that should be analysed, modeled, and handled as domain logic.

    The web form in the article is an example: user data that does not ‘fit’ either format or value is not an exception situation, it is to be expected and handled explicitly.

    There is absolutely nothing wrong with plain old return values and error codes. Exceptions should not be used for anything which could be anticipated in the domain logic of whatever layer or abstraction is currently applicable.

    There are some legitimate situations where exceptions are the right way, but they are very very few indeed. It is only when the abstraction is broken: for example, device failure (not, emphatically not, network failure: if you’re doing anything with sockets or networks you damn well better expect to program retry and link down logic).

    Using exceptions for domain logic is mental laziness disguised as a ‘modern’ technique.

  14. Paul Houle responded on 12 Aug 2008 at 4:03 pm #

    @Real, I’d like a more specific explanation of what you call “Domain Logic.”

  15. Karl responded on 14 Aug 2008 at 12:05 pm #

    @real: Let me show you how exceptions are used for expected situations in real business logic in an enterprise system:

    public class BusinessRuleSet
    {
    private Set rules = new HashSet();

    public void addRule(BusinessRule rule)
    {
    rules.add(rule);
    }

    public void addRules(BusinessRuleSet ruleSet)
    {
    this.rules.addAll(ruleSet.rules);
    }

    public void applyTo(int recordIndex, Object subject) throws BusinessRulesFailedException
    {
    BusinessRulesFailedException errors = new BusinessRulesFailedException();

    for ( Iterator iter = rules.iterator(); iter.hasNext(); )
    {
    try
    {
    ((BusinessRule)iter.next()).applyTo(recordIndex, subject);
    }
    catch ( BusinessRuleException e )
    {
    errors.addError(e);
    }
    }

    if ( errors.hasErrors() )
    {
    throw errors;
    }
    }
    }

    This is called via the following mechanism:

    public void create(Collection records) throws BusinessRulesFailedException
    {
    // Preamble to test for user access and such.

    int index = 0;
    for(SomeType record: records)
    {
    createRules.applyTo(index, record);
    index++;
    }

    // Call data layer to create the records.

    }

    This has been simplified for clarity.
    Business rule objects evaluate a subject to test a particular business rule. If the object is in violation of that rule, it throws a BusinessRuleException.

    The BusinessRuleSet object captures these exceptions and builds up a master BusinessRulesFailedException, which contains all of the rules that failed.

    The BusinessRulesFailedException bubbles up out of the business layer, into the UI layer where the call to create() was made.

    The UI catches this exception and then sifts through the individual exceptions contained within so that it can write out each error for each field in the data entry screen where a business error was triggered.

    The exceptions themselves are unchecked (though declared). It is up to the programmer to decide what he wants to do, and where.

    And for good reason too, as checked exceptions make it difficult to elegantly build higher order constructs.
    For example, as a result of checked exceptions, many middleware products have to declare “throws Exception” (or wrap it in another exception) since they have NO idea what could be thrown from underneath. Now everything built on top of this has to declare “throws Exception” (or manually unwrap the middleware exception, which causes a big ugly mess of evaluation code just to figure out what happened). How useful is that?
    The programmer using the product, however, will likely know what kind of exceptions will be coming out of it (or at least the ones he cares to deal with), and can handle them at the appropriate place.

  16. Paul Houle responded on 14 Aug 2008 at 6:08 pm #

    @Karl,

    I like that. One of the strengths of exceptions is that they are objects that can be manipulated outside of the try-catch-finally system. As you demonstrate, exceptions can be caught and then stuffed into a data structure: then they can be inspected later. It’s also possible to create an exception, store it in a data structure, and throw it at another point, which is useful for fault injection. Thanks for the example!

  17. Generation 5 » What do you do when you’ve caught an exception? responded on 27 Aug 2008 at 8:19 am #

    [...] For the most part, the code inside DoUnitOfWork() and the functions it calls tries to throw exceptions upward rather than catch them. [...]

Trackback URI | Comments RSS

Leave a Reply