41

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?

ahoffer
  • 613
  • 7
  • 19
  • 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
  • Intention could be communicated by a comment or a convention like underscored names are private to the object. – ahoffer Oct 28 '19 at 23:10
  • 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 02:42
  • 3
    Obligatory Yegge from 9 years ago: [Wikileaks To Leak 5000 Open Source Java Projects With All That Private/Final Bullshit Removed](http://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5000-open-source-java.html) – Benjamin Gruenbaum Oct 29 '19 at 12:35
  • @BenjaminGruenbaum: Obvious satire is obvious. There's plenty to dislike about java, but it's hard to argue with its ubiquity. – Robert Harvey Oct 29 '19 at 13:33
  • 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
  • @BenjaminGruenbaum: I had not seen that Yegge post before. Thank you! :-) – ahoffer Oct 29 '19 at 17:47
  • Answers have already pointed out Python as a non-access-modifier language. There are others too. And even in languages that do have access modifiers, some people just ignore them or make everything public, making a project essentially non-access-modifier. – Aaron Oct 29 '19 at 19:04
  • A simple example here is how not all the buttons of a vending machine are on the outside. Some more advanced buttons (e.g. to dispense a drink without paying - for testing purposes) are kept from the average consumer as it is ripe for abuse. – Flater Oct 30 '19 at 16:22
  • "put on hold as off topic" sigh. This happens to me a lot on this exchange. I wish there were a forum somewhere on the Internet where professionals could hold lively and engaging discussions about software engineering. Maybe we could create a Stack Exchange for that purpose? – ahoffer Oct 31 '19 at 03:08
  • 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 07:46
  • ["We already tried supporting those questions, we even gave them their own site. Sadly, it didn't work out..."](https://meta.stackexchange.com/a/200144/165773) – gnat Oct 31 '19 at 08:48
  • 1
    @Doc Brown. I don't think I have misunderstood. I believe that the acceptable topics for this Exchange are too narrow/overly constrained to make the site as useful and interesting as it could be. A reason given for putting the discussion on-hold is that the question "attract(s) [answers] that won't have lasting value to others." Read the answers. Do you think any of them have lasting value to others? I do. In any case, this is a meta-comment and belongs on a meta channel. If I want to purse it, I'll take the discussion there. Regards, Aaron. – ahoffer Nov 03 '19 at 18:42
  • Thank you to every who participated in the discussion! I have created a summary of it here: https://gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad – ahoffer Nov 03 '19 at 20:16
  • In PHP explaining intention worked fine just prefixing the internal members like `function _internalSupportMethod():`, even before there was private/public. Any new access modifiers can easily be ignored or changed using the reflection-api or even by preprocessing the source using stream-filters, so ...erhm? Sigh.. PHP will soon be source level compatible with Java... xD – Christoffer Bubach Feb 10 '21 at 15:57

8 Answers8

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.

Gort the Robot
  • 14,733
  • 4
  • 51
  • 60
  • 2
    Great example. Thank you. Data hiding is a useful concept. I've done it by underscore convention in C. I've seen it done by convention Python and JavaScript. I programmed Smalltalk and felt its access model was a good compromise between flexibility and effectiveness. For the last four years I have suffered greatly under the popular interpretation of how Java's access model should be used. I believed that people wouldn't inflict that on themselves unless there was some damn good empirical evidence for doing so. I think I was naive. – ahoffer Oct 28 '19 at 23:09
  • 2
    There are definitely differences in community attitudes as @RobertHarvey mentions, and in a perfect world, we'd all get to join the communities that matched our preferred style. I feel your pain as I personally hate the Java conventions with the fire of a thousand suns, which is a good part of the reason I'm do python. But the one thing worse than any particular style is not following the style of everyone else. – Gort the Robot Oct 28 '19 at 23:30
  • On the flip side, not having an access modifier nanny means that two years ago, that developer is delivering features on time. If the access modifier had been there preventing you from using the internal interface, you'd probably be waiting a couple weeks and maybe even months to petition for the library developer to open up the interface and likely to reimplement it with an interface that's suitable for a public API, and probably would've delivered the feature late or none at all. – Lie Ryan Oct 29 '19 at 12:51
  • 1
    That feature worked and delivered business value for two whole years, that's a real, measurable results there. The root of the problem here, is that the previous developer should have written tests when they decided to use an internal method, to ensure that the behaviours they depended on don't suddenly break. – Lie Ryan Oct 29 '19 at 13:00
  • 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
  • @ahoffer I realize this is the wrong place to ask, but I'm *very* interested in learning what you mean by "great suffering". Could you please elaborate a bit? – Celos Oct 29 '19 at 13:31
  • 1
    @KonradRudolph Not quite sure how that's relevant. Sure if you control both the application code and the library code, then it certainly makes sense to just solve the issue right then and there, I don't disagree. But more often than not, you can't control the direction of third party developers/libraries. And if the library has been abandoned by the original developer, the issue may never going to be fixed in the library. – Lie Ryan Oct 29 '19 at 13:49
  • 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
  • @JimmyJames The answerer is lucky in this case that the library developer is responsive to the change request. If you've been developing for any amount of time, you would see that not all library developers are as responsive to such requests. Many might not agree with your change request, and often they're busy with other stuffs and don't have time to deal with your issue. Yes, even if you've worked around the access issue, you still should file a bug report, but in the meanwhile, the solution worked just fine and is delivering value, and that's what matters most. – Lie Ryan Oct 29 '19 at 14:40
  • @JimmyJames I've often seen developers in large and small library projects sitting on perfectly written pull requests for years without pulling, for a bug that I deemed very crucial for the project I'm working, but for which aren't the priority of the library developers. Your only other real alternative here is to tell the customer than you can't deliver the feature. – Lie Ryan Oct 29 '19 at 14:50
  • @LieRyan Sure but I've patched open source Java libraries while I waited for a fix. The key in either case is that you keep track of the fact that you did that. Don't get me wrong, I'm a huge Python fan and it's my go-to language but the reality is that refactoring code is much easier in Java. It's nearly effortless in comparison. – JimmyJames Oct 29 '19 at 14:50
  • 6
    @LieRyan There's another alternative: you patch the open source code yourself. – JimmyJames Oct 29 '19 at 14:51
  • @JimmyJames Yes, and you'll now have to maintain the fork, and still have the same issue, which is that the original library developer made subtle changes that breaks your patch's assumptions. Either way, the original developer won't take your patched code into account when making their changes. So what would that really solve here? – Lie Ryan Oct 29 '19 at 14:56
  • 1
    @LieRyan "So what would that really solve here?" Delivering the feature. You stated/implied that there was no other way around this. The point is that the situation is not that dire in a language with enforceable modifiers. It's just a tradeoff. I don't see patching the library directly as being any worse than accessing internals and I think an argument could be made that it's a better solution. If your patch breaks on a new version, it's likely your monkeying with the internals will to. And in Java you find out right away: your build breaks. – JimmyJames Oct 29 '19 at 15:02
  • 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
  • Yes, this was exactly what happened. These were all internal teams. Doing the right thing involved a slack message and waiting a couple days for a PR. – Gort the Robot Oct 29 '19 at 15:20
  • 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
  • @GiacomoAlzetta Maintaining a fork that exposes the internals is meddling with internals. And it is no less brittle than just using the internals. In both cases the library developer won't take your use case into account. Whether you maintain fork or just crack into internal methods, you should be pinning your third party dependencies and have tests to catch breakages either way. And I think we both agree that submitting a merge request is the best solution, when it's possible. – Lie Ryan Oct 29 '19 at 22:44
  • @LieRyan I fail to see where I say that the fork should expose internals? The fork should provide an API to do what you need. You do **not** have to simply make public what was internal! – Giacomo Alzetta Oct 30 '19 at 08:08
  • @GiacomoAlzetta All the same. If your forked code uses an internal API to provide a public API then your patch is fiddling with internals in ways that the original library developers hadn't considered, and they won't be considering your forked code when making internal changes, i.e. the fork is brittle. If your forked code can implement what you need without calling into an internal API, then you would not have needed to fork the code to do whatever you were trying to do, so why are you forking? – Lie Ryan Oct 30 '19 at 08:19
  • 1
    @LieRyan So according to you forking is **always useless**. Nice, goes on to say of your understanding. All those people that creates millions of forks that are regularly merged in GitHub & co are just dumb people that don't understand that forks are utterly useless because they either meddle with internals and cannot be accepted by the authors or use only public apis and can thus be replaced by wrappers. Good job! The whole purpose of a fork is to extend implementation. If you are good you will do it modifying the implementation sensibly and the fork is accepted (as happens **regularly**) – Giacomo Alzetta Oct 30 '19 at 11:08
  • 2
    @LieRyan If the library developers were unresponsive to the point where contacting them initially would not have been viable, then they would probably also have been unresponsive two years later - which would result in the application breaking with the only short-term fix being to disable a feature that customers were already using. Most PMs would rather face the situation of "this feature will take longer than expected" or "we are now on a custom version of library X, upgrades will take longer in future" than the situation of "the app is now suddenly broken, fix unknown" – Errorsatz Oct 30 '19 at 20:54
  • Thanks Gort for your answer! I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. @Celos. You might find an answer to your question there. – ahoffer Nov 03 '19 at 20:31
  • Unplanned - probably quite extensive - source reorganization right away vs 5min headache in two years time, due to - what I can only suspect where a totally untested - library update... Ok. Not quite the horror story I was expecting. xP – Christoffer Bubach Feb 10 '21 at 16:10
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.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 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 03 '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 ...

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 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
12

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.

IMSoP
  • 5,722
  • 1
  • 21
  • 26
  • 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 03 '19 at 20:34
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.

Martin Maat
  • 18,218
  • 3
  • 30
  • 57
  • 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 03 '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 03 '19 at 20:33
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.

  • 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
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!

kutschkem
  • 530
  • 2
  • 9
  • Thank you! I have created a summary of the discussions here: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad. – ahoffer Nov 03 '19 at 20:34
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
Manziel
  • 380
  • 1
  • 4
  • 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 07: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
  • Well this statement is a bit different from the original one. But now we reached a point where the use case matters and there is no general answer. Depending on what your program is doing, it might be correct to expose address lines, it might be better to keep it as a single string or it might be necessary to have a full collection of specialized classes for addresses in different locales. But anyways, I think we both made our point ;) – Manziel Oct 30 '19 at 21:19
  • I'm not sure how it's different from what I said in my very first comment, but I'm glad you've understood my point now. And yes, it absolutely depends on the use case, and not all new implementations can be transparently wrapped to meet an older contract; but by adding getters and setters rather than exposing the implementation, you at least give yourself the chance to try. – IMSoP Oct 31 '19 at 08:59
  • Great advice. Thank you! I have created a summary and incorporated your advice: gist.github.com/ahoffer/d3c5fb0f7b652561edd0db1a052c68ad – ahoffer Nov 03 '19 at 20:35