2

I am designing a library with a bunch of interfaces with annotated exceptions. (The example is in (pseudo)PHP, but I am sure one could do something equivalent in Java or other.)

For now, the interfaces have some exceptions in common (e.g. DogProvider and CatProvider both throw an AnimalProviderException). But there are cases where I have to wrap one exception into another. In fact, I find myself doing this a lot, and I have the feeling it is not really helpful.

As I understand, different exception classes allow to have dedicated catch blocks, with different error handling for different exception types.

And the wrapping or re-throwing of caught exceptions would allow to add more useful contextual information.

But sometimes, or often, I find it to be just a re-branding, for the sake of the interface contract. (which is by convention in PHP, but would be enforced in Java)

interface DogProviderInterface {
  /** @throws AnimalProviderException */
  function getDog() : DogInterface;
}

interface CatProviderInterface {
  /** @throws AnimalProviderException */
  function getCat() : CatInterface;
}

interface DogToCatInterface {
  /** @throws AnimalConversionException */
  function dogGetCat(DogInterface $dog) : CatInterface;
}

class CatProvider_FromDogProvider {
  [..]
  /** @throws AnimalConversionException */
  function getCat(DogInterface $dog) : CatInterface {
    try {
      $dog = $this->dogProvider->getDog();
      return $this->dogToCat->dogGetCat($dog);
    }
    catch (AnimalConversionException $e) {
      throw new AnimalProviderException('Could retrieve a dog, but failed to convert it to a cat.', $e);
    }
  }
}

Obviously, if I am working with 3rd party interfaces, I just have to do the rebranding as required by the interface.

But if I have am designing the interfaces myself:


The question(s):

  • How granular should I design the annotated exceptions?
  • When should I let two interfaces annotate the same exception, and when should they be different?
  • If a method should really only throw one type of exception, and I am not going to add any extra functionality to my custom exception class, then the calling code will only have one catch block anyway, and it will be all about "didn't work, deal with it.". So what's the point then?
  • What are typical cases where calling code would actually behave differently based on the type of exception?

E.g. in the above case, most of the calling code won't care. It simply has to live with the fact that there is no cat available from the cat provider.

Useful might be a distinction between, e.g. for an animal conversion:

  • "this DogToCat instance is broken / misconfigured, so stop using it." vs
  • "the dog provided is broken / misconfigured, so it cannot be turned into a cat" vs
  • "the DogToCat instance is ok, the dog is ok, but we ran out of cats to replace the dog"

But the usual "one exception type per interface" does not really help with this, it seems.

In a dynamic language, and especially one with a weak type system such as PHP, often we want to make assumptions about parameters and other things, which cannot be enforced at "compile time". Object composition can be driven by a DIC definition array, which can really be application-specific.

E.g. if we get a dog from the dog provider, and then replace it with a cat via DogToCatInterface::dogGetCat($dog), and dogGetCat($dog) fails, this could be a sign that the dogs from the DogToCat implementation does not really handle the dogs from the DogProvider implementation, even if they are valid instances of DogInterface. This could means that the composite cat provider was poorly constructed.

See also:


Here is another example, more detailed, this time in pseudo-Java. Actually I wrote the same thing in PHP, but the Java syntax allows more concise type hints and "throws" declarations.

For the sake of the example I created interfaces for what would be just array (RowInterface) or object (ObjectInterface) in PHP. Obviously, generics would allow a more specific return type than just "ObjectInterface", but this is not currently possible in PHP, and would not really add anything to this example.

I am using a pseudo-value "EOF" instead of "false", which just means that a stream has no more data.

Regarding exception types, this code finds a "middle ground": Some methods use the same exception class, but others don't.

Introducing more distinct exception classes, and using them on the "throws" declarations of the interfaces, would also increase the need for try/catch/throw re-branding.

Obviously, if the implementation for RowStreamInterface is based on a CSV file, we would get yet another group of exception classes (e.g. file not found, invalid csv, etc.) which would need to be re-branded with try/catch to either StreamInitException or StreamFetchException.

Homegenizing the declarations, e.g. if every method just declares a throws DidntWorkException, would reduce the need for try/catch/throw re-branding.

I personally feel that the re-branding is annoying, and that most calling code does not really care about the exception type. But a lot of people seem to recommend dedicated exception classes.

// A row (array of strings), e.g. from a CSV file.
interface RowInterface {..}

// Structured key/value data object.
interface ObjectInterface {..}

// Convert a row to a key/value data object, e.g. by mapping indices to keys,
// and by applying string-to-value conversions.
interface RowToObjectInterface {
  // The exception is thrown if the row does not have enough columns/cells, or
  // if one of the string values does not match the expected format.
  public ObjectInterface rowGetObject(RowInterface row) throws DataConversionException;
}

// This one could be used inside a RowToObject implementation:
interface StringToValue {
  public mixed stringGetValue(string str) throws DataConversionException;
}

// Allows to fetch rows until EOF, e.g. from a CSV file.
interface RowStreamInterface {
  public RowInterface|EOF fetchRow() throws StreamFetchException;
}

// Allows to fetch key/value data objects until EOF.
interface ObjectStreamInterface {
  public ObjectInterface|EOF fetchObject() throws StreamFetchException;
}

// Can create fresh row streams, e.g. from a CSV file.
interface RowStreamProviderInterface {
  public RowStreamInterface createRowStream() throws StreamInitException;
}

// Can create fresh object streams, 
interface ObjectStreamProviderInterface {
  public ObjectStreamInterface createObjectStream() throws StreamInitException;
}

class ObjectStream_FromRowStream implements ObjectStreamInterface {
  ..
  public ObjectStream_RowToObject(RowStreamInterface rowStream, RowInterface headRow, RowToObjectInterface rowToObject) throws StreamInitException {
    ..
  }
  public ObjectInterface|EOF fetchObject() throws StreamFetchException {
    if (EOF === row = this->rowStream->fetchRow()) {
      return EOF;
    }
    try {
      return this->rowToObject->rowGetObject(row);
    }
    catch (DataConversionException e) {
      // Exception re-branding to fulfill interface contract.
      throw new StreamFetchException('Fetched a row, then failed to convert to a data object.', e);
    }
  }
}

class ObjectStreamProvider_RowToObject implements ObjectStreamProviderInterface {
  ..
  public ObjectStreamInterface createObjectStream() throws StreamInitException {
    rowStream = this->rowStreamProvider->createRowStream();
    // The head row contains column labels.
    try {
      headRow = rowStream->getHeadRow();
    }
    catch (StreamFetchException e) {
      throw new StreamInitException("Failed to fetch head row, could not create object stream.", e);
    }
    // rowToObject will be different depending on order of columns in headRow.
    rowToObject = this->headRowGetRowToObject(headRow);
    return new ObjectStream_RowToObject(rowStream, headRow, rowToObject);   
  }

  // Create a RowToObject converter based on the head row.
  private RowToObjectInterface headRowGetRowToObject(RowInterface headRow) throws StreamInitException {
    try {
      return new RowToObject(..);  // This could throw a ConstructionException
    }
    catch (ConstructionException e) {
      throw new StreamInitException("Failed to construct the RowToObject instance that is needed to start the object stream.");
    }
  }
}

In practice I would use the code like this:

objectStream = objectStreamProvider->createObjectStream();
while (EOF !== object = objectStream->fetchObject()) {
  doSomethingWith(object);
}
  • You might have better luck getting an answer if you provide some more realistic examples, rather than the contrived Dog and Cat ones. – Robert Harvey Feb 25 '16 at 4:02
  • I can do that, but then I would replace a lot in the entire question.. hope that does not make it a "moving target".. – donquixote Feb 25 '16 at 6:01
  • Why would it become a moving target, if it's the very same question but with clearer examples? – Robert Harvey Feb 25 '16 at 6:12
  • I appended a more detailed example. – donquixote Feb 25 '16 at 6:57
  • 1
    An article I completely agree with, and the reason why Microsoft never introduced checked exceptions in c#. – Robert Harvey Feb 25 '16 at 15:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.