The Getter Setter Debate
I’ll be honest. I never thought there was a debate as to whether getters and setters were good practice or not. It never occurred to me, directly at least, that I should avoid them. Then a couple of days ago, I was browsing Reddit and came across this article by Michael Feathers; of interest is this excerpt:
“John Nolan, gave his developers a challenge: write OO code with no getters. Whenever possible, tell another object to do something rather than ask. In the process of doing this, they noticed that their code became supple and easy to change. They also noticed that the fake objects that they were writing were highly repetitive, so they came up with the idea of a mocking framework that would allow them to set expectations on objects – mock objects.”
Then somehow I run across another article that happened to link to the Feathers post:
“Suppose that we want to print a value that some object can provide. Rather than writing something like
statement.append(account.getTransactions())instead we would write something more likeaccount.appendTransactionTo(statement)We can test this easily by passing in a mocked statement that expects to have a call likeappend(transaction)made. Code written this way does turn out to be more flexible, easier to maintain and also, I submit, easier to read and understand. (Partly because) This style lends itself well to the use of Intention Revealing Names.”
Ah, this blog entry has a code example, something I respect. Now I do agree with the latter point on Intention Revealing Names; this is a key Evanism from DDD, named Intention Revealing Interfaces.
However, this example seems to imply a code smell. Yes, the account object is hiding how it is managing its transactions to the consumer, but now appendTransactionTo() is altering its argument. This is something I try to avoid.
Then again, this smell could be avoided by returning a new statement object with the transactions appended. This approach seems slightly contrived and possibly inefficient in an efficiency-sensitive environment.
Another way I can think of:
statement.appendTransactionsFrom(account);
This gets you the intention-revealing part, but you lose on killing getters because appendTransactionsFrom() has to get at the transactions of account somehow.
Noting now that this getter-setter topic is not a rogue argument, I did a search and came across this JavaWorld article, excerpt following:
double orderTotal;
Money amount = ...;
//...
orderTotal += amount.getValue(); // orderTotal must be in dollars
The problem with this approach is that the foregoing code makes a big assumption about how the Money class is implemented (that the “value” is stored in a double). Code that makes implementation assumptions breaks when the implementation changes. If, for example, you need to internationalize your application to support currencies other than dollars, then getValue() returns nothing meaningful.
The business-logic-level solution to this problem is to do the work in the object that has the information required to do the work. Instead of extracting the “value” to perform some external operation on it, you should have the Money class do all the money-related operations, including currency conversion. A properly structured object would handle the total like this:
Money total = ...;
Money amount = ...;
total.increaseBy( amount );
OK nice – here is an example that does not alter its argument. However, it begs the question: how does increaseBy() operate without a getter for the amount of money in amount, assuming all member variables are private?
Maybe we can get away with this by defining a MoneyInterface that does not use getters and setters, only intention-revealing methods. Then, implement the MoneyInterface with the necessary getters and setters. Then in increaseBy(), attempt a downcast in order to get at those methods and perform the increase.
OK, I know, the huge downside here is the downcast. There is also the abuse of the interface construct.
Following the spirit of the Feathers post, shouldn’t we ask the amount object, the one on which we used a getter, to perform the action, like this:
double orderTotal = ...;
amount.increaseBy(orderTotal);
Granted, this does not take a Money object, but here you side-step both the getter-is-evil idiom and my objection above.
Another question I have for this crowd: is this getter-is-evil dance even valid at layer boundaries? I think back to The Project and I had code like this:
Public Function storeNewPC(ByRef pcToStore As PC) As Integer Dim qryStr As String = "" qryStr = qryStr & "sp_STORE_PC '" & pcToStore.getMake().getID() & "', '" qryStr = qryStr & pcToStore.getModel().getID() & "', '" qryStr = qryStr & pcToStore.getCores() & "', '" ... Return db.exec(qryStr) End Function |
Following this idiom, I shouldn’t be asking the pcToStore for all of its guts; I should instead tell it to generate the query string I need:
Public Function storeNewPC(ByRef pcToStore As PC) As Integer Dim qryStr As String = pcToStore.generateStoreStr() Return db.exec(qryStr) End Function |
Please excuse me as I vomit. Big no-no.
Possible side-steps:
- use reflection. However, as I alluded to in the Nilsson book review, this sort of trickery is both a little too complex and also kind of weak; if all you’re doing is using it to access private members, then you are already violating the spirit of the getter-is-evil argument.
- use the Friend keyword and keep repositories and domain objects in the same assembly. This works, but it carries with it the same “spirit” problem as above, and it is a .NET-specific approach.
- eliminate getters and provide methods that return an agreed-to data structure whenever info like this is needed…an IList, an array, a struct…whatever. This way, you’re not tied to internal data types. On the other hand, this approach is somewhat klunky.
- most industrial solutions will be using a third-party tool for this stuff anyway, so…use a third-party tool like NHibernate (which, by the way, uses reflection).
It could very well be the case that layer boundaries are special cases. Another JavaWorld article states that procedural boundaries (like my layer boundary; after all, the examples provided in these articles stay within the business logic (domain) layer) and getters that return interfaces are exceptions to the rule.
In the case of the latter, I have to wonder if I really need all of that bloat if I simply want to return an integer from an object. Do I really need to define an interface for some sort of holder, implement the interface, and then return the implementation? Even so, if I do that, won’t that interface have its own getter method to return the integer?
What’s even worse, all throughout the Nilsson book, .NET properties were used. And I’m now thinking of doing a re-make of The Project in C#, and I know I’ll come across this issue — now that I know it is an issue to start with. And I don’t know, but I think I had another thing to say about the statement/account example that I came up with when I was tossing in my sleep at 2 AM last night.
This is making my head hurt. You tell me. Am I thinking too hard?
June 16, 2008 at 6:45 pm
Interesting similar post and debate occurring here: http://typicalprogrammer.com/?p=23
June 17, 2008 at 6:54 pm
[...] Getter/Setter debate: http://moffdub.wordpress.com/2008/06/16/the-getter-setter-debate/ [...]
June 17, 2008 at 11:22 pm
You’re missing the main point of the getters/setters debate. You don’t have to eliminate ALL getters/setters, only the ones that aren’t needed.
In the JavaWorld example, Money will be calling getters and setters on amount in order to total up the values. Money DOES make assumptions about how amount is implemented; that can’t be avoided without making things ridiculously complicated (i.e. “no getters” is an antipattern whereas “few getters” is not).
The key here is that all of the assumptions happen in ONE place, and one place only. A change to amount will only require changes in Money, and nowhere else. From the outside, we have no real idea of how these two classes work internally, so our code won’t break when they change their implementations.
This localized coupling (and maintainability in general) is what you should strive for, rather than trying to be “pure” OO. We are only approximating a system, after all.
Of course, the money example is very contrived, and you’d never do this with a real financial system!
June 18, 2008 at 12:42 am
I figured as much. Nothing in this field is absolute.
I left a comment on Typical Programmer: http://typicalprogrammer.com/?p=23#comment-764 referencing a TimeAndMoney example ( http://timeandmoney.domainlanguage.com/ ) coded up by some fellow domain-driven design enthusiasts. In it, they begrudgingly include two discouragingly-named getters, admitting that they are necessary for architectural reasons like UI mapping and persistence.
About the assumptions happening in one place: ideally, but a publicly accessible getter is…publicly accessible. If you have some renegade coders on your team who don’t believe in OO and abuse the getters, their code will be affected too.
How to side-step that one? Either manually with some good code reviews, or a possible “pattern” to regulate which instances can call which getters (topic of a future post here).
June 18, 2008 at 11:25 am
Just put them in the same package and set them to package-private.
Or put warnings in the API documentation that the getters/setters are for internal use only, and are likely to change.
You can’t stop intentional misuse, so it’s really a waste of time. Better to make sure your programmers know how to program in an OO language.
If you have “renegade” programmers, getters and setters are the least of your worries.
June 24, 2008 at 2:36 am
On Allen Holub’s Money example. You should be aware that this example is in Java. The objects “total” and “amount” are both of type Money. In Java objects of the same type can access each others private variables (or anything else). Both objects are of the same type, encapsulation at the class level is not broken.
June 24, 2008 at 11:35 am
I should also be aware that the language doesn’t matter. I just tried that in both Java and C# and you are correct. My understanding of the rules of encapsulation was a little more conservative than reality.
For whatever reason, every time I read or heard the definition of the private access modifier, I assumed it applied to instances and not the class from a “static” POV. Thanks for pointing that out.
September 1, 2008 at 9:08 am
[...] http://moffdub.wordpress.com/2008/06/16/the-getter-setter-debate/ – и ещё немного в ту же кучку (геттеры-сеттеры). [...]
December 17, 2009 at 12:52 pm
мне кажется: шикарно…
February 24, 2010 at 9:52 pm
To go back to the statements/accounts example I’d say that
_ statement.append(account) _
should be fine if it is a Statement of an Account; it’s a statement of the account, so it should understand the nature of an Account and be able to walk the object graph to get the data. If instead it’s a statement of transactions
_ statement.append(account.transactionsInPeriod(periodStart, periodEnd)) _
and
_ statement.append(account.allTransactions()) _
should be OK because the statement understands the nature of Transactions, but not how to get them out of an Account.
I’d agree with Wolter that you have to use getter equivalents somewhere, and that misuse can’t be architected out completely; make the methods you want exposed nice and pleasant to use, review code, and make developers who break encapsulation buy the doughnuts until they’ve fixed the problem.