After some excellent feedback from the Moose list, I thought I could share with you an issue that I know others have been concerned about. What follows are my thoughts on the matter, but I'd very much like to hear advice from others.

Consider the following:

package Foo; use Moose; has 'some_value' => ( is => 'ro', isa => 'Int', );

When somebody does this:

# value contains the string "No charge" my $foo = Foo->new({ some_value => $value });

Do you want a 500 error on your Web site or a page which only displays partial information?

There is not a single person reading this entry now how can honestly say what the correct answer is. If you think you know which of the two choices above is correct, you need more experience.

The problem here is that you can't know the answer to that without understanding the underlying problem you're trying to solve and that might involve understanding a heck of a lot about your business. So I have a dilemma.

This example is strictly hypothetical as the code in question is core business code which I cannot share, but the underlying problem is real and serious. In fact, the business problem I describe here is actually an amalgam of business practice of a few of the larger companies I've worked for.

Imagine that you sell hotel rooms and you have three types: "luxury", "standard" and "budget". You may have a "room_type" attribute used in a few places, but you want its data type in one place:

package My::Company::TypeConstraints; use Any::Moose; use Any::Moose '::Util::TypeConstraints'; BEGIN { enum 'RoomType' => qw( luxury standard budget ); }

Now you can just declare that an attribute isa "RoomType" and when an outside supplier sends you XML describing available rooms and they have a room type of "executive", it blows up and they now can no longer tell you which rooms you can sell. Not only does this cost you money, but it costs them money too. It could easily hurt your reputation.

Now imagine that you have many, many of these attributes coming from a wide variety of sources and in a wide variety of formats taking a wide variety of paths through your code.

They are sending data via SOAP, RPC, JSON, email, telnet, call centers, etc. You've trained all of your internal staff and external suppliers on what the myriad data types are, but you have staff turnover, your code has bugs, their code has bugs, they in turn may have external data suppliers who may not be aware of constraints, and so on.

In short, there are a lot of ways that bad data can enter your system, but I don't have to tell you that. You already know it. If this bad data is causing you to lose €1,000 a minute, failure to notice it for an hour or so can you lose a developer's annual salary.

But what do we do? Let's step back for a minute and think about business needs rather than technical ones (they're often one and the same).

Most (all?) developers will accept that virtually all software has bugs. Larger companies tend to have more software than smaller companies and thus have more bugs. So why don't they collapse in a steaming pile of ones and zeroes?

All bugs are created equal, but some are more equal than others. — Aldous Huxley, creator of the Suidae programming language

It's probably pretty bad if you automatically ship a new car for only €53, but it's probably not so bad if you show the wrong sized thumbnail of said car.

So in the room_type example above, what could we do? We could have this:

has 'room_type' => ( is => 'ro', isa => 'RoomType', );

Now if an outside supplier sends the "executive" room type, we get a fatal error. That might be exactly the behaviour you want, but you don't know that if you don't know your business rules. It might mean that you've just lost a lot of money. So you could do this:

has 'room_type' => ( is => 'ro', isa => 'Str', );

But now we're accepting anything at all and our code might break in other, delightful ways. Further, we won't really know who is sending bad data and who is not. One way of handling this is the following.

package My::Company::TypeConstraints; use Any::Moose; use Any::Moose '::Util::TypeConstraints'; BEGIN { my %is_valid_room_type = map { $_ => 1 } qw( luxury standard budget ); if ( IS_NOT_PRODUCTION_SERVER ) { enum 'RoomType' => keys %is_valid_room_type; } else { coerce 'RoomType', from 'Str', via => { return $_ if $is_valid_room_type{$_}; LOG "'$_' is not a valid room type"; return 'standard'; }; } }

I'm sure there are better ways to write that and I'd love built-in support for this. However, not only am I not sure of the best way to genericize this, I am not sure that this is the best approach. However, what do we get?

For non-production uses (e.g., test suites), we can get fatal errors for type constraint violations. For production uses, we get sensible defaults and a nice warning in our logs to tell us that there's an issue. For a production system, you get fault-tolerance and logging of said faults. In other words, you bend rather than break.

This seems like a win-win scenario. You don't throw away type constraints entirely. You might just find out that it's your data consumer which is in error and not the data source. You can have a more gradual transition to Moose/Mouse from your hand-rolled code and you have a great strategy for handling uncertainty about exactly what constraints should be on a particular attribute.

For smaller systems, I'd probably not feel comfortable with this approach. For larger systems, there's a time when you need to balance the desire for "correct" code and "paying your mortgage".

I finish with quote from another brilliant programmer.