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 like
account.appendTransactionTo(statement)We can test this easily by passing in a mocked statement that expects to have a call like
append(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:
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:
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 = ...;
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.
- 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?