Tuesday, January 30, 2007

Method Invocation vs. Local Store

On many occasions, I have seen the code that invokes the same method on the same object several times within few consecutive lines of code. Let's have a look at the following code snippet:

if(person.getDateOfBirth() != null) {
System.out.println("Date of birth: " + person.getDateOfBirth());
}

When I see this repetition of code I feel like I should refactor it order to remove the duplicity. Refactoring itself would be very easy. The return value would be stored in a local variable and used wherever the original method was invoked.

The example above could be refactored in following way:

final Date dob =person.getDateOfBirth();
if (dob != null) {
System.out.println("Date of birth: " + dob);
}

The reason behind my urge to refactor such code is in performance. Method invocation can repeatedly cause execution of potentially expensive operations such as slow database access, network communication or some lengthly calculations.

If in the example above the date of birth of person object was very unlikely to change between the two getDateOfBirth() method invocations and therefore it was a safe bet to store the result locally in a local variable.

Invoking a method once, storing the result in a local variable could speed the application up. However, it could also break it.

Why would a local store break the application? Well, it may not, it really depends on the invoked method itself and the way our application uses the returned value. If a method performs some side operations it actually may rely on its invocation. Such operations can be for example increasing a counter, retrieving updated stock ticker info, etc. The return value may also depend on the number or time of invocations or other things that may affect it. Another example would be an application that performs business logic based on the most recent data.If the data that method returns varies frequently and business logic depends on it, storing it may not be such a good idea.

I'll give you a very bad example here, but this is the extreme where the result is guaranteed to return a different result almost every time:

if (Math.random() < 0.5) {
// notify developer A
} else if(Math.random() >= 0.5) {
// notify developer B
} else {
// this should never happen,right?
// :-)
// but it does happen with likelihood of 25%
}

So it really brings us down to the point where we have to examine what the method does, what data it returns and what the client does with the returned data.

Conclusion

  • Use local store if the invoked method returns consistent results and has no side-effects. Method invocation could be slower or potentially break the client code.

  • Use method invocation if the method invoked has side-effects or the data returned changes frequently and the client relies on the latest result.

3 comments:

Matt Ryall said...

> Use local store if the invoked method returns consistent results and has no side-effects. Method invocation could be slower or potentially break the client code.

I'd add another restriction to that list: only use a local variable instead of a method call if it simplifies the code.

Method calls are the bread and butter of OO programming. All modern VMs are optimised to make them extremely fast. You shouldn't extract a local variable just to prevent calling a method twice -- there should be some simplifying reason, or a measurable performance enhancement.

Often using local variables instead of method calls can make the code more complex. If the code is in a loop, you need to be careful with scoping. If you later want to extract part of the code into a new method, you need to either pass the local variable or replace it with a query method again.

Fortunately, most IDEs make the 'extract variable' and 'inline variable' refactorings fast and painless, so any decision can easily be reversed.

Anonymous said...

Also remember that HotSpot will automatically inline idempotent calls if it can, removing all overhead anyway.

Daniel Pitts said...

local variables tend to prevent more useful refactorings.

public void myFunction() {
final int count = getCount();
someOtherObject.doSomethingWith(count);
doSomeMoreThings();
someOtherObject.doSomethingWith(count);

}
if you inlined count, you could have:
public void updateSomeOtherObject() {
someOtherObject.doSomethingWith(getCount());
}
public void myFunction() {
updateSomeOtherObject();
doSomeMoreThings();
updateSomeOtherObject();
}

This is clearly a simple example. Imaging if you have several values that you want to pass on. If a particular method is slow, but the value doesn't change, then you might consider having it cache the answer.

Only as a last resort, specifically a profiler tells you that the call is costing too much AND there is no way to reduce that cost with reasonable resources, you should use local variables.


Creative Commons License This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.