Sonntag, 8. August 2010

Simpler Java #6: On Methods, Functional Programming and Mutation Limitation

Note: See the prelude below for some of my motivations behind this.

#1 Use immutable objects
See below for details on them.

#2 Limit the scope of mutable objects
You _must_ use mutable objects in java for a lot of usecases, where an immutable concept would be simpler and better to use, but the JDK approach is the default "consensus" for the dev community. The most popular example for this is the dreaded java.util.Collections-Framework: Even though immutable collections are way easier to work with, since they can be given limitless to other classes and methods, the JDK choose to go the mutable route instead WITHOUT immutable interfaces at all... As I've told right from the beginning, I do not want to tell you how to write the best code FOR YOU but for the best of everyone. And in the world of java, everyone expects collections to be mutable - like "dates", urgh.

However, not everything is lost as long as you try to follow the following simple rule:

#3Member functions must provide access to an immutable objects only, to prevent second-level changes.
"Second-level"-changes are changes to the members of an object, which can make it very hard to make safe assumptions about the internal state of an object. Not to provide access to mutable members rules out collections, arrays, dates and a lot of other objects as possible candidates for getters. This is a hard rule, there are no bypasses for it.
In general, it permits one of the common problems: You have an object that holds a list, and you want the outer world to get read access for the list or you maybe even want them to set a new list. In the later case, you will trigger some magic action as a sideeffect. (You shouldn't, but just say you WANT it for now).

Do you see the problem? What happens if someone changes your list inplace, instead of setting a new one?   First of all, your magic action won't work.
Secondly, all other assumptions you have made internally about the list may be screwed as well. Usually, these will be some static calculations for performance reasons and the like. And what its now? Misleading crap. You can program defensively around it, but why would you? Its a lot of work to do it, and a lot of unnecessary code lines (and wasted Performance).

#4How can I then provide access to them, after all?
The two possible options are using immutable "wrappers" (which should not have mutators in their signature) or to provide access only to copies of the object.
The later is the common idiom for arrays and dates, and its completely ok, but it can cost your performance if done heavily. The former is the preffered idiom, and an example of this are the immutable wrappers for collections: Collections.unmodifiableList(...).
In a perfect world, the java collections would have been designed in such a way that the list interface provided no write options, and a subclass called "WriteList" would have been made for this. However, there isn't, and working with immutable collections will always have the potential for UnsupportedOperationExceptions on the client side. That problem, however, must be handled by the client, not the caller of the function.

Thats it. This simple rule prevents dozen of dangerous combinations which are really hard to track. But once again: This counts only for the outer world, i.e. in public AND protected scope. On the lower levels AND in method scope, these limitations do not exist. It can be quite common to have lists of mutable objects (which this rule would permit on public scope) as a short-lived object in one of your methods.
Thats ok by all means, since the problems I described will usually only arise if you do not have the full control over who changes what.

One final word: It is not allowed to have all of your classes in the same package for your project to bypass any of the encapsulation rules. Hm ok. If you really can create a project in only one package, then I guess its ok. ;-)

Keine Kommentare:

Kommentar veröffentlichen