39

The theory is that access modifiers improve code safety because they support encapsulation of internal state. When doing OOP, every language I've used implements some kind of access restriction. I like some access models better than others.

I am on a team of Java developers. Our projects spend time in code reviews considering the access modifiers, their appropriateness, and the use of things like @VisibleForTesting (a Java annotation). Our projects also occasionally spend time de-finalizing or de-privatizing something in a 3rd party library if a source-code change is not feasible.

I went looking for the research that shows how the use of access modifiers affects defect density or occurrences of run-time errors. I cannot find any studies on it. Maybe my Google-Fu is weak. What is the evidence that access modifiers actually provide the benefits we assume they do? Where are the studies that quantify the problems with how access modifiers are used?

  • 10
    What benefits do you assume access modifiers provide? If asked, I wouldn't have said anything like "security" or "fewer defects". I would say they mostly exist to explain intention, as in "this is a private method, it's not here for you to call", even though there are easy ways around it. – Eric King Oct 28 '19 at 22:35
  • 1
    Enforce, would be the word. You can bypass conventions, but not (at least not trivially) modifier-enforced access restriction (i.e. the compiler). – Vector Zita Oct 29 '19 at 2:42
  • 3
  • 2
    Asking for external resources like studies and research are off-topic. But it is quite likely no independent research have been done for such a particular question. – JacquesB Oct 29 '19 at 14:07
  • 2
    @ahoffer: you seem to have missed the point why your question was closed. Please read Robert Harvey's answer again, then you may develop an idea how to ask differently, with a different expectation. – Doc Brown Oct 31 '19 at 7:46
11

To complement the existing excellent answers from Doc Brown and from Gort the Robot, another way of looking at access restrictions is as one of many ways we write modular programs.

In the simplest architecture, all variables and lines of code are accessible from all scopes. This would allow you to assemble code in any combination you wanted, and as the program grows, the number of combinations increases exponentially, making it hard to reason about what the program will do.

To improve this, we break down programs into reusable modules of various types. Each module has a contract defining the ways the program can interact with it; and an implementation which is hidden from other code.

The simplest type of module is a function: function signature (name, parameters, and return type) defines the contract; and the implementation can include local variables, and lines of code that cannot be jumped into from outside the function. The number of possible combinations then reduces from all possible interactions of variables and lines of code, to all possible interactions of functions.

Visibility modifiers simply iterate the same advantage: by grouping functions and variables into classes or packages, we can give each grouping a contract and an implementation. The number of possible combinations reduces further, to valid interactions of classes, or packages.

All of this can be implemented by convention rather than as part of the language - you could name global variables carefully rather than having local scope, and you could name functions carefully rather than having visibility. The more universal those conventions are, the more tools can support them - e.g. offline checkers can tell you if you're validating contracts, and IDEs can suggest only "visible" members.

In order to be truly universal, the convention needs to be baked into the language, and enforced by default tooling. This matters most when sharing or handing over code: if most tools read a leading underscore as meaning "private to this class", but the default compiler/runtime doesn't enforce that, you have to make a judgement whether someone else's code uses it "correctly". If you see private in a Java program, you can make pretty strong assumptions about the contract.

Ultimately, it's about trading flexibility for the ability to reason about and maintain the code. Just as there are cases when using goto rather than functions helps solve a problem, there are cases where accessing a private method gets a job done. If the language has not made that trade-off by what it enforces, the programmer has to make the same trade-off by how they use it.

| improve this answer | |
  • 1
    The last paragraph is a rather weak argument. Python, for example, only use naming conventions to denote private attributes, but most devtools written for python also "understands" the convention and can be configured (or are preconfigured) to skip suggesting names that indicates private attributes outside the appropriate context. – Lie Ryan Oct 29 '19 at 13:07
  • @LieRyan Hm, I see your point, I'll think about it a bit. Essentially I think there are two different levels of "language support" (or possibly a continuum): making the conventions sufficiently universal that tools can be written to use them; and making them mandatory, in the sense of "if it's marked private, you can't cheat and call it anyway". – IMSoP Oct 29 '19 at 14:03
  • @LieRyan I've expanded that paragraph into three, to go into the questions of standardising and enforcing the conventions. – IMSoP Oct 29 '19 at 14:34
  • Think about it like this, @LieRyan. If you're working with a group of nerdy programmers, and they, e.g., prefix "protected"-equivalent names with Invisibility and "private"-equivalent names with GreaterInvisibility, then their convention is perfectly clear to anyone in the group (possibly after explanation, if necessary), but you'd be hard pressed to find a tool that understands it. ;P – Justin Time - Reinstate Monica Oct 29 '19 at 22:08
  • This is the answer I chose. Thank you! I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. – ahoffer Nov 3 '19 at 20:34
68

Let me give you a real world example of when access modifiers "mattered" that I ran into personally:

Our software is primarily python, and one way that python differs from most other OO languages is that there are no explicit access modifiers. Instead, it is convention to prefix methods and attributes that should be private with an underscore.

One day, a developer was working on a particular feature, and could not make it work with the interface of the object he was working with. But he noticed that if he worked with a particular attribute that was marked private, he could do what he wanted to do. So he did it, checked it in, and (unfortunately) it slipped past code review, and into the master branch.

Fast forward two years. That developer had moved on. We updated to a newer version of an underlying library. Code that had been reliably suddenly stopped working. This resulted in lots of debugging and back-and-forth messages with another team in a different time zone.

Eventually we figured out the issue: the developers who owned that underlying object changed the way it worked in a very subtle way. Subtle enough that no exceptions were thrown, no other errors occurred. The library just became flaky. This happened because the developers of that library had no clue that they were doing anything that would cause any troubles to anyone. They were changing something internal, not the interface.

So after the fact we did what should have been done originally: we asked the library developers to add a public method that solved our problem rather than mucking about with the internals of their objects.

So that's what access modifiers prevent. They ensure that the separation of interface and implementation is clear. It lets users know exactly what they can do with the class safely and lets developers of the class change internals without breaking user's software.

You could do this all with convention, not force, as python shows, but even where it's just convention, having that public/private separation is a great boon toward maintainability.

| improve this answer | |
  • 7
    @LieRyan I’m not sure how relevant that observation is. There’s tons (!) of research that firmly establish that spending time solving problems early is way cheaper in the long run than solving them later. See Code Complete for an overview over some of that research. – Konrad Rudolph Oct 29 '19 at 13:20
  • 7
    @LieRyan Having a solution that worked for two years of working code is a bit of a false dichotomy. The proper solution (as described in the answer) would have also produced 2 years of working code and more with minimal/zero additional effort. – JimmyJames Oct 29 '19 at 14:20
  • 6
    @LieRyan There's another alternative: you patch the open source code yourself. – JimmyJames Oct 29 '19 at 14:51
  • 5
    @LieRyan: in the given case, apparently it was possible to ask the library developers successfully to add a public method, even two years later. That gives me the impression the developer who made the brittle change did not even try to ask the library team in first place. So yes, ignoring access modifiers to "get things done in time" is sometimes necessary, but when doing so, one should also give a serious try on finding a better solution. – Doc Brown Oct 29 '19 at 15:03
  • 6
    @LieRyan What you said doesn't make sense. Meddling with internals is worse than having to "maintain a fork" as this answer shows. Because whenever you upgrade it can break (and you may not even realize immediately). IMHO it's just better to create a fork with the fix, create a pull request for the public library. It's probable that it will be merged and once it's merged you have completely solved the problem forever (and probably for other people as wel) – Giacomo Alzetta Oct 29 '19 at 17:08
26

To my experience, the primary benefit of access modifiers is not the number of errors they prevent, so it would not make much sense trying to make a statistics over this.

The real benefit is, especially in large code bases, they facilitate the impact analysis of changes to a code base by several orders of magnitude. And that is something I experience daily. For making a change affecting only private stuff the consequences can usually be narrowed down to a relatively small part of the code base. For making changes to public parts the analysis is often way harder, since a change to a public function in one module could theoretically affect all depending modules.

Of course, even a change to some private function can have consequences on a larger scale, and not every change to a public method affects the code base as a whole. But access modifiers allow us better reasoning about what and where the majority of changes will have (of have not) wanted or unwanted effects, and so let us implement such changes correctly in a more efficient manner.

Let me add that to my experience, for small programs with less than, lets say, 2K lines of code the effect is small, but when you work at a system with >200k lines of code, things look differently.

| improve this answer | |
  • Thanks. This is a compelling reason that access modifiers matter. I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. – ahoffer Nov 3 '19 at 20:32
17

Access modifiers are a technique for implementing encapsulation. Use them if you seek the benefits that encapsulation provides.

In general, best practices in Software Engineering are not dictated by academic studies or double-blind, peer-reviewed experiments; they are dictated by pragmatism and experience. The evidence is "does it work in real life?" Or (being pragmatic) "do the benefits outweigh the costs?"

It also depends on what your goals are. If you come from a background where brevity is valued over explicitness, you may find that access modifiers do not benefit you at all. But sometimes you don't get that choice. When you work with a team of Java developers, the right things to do are generally the things that fit Java's sensibilities.

When in Rome ...

| improve this answer | |
  • Access modifiers have almost nothing to do with brevity. They have everything to do with division of labor and project size. For small one-person projects they are less beneficial. – Peter - Reinstate Monica Oct 29 '19 at 14:24
  • 2
    @Peter: I guess you've never heard of getter and setter methods. – Robert Harvey Oct 29 '19 at 14:47
  • Oh I have. That's the "almost". Bordering on the irrelevant for large projects. I truly hope you don't by default provide getters and setters for your private data? ;-) And the typical use case is that a class is written once (some additional typing for the access methods) but used multiple times (almost no additional effort, e.g. x() instead of x). Plus all the benefit is gone with your first refactoring which will be a nightmare if you touch the wrong members. – Peter - Reinstate Monica Oct 29 '19 at 14:54
  • 1
    ... or El Javarino if you’re not into the whole brevity thing. – JimmyJames Oct 29 '19 at 14:55
  • 4
    @peter: Well, my remark wasn't about brevity anyway. It was about "what do you consider valuable?" That's what makes questions like this so interesting: "Prove to me with double-blind, peer-reviewed studies that access modifiers are worth it." Impossible. "Worth it" is a value judgement. – Robert Harvey Oct 29 '19 at 14:56
8

Safety and maintainability aside, it keeps you sane.

Let's make this real world, without the fancy mumbo jumbo. Suppose you get a class library to support you doing your job. You know what it is supposed to do, you just haven't worked with it yet. So you add it to your development environment and look at what's in there.

Wow! 1200 classes! What the... what were these guys thinking? I only need to do whatever so reasonably I should be able to get by with 5, 10 classes tops. And they give me all these. I bet 99% of this is totally irrelevant to me, why don't they just show me the classes that really matter to me?!

So you browse through the name spaces and spot some class name that looks promising. It is called ThisIsTheOneYouActuallyWantToUse. How convenient. You type:

ThisIsTheOneYouActuallyWantToUse instance = new ThisIsTheOneYouActuallyWantToUse();
instance.

and IntelliSense shows you all the methods and properties this class has to offer. Wow! 200 members! What were these guys thinking?! Do I need all those? I bet not. Why did they not just show me the ones that matter to me as a user?

Et cetera. You complain for the rest of the day and you go home tired and disgruntled.

[Edit]

This illustrates the experience of an application developer using a third party library, trying to find a useful method in a relevant class. Note that this issue plays out on all levels. It can be a problem when one developer on a team reviews the work of another. Think of an end user who gets a menu with a couple of hundred items all flattened to one level. Or a document without chapters or paragraphs, only punctuation. A map of a city with one street name (if you use an integer for a house number, that would simplify things radically, who needs all these stupid sectors and street names anyway?).

As a user of other people's work you will always look for hierarchy, for main chunks that represent something. You need to see the forest from the trees to understand anything and zoom in on what you need in a couple of steps. Access modifiers represent just one level in the chain of organisations, systems, applications, modules, name spaces, classes and members.

| improve this answer | |
  • Swift (where everything is in one file, like Java) has a nice feature where it can display a header file from your source code. Guess what: All the private methods and private members are gone. So making things private that should not be accessible from the outside removes all the clutter. – gnasher729 Nov 3 '19 at 20:02
  • Good point. That is a very practical reason who access modifiers matter. I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. – ahoffer Nov 3 '19 at 20:33
3

In addition to everything that others have said, access modifiers are or can be important documentation. They document what the intended API is. The intended API is often only a small subset of all the methods that are defined. Having methods clearly labeled as "Not intended for use by clients" helps to avoid mistakes, and find the correct methods to use. In addition, that contract is checked by the compiler, great!

| improve this answer | |
  • Thank you! I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. – ahoffer Nov 3 '19 at 20:34
3

I don't know of specific research but there is a trove of failed software projects to study. Some of them fail because of their complexity. Complexity of monolithic software grows exponentially with size. Like all exponential growth the complexity eventually exceeds available resources: Throwing more and more manpower at a project will not save it if it is unstructured and too large.

Complexity is reduced through a software engineering paradigm conceived before most of us were born: Divide et impera.

For this, the parts must be loosely coupled or they are not real independent parts.

That's all there is to it. Access modifiers prevent one way of tight coupling by making only a defined subset of their members available to the outside. Enforcing this on the language level as opposed to making it a coding rule is necessary because programmers are notorious for their lack of self-discipline and coding rules are only enforced by rigorous reviews.

| improve this answer | |
  • You have a hypothesis that use of access modifiers results in more loosely coupled systems. I saw cases where this is true. I also saw cases when it is not true. I have seen cases Logically, the most loosely coupled software I saw had no access modifiers. When a compiled library uses another compiles library, only the public/API functions in the header are visible. Even in those situations, if the library returned nested structures and arrays, the modules could be very tightly coupled. I have a hypothesis too. – ahoffer Oct 29 '19 at 17:40
  • My hypothesis is that current best practices about how to use access modifiers lowers productivity more than it enforces loose coupling. I am looking for evidence to support or falsify these hypotheses. – ahoffer Oct 29 '19 at 17:44
  • Saying the benefit is self-evident is of no use at all. OP is asking so either it is not as self-evident as you believe or OP does not know the point and it is better to just explain the usefulness than risk sounding condescending. – Captain Man Oct 29 '19 at 19:05
  • @CaptainMan I see your point. Editing. – Peter - Reinstate Monica Oct 29 '19 at 22:34
  • @ahoffer There is more to software design than access modifiers, true ;-). – Peter - Reinstate Monica Oct 29 '19 at 22:42
2

Do access modifiers matter? Yes they do. I think the answer with the python example is illustrative enough. However, there are some pitfalls on this, especially considering how it is often used in the java world:

  • You have to be honest with your access modifiers.
  • What is public will be treated as public by any user of your class. You might not be able to change or remove public methods if users of your class are not under control
  • As much as visibility, mutability matters.
  • In larger systems decoupling is often already done by using interfaces which only expose the public methods but not any members or even the concrete implementations

Nothing makes a class more complicated than having 10 members with a trivial, often auto-generated getter and setter. It is not useful in any way to make the variable foo private and provide a direct, unchecked setter like this

    class Foobar {
      private Foo bar;

      public void setBar(Foo bar) { this.bar=bar; }
      public Foo getBar() { return bar; }
    }

In this case you have gained nothing on a public variable but added two methods to your class. And if everybody can directly set (almost) all your members you have not decoupled in any way. At the same time, knowing that something is immutable can make life easier as a caller.

So I would recommend the following

  • If you have a primitive type and it is immutable, make it public and final
  • For methods try to rely on interfaces for information hiding, not on individual classes. It allows you to exchange a bunch of collaborating classes without external dependencies
  • For variables of complex types try to pass them during construction (if possible). Avoid trivial getters and setters as much as possible
| improve this answer | |
  • Regarding trivial getters and setters, they're sometimes an important part of allowing a class to evolve. Where version 1 has a trivial one-line setter, version 2 might need to proxy the call through to another object, or keep multiple pieces of state in sync, so you don't want users accessing the data field directly. In some languages, that can be done with special property syntax to define the get and set behaviour, but in others the only way is to wrap it in a method, and keep the property itself private. – IMSoP Oct 29 '19 at 23:09
  • While this is a correct argument in theory, the reality so far proved different at least in my experience. Once the code is out there is always something more important to do than adding simple checks. And if you get complex dependencies and checks, there is often a major class rework involved anyways. Spraying trivial setters just because there might be an evolution of the class is a classical example of overengineering and waterfall thinking. If customers are supposed to use your class, there should be an interface anyways that the customer is implement against – Manziel Oct 29 '19 at 23:26
  • The kinds of object where you want trivial setters are rarely the kind where you want interfaces; they're more likely to be simple data objects. As for the rest, I'll just agree to disagree; I see setters as a low-cost piece of encapsulation that I've taken advantage of many times. – IMSoP Oct 30 '19 at 7:22
  • If it is a simple data structure there should be nothing preventing one from having the check in version 1.0 (it does not have to be on version 0.1 however). If you add checks to code that is already on use you will break the code in a non obvious way. It will still compile as before but may fail during execution. Your callers better have a good test coverage then – Manziel Oct 30 '19 at 10:46
  • I never mentioned adding checks that will fail during execution; I mentioned adding logic, encapsulated in the class, that is completely transparent to the code outside the class. Simple example: version 1 of a class has fields called string addressLine1, string addressLine2, and string addressLine3; version 2 changes the implementation to string[3] addressLines, but maintains the contract that setAddressLine1(string) will set the first line of the address. It might also add a new contract that setAddress(string[3]) updates all three lines at once - or both might already exist. – IMSoP Oct 30 '19 at 11:18

Not the answer you're looking for? Browse other questions tagged or ask your own question.