in reply to Re^7: replace conditionals with polymorphism
in thread replace conditionals with polymorphism

I could do that (no OOP), but that would be premature under-abstraction :-)

No seriously, you make a great point about your having used this module for years and not needing that particular abstraction. And I know you have loads and loads of experience. But let me give a counter story.

One of my first jobs out of school was supervising a team trying to put together a consolidated data model and data dictionary for about 300 coorporate information systems. I learned a lot more than I ever wanted to know about the vagaries of abstraction and language. And most of all I came to the conclusion that there will never be one way to do things and that the best one could do to control confusion was to create good frameworks for doing the same thing many ways. (Maybe that is why I like Perl so much?).

The main reason for OOPing up an implementation is that it provides a framework for TIMTOWTDI. When software is released publicly or across a corporation, it is virtually guarenteed that needs will change across user groups and time. Even during development, customers rarely can articulate or correctly prioritize the corner cases until they've seen a few demos, and by that time, an awful lot of code may be written.

However, just to be clear, I was not suggesting a full scale OOP evangelist style of OOPing up. Most OOP implementations go way overboard creating a mess of interconnected objects that would make a circuit board engineer's eyes cross. They assume that all objects must be mutable. That danger lurks wherever data is accessible to outsiders, etc, etc. Yada, yada, yada..

Rather I had in mind something much simpler. Something that would allow for the possibility that an OOPish need will arise without destroying the overall clarity of your code. How would I do that?

As a first step, I would make changes that do nothing more than make it possible for consumers of the parser to call your parser using an OOP syntax. Internally, aside from the ever-present $self, the object wouldn't really be OOP at all. This would involve little more than

I'm not even sure I would bother giving new(...) parameters (other than $sClass) or even moving the configuration data into a hash. And though I would likely add getter methods for configuration data, I certainly wouldn't bother adding mutators or fancy methods to hide data.

My reasons would be the same as yours - we don't know enough about the way it should be abstracted (yet). Maybe the users don't need to configure the object, they only want to override token processing. Maybe they want to configure, but are ok with doing it only when the object is constructed. Maybe they need a fancy way to add new syntax elements or may be they don't. Maybe they are happy to rely on training and social controls to keep data private and maybe they aren't. We just don't know. All of these can be added later without disrupting existing code. So I would keep it simple.

Ah...but if the changes are so little - why bother? If you really need to, you can do those kind of changes later on. For now they are just fluff...

Why? Because for very little cost, it reduces (not eliminates) the risk that we will have to go out and review 100K or more lines to find all the places we need to change from a function interface to an OOP interface. It reduces the risk that we will need to release a backwards incompatible product to clients. It reduces the risk that we will have to redesign the class to support both a procedural and OOP interface (which will definitely muck up the clarity of your code). It reduces the risk that we have to choose an awkward ordering of parameters just so we can guess which interface is being used. For very little cost and virtually no premature abstraction we create an interface that is more likely to grow in a managable way.

So yes, I agree with minimalism, but the minimalism should be in the design of the object, not in the avoidance of objects.

Best, beth

Replies are listed 'Best First'.
Re^9: replace conditionals with polymorphism
by tilly (Archbishop) on Feb 11, 2009 at 19:02 UTC
    Sorry about taking a while to respond, I have a lot going on right now.

    First let me comment that there is a bit of a bait and switch here. Not long ago you were suggesting using objects to allow polymorphism on tag recognition. I pointed out why that wasn't a good place to put an abstraction layer. Now you're suggesting using objects to hide the overall engine. That's a change in design.

    Secondly it seems to me that you're having trouble recognizing abstraction that doesn't come with an OO bow tie on top. The style of programming used in that example offers equivalent if not greater opportunities for abstraction than OO does. Admittedly you abstract in different ways in different places. You have more flexibility in how to abstract at a cost of not providing a standardized framework for others to recognize. But under-abstraction is not a problem with that style.

    What flexibility would you hope to gain in this specific situation by wrapping the interface with an object? Make that clear and I'll show you how to do the same thing without objects. Please trust me when I say that I am not taking this position out of any ignorance of how useful OO can be. Quite the reverse in fact. I believe I can quite honestly say that I understand OO much better than most programmers and I understand a number of other programming techniques as well. Therefore I am in a better position than most to decide when OO is the right abstraction technique to look for.

    For more on the general limitations of OO as an abstraction pattern, The world is not object oriented may be of interest. But, lest I sound entirely negative about OO, I should hasten to add that there are cases when I think it is appropriate and useful. One important example being when you have a series of different decisions that need to dispatch in a coordinated fashion. In which case you can do the dispatches by making method calls on objects.

    For the record I am not alone in my views. The ever quotable Paul Graham has even stronger views. They were quite quotably stated in his essay The Hundred Year Language:

    I don't predict the demise of object-oriented programming, by the way. Though I don't think it has much to offer good programmers, except in certain specialized domains, it is irresistible to large organizations. Object-oriented programming offers a sustainable way to write spaghetti code. It lets you accrete programs as a series of patches. Large organizations always tend to develop software this way, and I expect this to be as true in a hundred years as it is today.
    Now it is easy to dismiss Paul as a gadfly, or to assume that he doesn't know much about programming. But before doing so I really, really, really recommend reading his On Lisp cover to cover with comprehension. Once you see how he tackles a variety of problems using different techniques, you may take more seriously his contention that OO's toolkit is not the only, and is often not the best, way to approach problems. (Incidentally in one example he implements an admittedly basic OO system in 8 lines of code. He then shows how to add features to it.) While you are at it you may wish to read Structure and Interpretation of Computer Programs for a related but somewhat different set of approaches to abstraction. (That book is intentionally more introductory.) If reading Lisp is a block for you, Higher-Order Perl covers related ideas in Perl.

    Those are, of course, all oriented towards a functional style. But there are perfectly good abstraction techniques for straight imperative code. The classic Code Complete 2 has as good an explanation of the basic ones as anything else does. Be warned that it is a weighty tome, but in my opinion virtually everything in that book is something that every competent programmer should know inside out and backwards. Even if you know 80% of what is in that book, the remaining 20% makes the purchase and reading worthwhile IMO.

      Thank you for such a long and thoughtful response.

      I apologize if you felt that I was baiting and switching, but my sense when I read Re^7: replace conditionals with polymorphism was that you had come to some incorrect conclusions about what I meant by applying OOP to your code. I was merely trying to clarify what I originally meant.

      In particular, I don't think I ever suggested turning the tags themselves into objects. That would be rather silly in my opinion and destroy the whole structure of your original code. It would have been a variation on the ridiculous idea of encapuslating single case statements in objects - which I think I made pretty clear was ridiculous (even if I did it nicely). I seem to recall that my first post about OOPifying your engine specifically suggested that your tag processing functions could be turned into methods, not objects. But then again, if you read it otherwise, I wasn't being clear and I apologize for the confusion.

      As for lisp - read it, use it (or rather its poor cousin elisp). Please don't think that because I am advocating an OOP wrapper in this case that I am limited to one paradigm. That is as bad as someone trying to trivialize your views by suggesting that they arise out of ignorance. And BTW I agree absolutely with Paul Graham and your post The world is not object oriented - as would Wittgenstein and the buddhist philosophers.

      At some point, perhaps in a dedicated thread(?), I would love to have a discussion with you about when each paradigm is appropriate, are there rules of thumb for determining it, or is it purely a matter of experience and intuition (your 3X rule), and also how we teach it.

      Best, beth

      Update 2009-02-12: I realize in reading tilly's post below I have once again overstated my case. The idea of mapping tags to methods is part of a miniminal object representation - using no more OOP than is necessary to shield implementation and make the interface used by consumer code more likely to be stable. As the requirements became more clear, I completely agree with tilly that functions that took clumps of data and code references and generated families of token handlers might be a good idea. And I also might make judicious use of tags=objects. Or maybe even some approach that mixed the two paradigms. It really would depend on how needs evolve.

      I guess we differ in that I would make handler generator functions into methods that would supplement the older tag=method approach. The key issue for me is that we have a cluster of related methods using at least some shared configuration data. I find (based on my own experience) that treating unrelated things as separate is inherently unstable, so I would like to hide my changing understandings of exactly how they are related behind an object. tilly would probably argue that since an object presumes the existence of relatedness, it is yet another one of those changing understandings, rather than a wrapper to hide those changes.

        My understanding of what you originally meant about applying OO to the code was from Re^6: replace conditionals with polymorphism where you wrote, ...turning the configuration data into the data portion of an object and the definitions of dispatch routines into methods. And then went on to discuss how you could get several different mini-languages by redefining 4 tokens (open tag, close tag, open constant tag, escape).

        My response to that was that the number of different types of tokens goes up rapidly when you add in convenience features like turning emoticons into pictures, autolinking URLs, and using \ as an escape mechanism. The result is that the number of different methods in your API quickly grows. You came back from that with the idea of putting an OO wrapper around the whole thing. I'm still unsure what that would buy.

        The basic problem is that your original suggestion was trying to draw an abstraction layer across the tight coupling between configuration and the handlers generated from that configuration. Tight coupling across a complex and young API is guaranteed to mean a lot of changes. A better idea is to have building the configuration hash broken up into a series of functions that return related sets of handlers. Like valid HTML tags, emoticons, automatic paragraph breaking, and convenience shortcuts. If this was for a discussion site like this one, you could then expose to the user the choice of which types of functionality they want available for processing their posts. And now even fairly significant rewrites of this code could take place without problems. Of course this approach doesn't lead you in an OO direction.

        On the question of how to select which paradigm to use when, I am a firm believer that programming should be viewed as a craft. Which, among other things, means that people need to be mentored. You can read all you like and be told all of the rules that exist without necessarily being able to apply it very well. But when you come up with a design and get detailed feedback on your design, things work much better. Mentoring online is better than nothing, but mentoring in person is much better.

        The fly in the ointment is where to get competent mentors from and match them up to programmers. I am all ears for a solution because I don't have one. However I had the luck to have a good mentor when I was just starting out, and I'm firmly convinced that that is the best way to go.