One suggestion. I would prefer to have your get() routine barf if you tried to get a non-existent attribute instead of proceeding. This allows you to catch typos in names at run-time.
Actually, in the original code, it printed out an error, then continued. And, returning undef does barf, if you're bothering to check your return values for error before continuing. If you're not, then you deserve all problems you get. (Hint, hint!)
To continue on with tilly's million subroutines, it's all about interfaces. A subroutine, in a very basic way, is just an API between you, the programmer, and some (hopefully!) well-defined and bug-free behavior. What the subroutine does under the hood should be something you don't have to worry about. That's one of the reasons to create a subroutine. "Code once and forget." (Also, there's "Code once, use twice" for the other reason to create subroutines.) | [reply] |
Two questions for you.
- Why should your user code make assumptions for you about whether or not undef is a valid value? That implies a knowledge of your internals that I don't like.
- Compare two coding standards. One says that it is the responsibility of calling code to test for possible error returns and the other says it is the responsibility of the code being called to test and throw exceptions. Which is more likely to happen without error? In which can you put better error messages?
Feel free to convince me otherwise, but my current thinking is quite clearly that it is better to throw exceptions early. User behaviour that didn't want that can trap it with eval BLOCK. (Similarly I consider it the responsibility of functions to do something useful in both array and scalar context.)
BTW the shortcomings of C have a lot to do with my attitudes on throwing exceptions versus making it the
responsibility of the called code to process possible error
returns. For a particularly bad example, consider EAGAIN. It is well-documented. A ton of system calls can return it. But in practice it almost never gets returned, that aspect of code never gets tested, and so that aspect of code tends to be particularly buggy. The result? A Unix system runs fine for months on end. Then you put it under unusual load, once, and key long-running processes wind up with messed up states. Right when you least wanted that. And it isn't just one or two badly written programs that do that. There are lots of them out there.
Now there are reasons why EAGAIN exists, legitimate reasons. Given what a Unix kernel has to do, I am not going to argue that it shouldn't exist. But that is a reliability flaw that I don't think should be introduced lightly.
| [reply] |
I'll address your last remark first.
(Similarly I consider it the responsibility of functions to do something useful in both array and scalar context.)
The full function does do something useful in both array and scalar contexts. I didn't write out the full 70 line version because it was meant to be an example, nothing more. If you want, I'll give you the current version of the abstract class that uses that function and we can discuss it. I'm always looking for input and criticisms on my code. I'm merely defending the fact that your criticisms (which may seem to imply that I don't know what I'm talking about) have already been taken into account. :)
I hadn't thought about using Carp and making it the caller's responsability to trap the croak within an eval block. The reason is that I don't want to make any assumptions about how my abstract classes might be used. Carp is great ... for CLI systems. For GUI systems, it's not so good.
Requiring that all values be defined isn't an onerous burden. At some point, in any module, assumptions and restrictions need to be made in order for the module to work well. We, as programmers, accept them all the time. For example, most of the functions in stdlib.h, std.h, and stdio.h return 0 when successful. We accept that 0 is a perfectly valid success code. Why, when every single other language I've come across does things the opposite way, do we blithely accept that? (I in no way am attempting to say that this is wrong, but merely use it as an example of restrictions on the rangespace of functions.)
In addition, attempting to access an attribute that doesn't exist isn't an exception. It can be a perfectly valid case. For example, you might have a list of objects of various kinds. You want to do processing on a list of attribute names, but not every attribute is in every object. So, you could do something like:
foreach my $object (@object_list) {\
ATTRIUBTE:
foreach my $attribute_name (@attribute_list) {
my $value = $object->get($attribute_name);
next ATTRIBUTE unless defined $value;
# Do stuff here.
}
}
That may seem contrived, but I'm sure you can think of other, similar examples. This is much clearer to the average programmer, imho, than using an eval block and checking $@. Plus, if someone wants to use undef, they can usually just use "" and everything is just fine. Or, if they absolutely need both undef and "", use the string __UNDEF__ (or some variant), and they're fine. If you can give me a real example where the same attribute can use undef and "", I'll be impressed. :)
Finally, I don't think that bring up EAGAIN is a good counter-example. Comparing an abstract class to the Unix kernel doesn't really ... I just don't think it's apples and apples. If you could explain it further (I haven't started my exploration of the Linux kernel yet ... that's for this winter), I'd be happy to listen. | [reply] [d/l] |