Eliminating fuzziness of access modifiers

For a while I am puzzled by the unsuitability of public and protected - e.g. default access modifiers in many object oriented languages - for design and especially maintenance of APIs. They are so natural in OOP, yet so harmful for anything that needs to be compatible for more than one version that I have to ask: why there are no better alternatives?

I've summarized my thoughts in Clarity of Access Modifiers essay and I'd like to bring it to attention of LtU readers as I am sure some of you will know the reason why we have these fuzzy modifiers. Also some of you may know about languages that offer better alternatives with more clarity. I am looking forward to hear your opinion. Feel free to comment here or at the page's discussion tab.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

I've come to think about

I've come to think about public/private/protected as common patterns for capability programming: the API designer can cleanly hide capabilities from the consumer and peak into the consumer's object. There are likely benefits for performance, but, overall, that's it. In this light, their limitations on overriding / late-binding are defensive measures: they help library writers enforce invariants. However, they're only a mechanism; for anything interesting, the burden of proof is on the API writer, and these modifiers just serve as coarse tools for implementing it.

Your focus seems to be on the consumer of the object: how can the consumer safely manipulate (extend) one? At this point, the problem is the lack of rich specifications; namespaces (or access modifiers) are just mechanisms, not revealing the finer requirements. The posting for Liskov's well-earned Turing award is timely: Java's only safety system is a weak type system; it's insufficient for what you want. Java allows the expression of basic substitutions, such as your extension doesn't return an int instead of a string, but nothing behavioral (e.g., type state -- see J. Aldrich, or, really, Liskov's seminal paper!). The solution of subtle and ad-hoc reliance on new modifier interpretations, while perhaps helpful for those stuck with a legacy language, seems like lipstick on a pig, especially relative to modern approaches.

Simple but errorprone

Deep in my mind I am rationalistic person and I enjoy solutions with flexibility and elegance. On the other hand, right now I am seeking for clueless solution. A solution that will help people do the right decision with as little understanding as possible. Example:

By default defining method void sayHello() in Java makes it invisible to external API users (which is good, btw.). However as soon as your friend tells you to let him access that method, and you start to seek for the simplest solution, what will you be suggested? Make the method public. Wrong. Well, it works but also exposes you to all the maintenance issues described in my essay.

Having callable around (proposed combination of Java's public final) the like-a-hood a clueless developer did the right choice would be much higher. Hopefully.

Re. "Java allows the expression of basic substitutions, such as your extension doesn't return an int instead of a string, but nothing behavioral" - sounds interesting from rationalistic point of view, however this might be very distinct from problems those millions of Java developers solve daily. Maybe the masses can learn how to describe behaviour as part of their type system in about ten years, but I hope they stop doing the mistakes with public and protected sooner.

Why "capabilities"?

I'm unclear how capabilities and access modifiers relate, unless the root issue is that languages with access modifiers rarely support closures.

Closures are one mechanism

Closures are one mechanism for building invariants in controlling capabilities; access modifiers are another. If you go beyond object capabilities to general language capabilities (instead of "X can't write or read Y's fields" to "X can't write to field Y"), which I'm starting to think is more in line with how we actually write code, access modifiers are natural mechanisms for securing certain operations.

The second part of your sentence suggests, if OO languages had lambdas, they wouldn't need modifiers. Perhaps, with strenuous encoding, that's true, but what's the point? We need to extend a secure calculus to the full language, and, if we really expect people to write secure code, we should reduce all possible barriers to doing so, including forcing them to write strenuous encodings for notions like "private".

There is a (large) chance current modifiers aren't ideal in terms of language capabilities. However, I'd wager a private instance variable is often closer than "lambda" :) I've been playing with membranes where user policies can control objects or even their fields; this is just a more dynamic, less intrusive, and more adviseable form of "private". What's the right way to go? I don't have a user study to say which.

Eiffel, Modula-3 and Dylan...

...have unique solutions along these lines - although none address your specific concerns. LtU discussion on public vs. published interfaces is probably relevant as well.

CTM

Doesn't CTM have something interesting to say about this?

I'll admit I haven't gotten nearly as far with that book as I should...

callable, slot, callback

One of the last references in the "public vs. published" talks about Eiffel and its list of types allowed to access a particular attribute. Interesting. I tried to go through some Eiffel tutorial but it does not look like solution to my problem.

First of all it looks like that in Eiffel there is no way to hide something from subclass (at least there is an example that overrides more_sig although defined as {NONE}). The other problem I see is that the default value for access modifier is "everyone" which is comfortable, but dangerous.

What I am looking for is a language that would make:

  1. by default nothing is visible to API users
  2. method can be marked as callable - then everyone can call it, nobody can redefine it
  3. method can be marked as slot - can be called by definer only, subclasses has to implement it
  4. method can be marked as callback - subclasses can call it, nobody can redefine it

Of course there can still be private, package private, or similar modifiers. Those are behind the scene and API users don't see them. But forcing each API visible method to choose one and only one from callable, slot or callback, is the holy grail I search for. Eiffel does not feel to help with that. Anyone has better pointers?

Default final

It sounds like you mostly just want 'final' to be the default with only abstract methods non-final. Then 'public' and 'private' become your 'callable' and 'callback'. Note that this scheme loses the ability to provide default implementations of methods, since nothing is overrideable. You might consider adding an 'overrideable' keyword for this purpose.

Also note that in moving to this scheme, you're going to lose the standard OO inheritance model, which is the reason the default is non-final. In Eiffel you have pre- and post- conditions which overriding methods must respect. This allows you to reason that a method will work even if the methods it relies on are overridden. Java doesn't have pre- and post- conditions, so its inheritance model seems unprincipled. You can override public methods in ways that break invariants needed by base classes, and the languages provides no support in detecting this.

Algorithm to eliminate fuzzy modifiers

Sort of true. The mapping to existing Java modifiers is:

  • callable = public final
  • callback = protected final
  • slot = protected abstract

and ban:

  • public alone
  • protected alone
  • public abstract

Although I am suggesting to eliminate half of existing modifiers, I do not think the language would loose any expressive power. There seems to be 1:1 mapping to switch any API to callable/slot/callback style while keeping the original power of the API.

Pre/post conditions look like a good idea, just my dreamed system would have to enforce existence of such checks for every overriddable method. I am trying to prevent developers to accidentally make a method overridable while acting without thinking (usual mode of majority of developers, including me). Rejecting compilation until each overriddable method has at least some pre/post condition would force them to think about meaning of their actions immediately and not after few future releases, when it is too late and one can only suffer with consequences.

Poor translation

Your proposed translation does not have the same properties as the original. Your default implementation idiom requires hooking up every abstract method to its default implementation in every concrete class. Worse is that in the presence of any abstract method, your scheme prevents extending a concrete class. You need two versions of every class to do standard OOP - the version where things aren't yet hooked up that you can extend, and the hooked-up version that you can use. This loses the sub-typing relationship between concrete classes that exists in normal OOP.

I agree with your sentiment that making right things obvious and wrong things awkward is a noble goal. I'm just pointing out potentially unwanted consequences of this idea. My viewpoint is that OOP style per-method inheritance is bad, and my reasoning is pretty much based on the line of thought of our last two posts: You need precise types (tight pre- post- conditions) to make it safe but putting such tight checks everywhere is impractical.

Capabilities = Promised Operations?

The design of object-oriented subsystems have always entailed a focus on and proper (and limited) disclosure of close collaborations between classes within a subsystem (or framework). To this end, it may be useful to consider promised operations.