There have recently been a few discussions and blog entries centered around whether getters and setters are good or bad. It seems that invariably someone (or many someones) invoke the YAGNI principle (You Ain't Gonna Need It) as a way to defend their belief that getters and setters are bad, or even useless.

Stop it. YAGNI is a good concept, but I feel that people are trying to use it as a blanket to smother any discussion that involves planning for the future. If YAGNI were applied literally and without thought, we'd all have one giant .cfm file with a huge block of procedural code in it that executed an entire application. Why bother with CFCs at all? Why bother with methods? This can all be done inline in procedural code. A fundamentalist YAGNI view could easily rationalize this decision, and throw solid principles like cohesion, encapsulation, and loose coupling out the window.

Obviously we DO plan for the future when we code. YAGNI is a general principle, not a rigid rule. YAGNI is meant to help avoid over designing something. Do you really need to implement the State Pattern here? Do you really need to create an Abstract Factory and the complexity it brings because you might need to change database platform or encryption algorithms? Almost certainly not. In cases like this, YAGNI can be a valuable litmus test of your design.

However, there are situations where good practice and experience has shown that certain approaches are useful whether you are sure it will change or not. Breaking SQL into it's own classes is almost always a good thing just for the cohesion it offers. One might not be positive that implementing looser coupling is truly needed now, but clearly some attention to basic coupling concerns is always valid. The point is that the idea of balancing concerns has got to be kept in mind. While it can be a grey area, particularly to newcomers to OOP, there is a middle ground between over-engineering and under-engineering. There are situations where something may seem to violate a rigid interpretation of YAGNI that should nonetheless be strongly considered.

Take an example from the Java world of encapsulating object construction. In basic terms, most people create new objects using the "new" keyword, i.e. "foo = new Bar()". The problem with this is that you have given up control of what is being created and how it is being created. Once the "new" keyword is littering your code, any future change to the way something needs to be created is going to be a massive pain to implement.

However, with virtually no extra effort, one can add a huge insurance policy against this situation. By simply making the constructor private and adding a static method called getInstance() that returns a new object, you have moved the logic for how something is created from the calling code (all over your application) into the object itself. Now if you ever need to modify how this object gets created, you have much tighter control over how that happens.

YAGNI would rail against this. But the point is that with 30 seconds of extra work, you have a much more flexible situation. You've potentially saved yourself hours of refactoring and testing in six months. Anyone who would say that that isn't a valid tradeoff is, to me, being very shortsighted.

Again, the point is that many things violate YAGNI in strict principle that are still useful because they are easy, fast, simple, and offer significant potential returns.

Back to getters and setters, I believe these fall into this category. They take virtually no extra effort to create (a snippet or generator will spit out 15 getters and setters at the press of a key). They are not over-engineering your application or blindly pushing large design patterns into your code, which is what YAGNI is really about. It is a simple, easy thing to do that takes very little effort and offers significant potential rewards.

It is true that in an ideal world, we wouldn't even need getters, setters, or public properties. We'd always ask our objects to do things and focus only on the behavior or services an object can provide. Unfortunately, in the real world, data has to get into an object (to populate it) and has to come out of an object (to display it). These boundaries are among the only times invoking getters and setters is justified, but at these boundaries, the encapsulation and clarity that getters and setters provide is, to me, well worth the extra few seconds it takes to generate them.

So, please, feel free to use YAGNI as a guiding principle and let it help keep your code sleek and simple. But don't allow a blind adherence to it to smother potentially good and simple practices that can be very beneficial with very little cost!

Comments Comments (18) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 7462 Views

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)

  • # Posted By Dan Wilson | 10/14/08 1:49 PM

    I agree with this perspective and think it represents well reasoned design. Certainly we are all against blindly adding indirection to our applications. In the cases you mentioned, and especially getters and setters, the slightly extra typing up front pays off down the road in a clearer API and smaller refactors.
    The use of a getter/setter gives a very useful hook into common events in an object. As the application morphs, these hooks are ready made spots in which to keep the external API the same all whilst modifiying the innards. Encapsulation at its best... achieved for very little extra work and hardly any indirection at all.


    DW

  • # Posted By Ben Nadel | 10/14/08 1:53 PM

    Nice reality check.

  • # Posted By Rick O | 10/14/08 2:01 PM

    I certainly didn't mean to cause a rant. ;-) You get extra points for your reduction to the absurd, though.

    But, I must say, your case against YAGNI is almost exactly my case for it. Let me be specific:

    "a snippet or generator will spit out 15 getters and setters at the press of a key" -- Why is this a good thing? Or more to the point, why is this a good *default* thing for most standalone web apps? (Which, again, is what I'm talking about -- not about frameworks.)

    "the encapsulation and clarity that getters and setters provide" -- I'll grant you that setters are almost always useful for encapsulation. Certainly. Clarity -- yes and no. Setters draw a definitive line in the sand saying "don't worry your pretty little head", so I'll grant that it simplifies. But, by definition, you are setters turn code into a black box, which reduces visibility, and in some sense also clarity. (How many times have you had to use code that made you want to tear your hair out and say "wtf are they doing in there?") So, I'm not so much arguing your point as your choice of verbiage -- for setters.

    But getters? In CF? Again I say: why? It's not like you're going to be doing any reference counting or anything goofy like that. And CF doesn't suffer from the "new" problem you outlined above. You might make the case that you need to ensure that you provide a deep copy and not just a reference -- and on that case I'd agree with you. But that case should be obvious from the outset. So ... other than that case ... why?

    "the extra few seconds it takes to generate them" -- A few seconds of joy, 18 years of pain. To paraphrase. ;-)

    I'm not arguing against OO. I'm not arguing against encapsulation. I'm arguing that blind faith in the arcane rituals and rites of OO is just as bad as complete ignorance of OO. Or, to mis-quote you: "don't allow a blind adherence to it to smother potentially good and simple practices that can be very beneficial".

  • # Posted By Brian Kotek | 10/14/08 2:19 PM

    Hi Rick. You also get extra points for labeling my opinion as absurd. Sorry! I should have known that if I disagreed with someone it was just me being totally crazy. ;-)

    To answer: the point is that creating getters and setters takes no effort. So anyone who says "it's too much work" is simply not using the tools at their disposal. So that argument is moot.

    Getters allow the ability to add additional logic without changing the external code. If I have a public property named "price" and I have code all over my application that calls it directly, and then I need to add calculations to how price is determined based on customer discount levels, I'm screwed. If I had just taken the extra 5 seconds to use a getter instead, I wouldn't have to change anything else. That is the point, and that is why taking the extra few seconds to put a getter in place offers much more flexibility for zero cost.

    I'm not saying anyone is arguing against OO. I'm just pointing out that if one really runs with YAGNI they'd have an app that had no futureproofing at all. The point is that YAGNI is a good principle, but it has to be balanced with common sense tradeoffs. Don't over-engineer, but at the same time, don't under-engineer.

  • # Posted By Rick O | 10/14/08 2:31 PM

    For the record, I wasn't calling you absurd. I'm outspoken, sure, but I'm not a complete jerk. (Well, not normally.) For the "absurd" comment, I was referring to: "we'd all have one giant .cfm file with a huge block of procedural code in it that executed an entire application".

    But again, I find that you are really just underlining my own point:

    "If I have a public property named "price" and I have code all over my application that calls it directly, and then I need to add calculations to how price is determined based on customer discount levels, I'm screwed." -- Yes, but you *should* be screwed. Your definition of "price" has fundamentally changed. In your getter-based world you'd be breaking any code that depended on the discount levels *not* being taken into account. Your getters have provided an illusion of forward-compatibility, but a reality that is the opposite.

    I know, of course, that this is a contrived example, but in my YAGNI world you'd still have the "price" property/member for the version1 consumers, but you'd also have a "calculatePrice()" method that the version2-consumers could call and understand. Yes, they'd have to change their code -- but shouldn't they?

    "a getter in place offers much more flexibility for zero cost" -- See above - it's the *illusion* of flexibility. In reality, if you don't want to break existing apps, then you're just as stuck as you would be without the getter.

  • # Posted By Brian Kotek | 10/14/08 4:13 PM

    The point is that if you adhere rigidly and inflexibly to YAGNI and take it to it's ultimate end, what you would have is a giant .cfm file. As soon as one backs away from that, what is happening is that we are making a tradeoff of increased complexity to allow for easier maintenance. So at that point, all we're debating is how far across that continuum of tradeoffs we are going. This isn't absurd, it's simply how things are.

    "Yes, but you *should* be screwed. Your definition of "price" has fundamentally changed." I disagree completely. Who's definition of price has fundamentally changed? Not mine. I ask a Product for it's price. I want a number back that represents the price of that Product. This is the very heart of encapsulation and OOP: I should not have to change existing code for this. If I do, I implemented a very poor design indeed.

    "In your getter-based world you'd be breaking any code that depended on the discount levels *not* being taken into account. Your getters have provided an illusion of forward-compatibility, but a reality that is the opposite." I say false. It's up to the object to decide when discount levels should or should not be taken into account. If you really believe what you're saying here, you're blowing apart the whole point of OOP. If my external code is dependent on the internal implementation of my Product, I'm doing something very, very wrong. I'm fairly startled that you're actually trying state this as some kind of argument to support the elimination of getters and setters.

    "but you'd also have a "calculatePrice()" method that the version2-consumers could call and understand. Yes, they'd have to change their code -- but shouldn't they?" No, they shouldn't. In fact, this is far worse: now you're advocating complication of the API, lack of consistency, and external code "knowing" whether it should call ".price" or "calculatePrice()".

    " In reality, if you don't want to break existing apps, then you're just as stuck as you would be without the getter." Nothing breaks, that's the entire point. Everything that asks for a price gets a price. The fact that you feel that somehow the calling code should "know" about how the price is determined is at the heart of the issue. I would say emphatically that it should not. As long as the Product has the correct logic to determine how to calculate the price, nothing outside it ever has to know anything changed at all. If it does, the design is flawed.

    Maybe we need to agree to disagree, because I'm clearly not convincing you, and what you're saying sounds like a very bad approach to me.

  • # Posted By Rick O | 10/14/08 4:53 PM

    Let me simplify your example a little bit, so that I can explain my point a little better.

    You have website that sells something, and has a shopping cart API, done up in getter-setter fashion. There's a single getPrice() method.

    Tomorrow, some lawmaker decides to finally repeal the moratorium on Internet taxes so that you are now required by law to apply your local (not remote) sales tax to every order. You go and update your getPrice() method to include the local sales tax. Everyone is happy, right?

    Last year, I wrote a spider that goes out and compares the prices of several competing sites, yours included. When the law is passed, I look up the documentation for your API and see that it doesn't say a thing about taxes, so I go ahead and add taxes to the price your site gives me. (Or worse yet, your API documentation said "no taxes are included in this price", because, at the time, they weren't.)

    Tomorrow, you see a noticeable drop in sales referrals from my spider because all of the other sites are suddenly undercutting your price by 5-10%.

    To me, the consumer, you have just changed the rules out from under me. Sure, those rules still make sense to you, but they aren't the rules we agreed upon when we started playing the game. You've made an API change, whether or not you want to call it that, because the returned data has fundamentally changed, even if it is superficially "the same".

    Yeah, my proposed (and very contrived) solution breaks encapsulation and OO. But that's the point -- you've already done it too, I'm just being up front about it.

    We may indeed have to agree to disagree.

  • # Posted By Brian Kotek | 10/14/08 5:12 PM

    First, I would not update getPrice() to include tax. That would be ridiculous. So right there the remainder of your argument collapses. Your spider would not see a change in price. Just so we're clear now: nothing asking for the price sees any change. Modifying getPrice() to include tax would be a very poor solution.

    Tax has nothing to do with the actual price of the Product. Tax is something that is applied to an ORDER. It is a separate, additional value that exists independently of the price of the Product, just like shipping charges. So the better place to implement the new rule would be at the Shopping Cart or Order level.

    Since this is my blog I'm obligated to defend my assertion, so you can feel free to let this debate end whenever you like. But as far as I'm concerned, what you've laid out has only reinforced my whole point: having getters and setters gives you much more flexibility than direct property access for zero cost.

  • # Posted By Joe Rinehart | 10/14/08 6:56 PM

    Damn it, this horse is dead, and here we are bringing out baseball bats to give it a damn clubbing.

    If the ability to add logic to property access (both read and write) is a YAGNI thing, why the does every modern language (C#, VB.NET, Ruby, Groovy, Objective-C, etc.) provide a way to add this logic? It's necessary stuff.

    In ColdFusion, we don't have as elegant a construct (yet), so we're stuck in the Java world of writing get/set pairs. Snippet. Two seconds. Done.

  • # Posted By ike | 10/14/08 7:10 PM

    In my case my issue with using code generators (or IDE tools) to spit out getters and setters is that it's a quick way to produce a lot of code that in the long run becomes more difficult to maintain.

    I still find it rather odd that when these discussions crop up there's almost no mention whatsoever of the fact that using individual getters and setters vs. using the public "this" scope is a false dichotomy (otherwise known as the "fallacy of the excluded middle"). It's simply unnecessarily black-and-white thinking to consider those the only two options. There are in fact a variety of options, and the one I prefer involves neither of those solutions and involves neither of the drawbacks of either of those solutions (either the lack of encapsulation or the adding of code).

    I disagree that using code generators obviates the problems of maintaining the code, because code is modified *after* it's generated. I've worked on a good number of applications where code was generated and yes, up-front time to code is reduced. Maintenance is always increased. Because any time you add a column to your database (which happens in my experience rather frequently), you've got to add two new functions. Or you've got to run the code generator again and use a file-merging utility (a tedious task I try to keep to a minimum). The way around that if you want to generate code with individual getters and setters is to use a run-time code generators like Reactor which involves a different set of problems that I don't much care for either. Although I'll take the issues created by run-time code generation over "snippets" or any other IDE-based solution every, every, every time.

    There are effective ways to obviate all those issues, they're just almost never discussed when these rants break out.

    Here's one example: http://ontap.riaforge.org/blog/index.cfm/2008/5/19...

    I thought programmers were supposed to be creative problem solvers. Why do we insist on fixating on thoroughly uncreative solutions to this one problem as though there are only 2 choices?

  • # Posted By Brian Kotek | 10/14/08 8:00 PM

    Hi Ike.

    The other choice you're referring to, if the link you sent is an indicator, is to use generic getters and setters, which to me is only slightly better. You do at least gain the advantage of being able to modify what happens later without changing client code, but you still end up with an object that has no API. Don't bother running something like that through a documentation tool, because what you can back is useless. Without opening the actual file you have no idea what the valid properties are, which ones are supposed to be public vs. private, etc. If Adobe ever develops an IDE that can introspect components, everyone using this approach is going to be pretty much out of luck.

    I fail to see how generated getters and setters are some kind of maintenance problem. You generate them and they just stay as they are. There is no maintenance until you need to modify one, and I have indeed had to do this many times, being thankful every time that I used a method to isolate the change.

    If I add a property to a database, that is a 5 second change in Eclipse and the work is done. In the exact same amount of time it takes someone to add the property to the component, I can generate a getter and setter pair with a snippet. I'm sorry but this argument about work is just not holding any weight with me. And yes, if you use an ORM the problem is even less of an issue.

    As far as being a "creative problem solver", I simply don't feel that this warrants "creative anything". All the complexity added and clarity lost by trying some form of generic method handling in this is simply not even worth my time when I can create the actual methods and be done with it immediately, moving on to tasks that actually matter. The real point of this blog entry was not to beat the dead (and mostly meaningless) subject of how to handle property access, but to channel my concerns about the invocation of YAGNI as a way to smother any discussion on good practices that are easy to do and offer a lot of future flexibility. Given two choices that require the same effort to implement, I will usually choose the one that offers the most flexibility down the road. If I never need that flexibility, I lose nothing. But on the occasions where I have needed it, making that choice has save me countless hours of tedious work.

  • # Posted By ike | 10/14/08 8:19 PM

    Re: losing the API - umm... no, because you have cfproperty tags. So that entire argument is invalid.

    Re: 5 seconds to add a new column... due to a proliferation of standard-style get/set methods in a variety of objects combined with an insistence on stored procedures for every access to the database, at my last job, each new column required FOURTEEN parts of the application to be changed across 5+ files, not all in the same object (or even in the same place). Most applications in my experience aren't that bad, but in my experience, most applications are closer to that than they are to having the 1-2 places my apps need to change for a new column. I have no idea how many hours I spent just maintaining getters and setters that ultimately did nothing on that last job. It was a lot. Columns were added to the db pretty regularly to accommodate unexpected needs.

    And there are plenty of examples of my own code in open source readily available for scrutiny to show that the solution I showed is a good one - that it actually does save time and coding and does not make the application any more difficult to work with.

  • # Posted By Brian Kotek | 10/14/08 9:31 PM

    Sorry, the example you linked to had no cfproperty tags and I'm not a mind reader. Regardless, the argument is not invalid to me. I still think it is interesting when people go the "cfproperty combined with generic getter and setter plus random custom getters and setters" and somehow manage to convince themselves that that is less work than just generating a getter and setter. But whatever floats your boat!

    Regarding the 5 seconds to add a new column, I'm not sure what to tell you. I've been using generated getters and setters for many years now and I've never had an issue on that side of things. Now when it comes to updating forms, updating SQL, updating validation, etc., yes, but none of that has anything to do with whether you use a getter and setter or not.

    I also never said "your solution wasn't a good one", just that I personally wouldn't use it. And I must admit I'm losing heart somewhat that so many folks take that one small piece of what I was saying (using getters and setters) and drive that into the ground, while seemingly missing what most of the blog entry was about (don't let YAGNI be an irrational restriction).

  • # Posted By Rick O | 10/14/08 10:48 PM

    Instead of continuing to nitpick flaws in our contrived examples, I've taken some actual code samples and explained my points about them:

    http://rickosborne.org/blog/index.php/2008/10/14/o...

  • # Posted By Jason Dean | 10/14/08 10:55 PM

    Brian, for what it's worth. I got a lot out of this entry. It answered many of the questions I have had since I first saw the rant about getters and setters and saw so many people saying "Right on!" and "Yeah! You're right".

    As a newcomer to OO development I was really starting to feel like I was "getting" (no pun intended) the whole encapsulation thing and forward compatibility and design patterns, etc. I was finally starting to feel confident that I was moving into the world of OO and had a good idea of what I was talking about. And then I see a post like that and it shoots me down, and makes me feel like a n00b.

    Your post repeated so many of the things that I was feeling but could not articulate. My confidence in what I thought I knew has returned (somewhat).

    Some of your fellow experts could learn a lot from the way you explain what you do and your reasons for doing it.

    Thank you

  • # Posted By Rob Wilkerson | 10/15/08 10:15 AM

    Yes. Yes, yes, yes, yes.

    I actually make a point of telling my guys that they're not building the app for now; they're building it for a year or so from now. Usually, I'm speaking specifically with respect to how their code is written (other devs need to maintain it later), but I think it's also relevant to this discussion.

    Designing for later doesn't mean that you need to pretend to be some kind of oracle. Just have enough understanding of the application and its audience to anticipate future needs. The goal isn't to meet those needs now, but rather to avoid slamming the door on those possibilities. Most of us have been doing this long enough to anticipate what business owners will need/ask fo even when they don't realize it yet. It's a _Groundhog Day_ thing, I think.

  • # Posted By Qasim Rasheed | 10/15/08 10:02 PM

    Brian,

    Another well written post which reinforces my personal opinion to use getter & setters by not typing those all - but use one quick snippet. Also thanks for providing link to the article from net objectives. I need to read these more often in addition to re-watching the e-zines.

    Cheers....

  • # Posted By Alan Livie | 11/5/08 5:42 AM

    With onMissingMethod() in CF8 we get the benefit of writing client code getters and setters and onMissingMethod() handles it nicely ... then if the behavior needs to get richer we can hand-code the getter or setter in the cfc.