Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re (tilly) 2: To sub or not to sub, that is the question?

by tilly (Archbishop)
on Jun 26, 2001 at 15:58 UTC ( [id://91562]=note: print w/replies, xml ) Need Help??


in reply to Re: To sub or not to sub, that is the question?
in thread To sub or not to sub, that is the question?

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.

However beyond that using subroutines gets you into the following paradox. Suppose that working with heavily factored and organized code is 5 times harder per page of code. Suppose that it does the work of 20 pages of unfactored and unorganized code. If you assume that the issue of tracking down the same bugs in unfactored code is similar to the issue of changes made for one reason affecting things all over the place, then that is a gain in productivity of a factor of 4. However whenever picking up that code I can guarantee you that you will feel that factor of 5 issue.

As for the assumption, that is where experience comes in. It is easy to break things into a million subroutines. What you learn over time is how to break things into a million subroutines that organize into clumps which present loosely coupled interfaces to the rest of the world. After you do that you just do not modify existing public interfaces lightly. Add a parameter, sure. But don't lighly assume that nothing depends on old (even old and stupid) behaviour.

Also another good idea - the test suite - makes this easier by giving you a good idea what behaviour someone else is depending on somewhere which you didn't remember.

  • Comment on Re (tilly) 2: To sub or not to sub, that is the question?

Replies are listed 'Best First'.
Re: Re (tilly) 2: To sub or not to sub, that is the question?
by dragonchild (Archbishop) on Jun 26, 2001 at 17:39 UTC
    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.)

      Two questions for you.
      1. 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.
      2. 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.

        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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://91562]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (4)
As of 2024-04-24 22:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found