Strong vs weak encapsulation

# David-Sarah Hopwood (14 years ago)

Strong encapsulation means that the code implementing an abstraction can control the visibility of its fields (i.e. where they can be accessed from), without any loopholes that can be exploited by code outside the abstraction's scope. Weak encapsulation allows such loopholes. Note that I deliberately use the term "fields", in order to avoid the over- specification that private fields necessarily have to be ECMAScript properties.

I suggest that we discuss which of these strengths of encapsulation we want, separately from the choice of how to specify the semantics of private fields.

In principle there are three possible positions:

a) private fields should always be strongly encapsulated; b) private fields should be weakly encapsulated; c) private fields should be strongly encapsulated when a sandbox is in effect, and weakly encapsulated if not.

I will be arguing for a) and against both b) and c).

We can analyse this question from the complementary perspectives of either software engineering or security. The security perspective is fairly straightforward: weak encapsulation does not provide any benefit because it can be bypassed by attacking code. c) may be acceptable, but a) is preferred, so that code not originally written for use in a sandbox is less likely to fail when run inside one.

From the software engineering perspective, we need any benefits of encapsulation to be derived even when not running in a sandbox, which means that b) and c) are roughly equivalent, and the choice is between those and a):

Weak encapsulation creates a situation where the incentives of the programmer(s) providing an abstraction are misaligned with the incentives of the programmer(s) using it. The provider wants to be able to rely on the encapsulation to prevent clients from depending on implementation details, so that those details can be changed compatibly. The client programmers just want their code to work. The client can (sometimes) get their code to work by breaking the encapsulation. This is often perceived to be easier than communicating with the provider in order to get the bug fixed or support the missing feature.

Some client programmers, faced with such a problem, might refrain from breaking the encapsulation, even though they could have used it to work around the problem. In that case, of course, they obtain no advantage from the encapsulation mechanism being weak. Only clients who do choose to break the encapsulation obtain any such advantage, and they do so only at the expense of making their code more fragile and creating compatibility problems for the provider. The provider can choose to ignore such problems, but then any client code that violated encapsulation will be liable to malfunction when it is used with the new version of the provider's code. (It is possible that any particular change will not break any particular client, but that would only be by luck.)

Misaligned incentives do neither party any good.

There are a couple more points that strengthen the above argument:

  • it may be the case that an encapsulation-violating client appears to work in testing, but actually doesn't in all situations. This is quite likely when the client programmers' understanding of the provider code is based on reverse-engineering.

  • the ability to break encapsulation as a workaround may create a disincentive to reporting the bug or feature request. The abstraction is therefore less likely to be changed in a way that other users could benefit from.

A possible counterargument goes something like this:

By breaking encapsulation, a client programmer may be able to make this version of their code work with the current version of the abstraction they are violating. This does have some value (even though the code is now fragile, and less understandable because the provider and client code cannot be understood independently).

As you can probably tell, I'm not much impressed by this counterargument. It's a viewpoint that favours short-termism and code that works by accident, rather than code that reliably works by design.

Note that in the case where the same programmers are writing the provided abstraction and its only clients, it makes no sense to use reflection to bypass encapsulation; they might as well make the field public (or use selective visibility as discussed below). Doing so would result in clearer code. So weak encapsulation provides no advantage in this case either.

There's another potential counterargument against the way strong encapsulation is supported in some (typically class-based) languages, that may have more merit. It says that a sharp distinction between "public" and "private", where "private" fields can only be accessed from within their class definition, can sometimes be insufficiently expressive. That is, we might want "private" fields to be selectively accessible from outside the class definition, but not to all code outside it.

Note, however, that for both the soft fields and the private names proposals, the scope in which a private field can be accessed can be controlled lexically. (In the soft fields case, this does not depend on the use of the 'private id' syntax.) This can be used to simulate visibility mechanisms found in other languages, such as export lists and interfaces with different visibility in Eiffel, package access in Java, etc.

Not all visibility mechanisms from other languages can be sensibly simulated this way. For example, friend declarations in C++ allow arbitrary code to give itself private access to arbitrary other code. ("I'm your friend! Deal with it!" :-) That could only be simulated by putting the 'private' or SoftField declaration at global scope, which would be pointless. Note that friend declarations are a widely criticised misfeature of C++ -- e.g. see www.zechweb.de/Joyners_cpp_critique/index003.htm#s03-26 --

precisely because they are incompatible with strong encapsulation.

The private names and soft field proposals are similar in the visibility mechanisms they can simulate, but soft fields are slightly more general. In either proposal, visibility can be restricted to a particular lexical scope. In the soft fields proposal, because SoftFields are first-class values, it can also be restricted to any set of objects that can get access to a given SoftField. I don't claim this to be a critical benefit, but it is occasionally useful in object-capability programming. For example, in www.erights.org/elib/capability/ode/ode-capabilities.html#simple-money,

a Purse of a given currency is supposed to be able to access a private field of other Purses of the same currency, but not other Purses of different currencies. The implementation at www.eros-os.org/pipermail/cap-talk/2007-June/007885.html

uses WeakMaps to do this, and could just as well use soft fields if transliterated to ECMAScript.