Where should exceptions go?

  • strict warning: Non-static method view::load() should not be called statically in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/views.module on line 906.
  • strict warning: Declaration of views_handler_argument::init() should be compatible with views_handler::init(&$view, $options) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/views_handler_argument.inc on line 744.
  • strict warning: Declaration of views_handler_filter::options_validate() should be compatible with views_handler::options_validate($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/views_handler_filter.inc on line 607.
  • strict warning: Declaration of views_handler_filter::options_submit() should be compatible with views_handler::options_submit($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/views_handler_filter.inc on line 607.
  • strict warning: Declaration of views_handler_filter_boolean_operator::value_validate() should be compatible with views_handler_filter::value_validate($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc on line 159.
Leeland's picture

Lets talk about exceptions for a moment. At the office a discussion came up that essentially was about if we should group our exception objects together into a single module/subsystem wide package or have them called out in the packages next to the code they were used by.

Personally I think packages specific to exceptions are bad ideas because it detaches them from the business objects they are supposed to be supporting. An exception should represent the possible result of a direct action on a domain object. I think they should be subsystem/object specific.

Lets play with a simple banking example where you have accounts, credit activity, debit activity, etc. So we have Account objects, Credit objects, and some others (you get the idea).

Now some common terminology:

  • Invalid - provided value is not valid;
  • Illegal - provided value is valid, but used out of context; and
  • Duplicate - provided value is already used by another entity in the system.

Now we can ask some simple questions that will drive the design of the exceptions:

Account objects:

What could go wrong when defining account types?

  • com.ourbank.account.types.DuplicateAccountTypeException
  • com.ourbank.account.types.AccountTypeDataValidationException

What could go wrong when creating an account?

  • com.ourbank.account.DuplicateAccountIdException
  • com.ourbank.account.DuplicateConsumerIdException
  • com.ourbank.account.InvalidAccountTypeException
  • com.ourbank.account.InvalidAccountStateException
  • com.ourbank.account.IllegalAccountStateException
  • com.ourbank.account.AccountDataValidationException

What could go wrong when updating an account? (notice some of these are duplicated from above - but that's ok)

  • com.ourbank.account.InvalidAccountException
  • com.ourbank.account.IllegalAccountStateException
  • com.ourbank.account.AccountDataValidationException

What could go wrong when creating credits? Most problems with creating credits are actually problems with accounts.

  • com.ourbank.account.InvalidAccountException
  • com.ourbank.account.IllegalAccountStateException
  • com.ourbank.account.InvalidAccountTypeException
  • com.ourbank.ledger.IllegalCreditAmountException

What could go wrong with consuming credits (debits)?

  • com.ourbank.account.IllegalAccountStateException
  • com.ourbank.ledger.AvaliableBalanceExceededException
  • com.ourbank.ledger.AvailableBalanceReservedException
  • com.ourbank.ledger.IllegalDebitAmountException

Are there commonalities to the 'types' of exceptions we use? For now, looking at a high-level, we're allowing both regular and unchecked exceptions:

  • com.ourbank.AbstractPaymentsException
  • com.ourbank.AbstractUncheckedPaymentsException

By now I hope you are seeing the patterns...

This now makes it easier to determine if an exception is really applicable on a specific object and even may cause you to second-guess your design (which is always a good thing). When you put them all together in a single package then you start looking at them as engineering artifacts and not as domain artifacts. This leads to more cross-dependencies and possible "spaghetti" outcomes.

Whether you put them in a "model" package really doesn't matter but they really are part of the domain and follow the same support requirements so they should be treated the same as any other domain object.

Thread Slivers eBook at Amazon