Technical Practices: Never Return Your Data

No Getters

That's the normal form of this technical practice.

No Getters

There's some uncertainity around what "No Getters" means. OK, there's two versions of it.
Yegor Bugayenko in Elegant Objects Vol. 1 says,

It's all about prefixes

That's section 3.5.3 in Elegant Objects Vol. 1. I very strongly disagree with it.

When it's all about the prefix, then you're not effectively encapsulating behavior. If you're not a compulsive reader of my blog, and why would you be - I'm me. Then you may not know my view of encapsulation: Behavior. Encapsulation is all about behavior. It's what allows objects to message each other, by asking them to do something.
An example given for 'just drop the prefix' is the difference between

class Cash{
    private final int value;
    public int getDollars(){ return value; }
}

getDollars is saying, "Go into your data, find dollars, and return it."

I don't disagree with this sentiment. I think it's super explicit about what the object is doing. There's no data hiding or implementation hiding.
Yegor follows with

class Cash{
    private final int value;
    public int dollars(){ return value; }
}

dollars is asking, "How many dollars do you have?"

Again - I don't disagree with this view. Which follows A LOT of Object Oriented literature about respecting the object. This is BETTER, but it's still wrong. This doesn't help us write good object oriented code. My love for Elegant Objects Vol. 1 (it's a fantastic book) is due to it helping me finally click what OOP is. It pains me to see these things that get SO CLOSE... and stop at a point I'd consider a failure to reach better OOP.

Fred George has produced nightmares of a single question when trying to get information from an object

Why?
Why do you want the data?
What are you going to do with it?

From these questions you'll find THE BEHAVIOR that exposing the naked data is enabling.
I also disagree with Yegor that dropping the get makes the data non-naked. It's still raw, naked, behaviorless data. It's wrong.

Once you identify the behavior, you've identified something that elsewhere in the code could use. It's not available to them. It's in that one bit of code in that one spot for that one thing. It's not reusable.

There's behavior around the dollars data. This behavior needs to be encapsulated. A fully encapsulated class is one that only exposes behavior. That only provides interaction. If your object returns raw data - your object is not encapsulated.

I don't know if Fred started the thought about never returning your data, or if it evolved from my evolved definition of encapsulation being based on behavior. I'll give Fred a lot of the credit though. While working with him, I never returned a reference I held.

Going back to the questions that have now identified the behavior you want - encapsulate that. Put that behavior into the class.
Yegor's Cash class is still a databag; he just names it in a way it doesn't shout it from the rooftop. The databag-ness is hidden from us. We're hiding the smell.

Concepts - Not data storage

One of the points I'll get to in another post is about not passing around data storage types, or primitives. Yegor knows this, and his Cactoos library provides ways to avoid doing so. His Elegant Objects Vol. 1 examples do not.
I'll write more on this in the future, as it's one of the critical Technical Practices of maintainable code - for now, I'll summarize.
By passing back an int we're passing back ... an unknown. We're providing the underlying storage mechanism.

Ignoring that we can do anything with this allowed by the operating system; what is it? Probably dollars... I hope some dollars...
It drives procedural code - Not object oriented code. The exact thing he and I are both striving to get out of Object Oriented Code.

int amountToPay = costOfMeal().dollars();
int onHand = cash.dollars()
if(amountToPay <= onHand){
    ... do something ...
}

I'm forced to do behavior around the data, maybe with the data.

int amountToPay = costOfMeal().dollars();
int onHand = cash.dollars()
if(amountToPay <= onHand){
    return new Cash(onHand - amountToPay);
}

This is so very wrong. Is this the only place in the whole code base that wants to know if something can be paid for; and pay for it?

What are we gonna do, turn it into a static method?!?!?! ... oh, BTW - No Statics; another planned post.
No.

Let's look at this example and our earlier questions?

What do you want the data for?

To compare to another value.

Cool... Ask the object to do it.

Cash amountToPay = costOfMeal();
if(cash.canCover(amountToPay)){
    return new Cash(cash.dollars() - amountToPay.dollars());
}

Again

What do you want the data for?

To subtract an amount

Cool... Ask the object to do it.

Cash amountToPay = costOfMeal();
if(cash.canCover(amountToPay)){
    return cash.subtract(amountToPay);
}

We've now taken TWO BEHAVIORS and put them into the object. It's data now doesn't matter, at all. We are asking it to DO for us. Not politely asking it for it's data. The data returned was still raw.

In the fully encapsulated example we don't know anything about how Cash has it's information. They types, the conversions - We know NOTHING.

We know NOTHING!

Do you know how beautiful it is when working with code to be so freed to know nothing about what you're working with!
I get to ask one Cash object if it canCover another Cash object. I can then get the difference of the two Cash objects. Is that a new in, or float, or how does it want them rounded. What level of precision should that be?

We don't have to know!!

I want to really hammer home that when you don't return a reference you hold; your code is almost force to return objects. These objects have behavior. These behaviors are how all objects interact... You're now doing object oriented programming.

Never Return A Reference You Hold

This is the one practice I advise engineers adopt to improve their code base. It starts to force behavior into a single location. Any behavior around a piece of data is co-located with that data.

This isn't a perfect solution; there's a lot more to writing great OO code - and I've written my thoughts on most of them.

When you have getters, you CAN'T have object oriented code. You can do OO without most of the other practices (though it's still terrible), but when you have getters - you have databags and not objects - You don't have Object Oriented Code.

Show Comments