Hiding in Plain Sight - Confession 38

2014.11.13 15:27:43

header My first ‘real’ programming language I started with was Java. I used it for many years and I used it a lot. One convention that I never understood well and only forced on myself for the sole reason of it being a convention was the creation of setter and getter methods for fields. It never made sense to me because in most cases I won't do any computations on the fields when they're being set or accessed, so why a method?

The only point I can see in accessor methods when they aren't used to copy or compute properties is when you have a project that has to be backwards compatible. In that case you might at some point want to change the inner representation of a class and to ensure that other dependant projects don't burn immediately you can emulate the behaviour by rewriting the accessor to refer to the right property or compute it somehow. But there's a funny part to this.

I never in my life had such a situation. I've used Java for many years, built medium sized applications with it, and yet it was never ever a good thing that I had accessor methods. In fact, it would've been easier and better to not have accessors because then if I changed a property I would've gotten compiler errors about bad accesses elsewhere in my code, which would ensure that I always know where to change everything. This isn't a strong answer now because people have invested a lot of work and time into refactoring tools so instead you hit a button that searches your code and does the replaces for the methods. Still, the point remains that for projects that aren't going to absolutely need backwards compatibility accessor methods are pointless.

Now let's look at the other case where I granted accessor methods: When you have to compute a property rather than directly returning it. In that case the method should probably be called something entirely different altogether anyway to ensure the user knows what the balls is going on in your API and doesn't just shrug and suppose it's a direct field access. So this point, too, is effectively a moot one. But then, what kind of situation can we think of where accessor methods would actually be granted?

You might argue that you want a consistent style of interface. In which case there's no argument either because if you had no accessors whatsoever you would have a consistent interface too: properties and computational functions. So, no, this does not count either.

The absolutely last thing I can think of would be read-only properties. In this case, the only solution really is to declare your field private and put up a getter method. However, in probably the majority of cases fields are not read-only and even if they are they could probably be read/write without making the system more prone to disaster.

I know that the purpose of accessors is usually stated to be ‘information hiding’. That in itself isn't a proper reason though. How is it still hiding if it's the exact same as accessing the field directly, minus a function call? If it is actually done with the purpose of hiding the field, should it actually really be hidden at all? Why hide anything?

This blog mainly came together as another tangent on something I recently encountered on IRC: “So much of programming is about abstraction. Hiding things so others don't have to think about them”. It suddenly made me realise just how prevalent the idea of ‘hiding’ is in programming. An idea I strongly disagree with, as I don't see that as the job of abstraction or a programmer at all. Getters and setters are just the epitome of that idea.

As a programmer your task is never to hide anything. It is to build abstractions so that annoying and repetitive tasks become convenient and easy to use. No part of this necessitates or in any form encourages hiding. The idea of ‘protecting the user’ by hiding things is something that I blame OOP for and strongly despise because it's so wrong. In fact, you should expose as much of your library as you can to cover for all the corner cases that you could not expect. If the top level of your abstraction tower is not sufficient, a user might hook into a lower part and still get their work done without having to either switch to a different library or wait three years for you to implement what they need.

But I hear you scream, what of my precious program state? What if the user unknowingly disrupts it and everything goes bonkers? What if I have to change the internal architecture and that will change the low-level interface? There are answers to all of those questions. Most of all, it is trust in your user that they know what they want to do. Proper documentation (this includes the interface design), too, is a vital part as then users can know what to expect if they dig deeper. And as for the case where you have to change lower parts, if your environment has good debugging tools and, again, documentation is provided, that too should not be too big of a problem.

Generally I find that information hiding is a bogus concept that corporate programming, fear, and OOP have forced onto the minds of everyone, poisoning the ideas and programs with pointless wrappers that don't have to exist in the first place. CL has helped me realise what an absurd idea this is, as it is impossible to completely hide anything in it. There's always a way to access someone else's data and that has been useful to me many times.

To me it is a much bigger problem to expect what the user might want to do than it is to keep your software ‘protected’. When I write a program I know what I want out of it, but making it accessible to others requires thinking about what they might want to do, and there I only know for sure that I won't ever think of all the use-cases, if even a majority of them. So, I instead write documentation for what I have and trust my users to take the responsibility upon themselves if something breaks when they attempt a more risky manoeuvre. And if it does indeed burn, then most likely my code is at fault for not covering their needs, or not properly explaining how to use it.

To reiterate: Your job is to write abstractions and make life easier. Hiding is only ever your job if not doing so would make life harder, and I find that is extremely rarely the case.

Necessary caveat: Of course I don't expose everything in my software either, but I don't go to any lengths to hide things unless I am absolutely sure it is closely tied to some other function or state that renders it useless outside of its context. In CL I like to think that exported symbols are “advertised”, everything else is just “there” and only percent-prefixed symbols are truly and honestly supposed to be “hidden”.

Written by shinmera