Thursday, May 31, 2018

Get and Set

Editor’s note: I found this two-year-old draft hanging around with all my other posts on my PC.  What follows is the original post, since it appears to have aged fairly well, although it could use a conclusion…

It happens that having to call methods of a class in a particular order—unless there’s an obvious, hard dependency like “don’t invoke Email::send() until all the headers and body have been added”—leads to fragility.  There’s no hint from the IDE about the dependencies or ordering.

Worse, if a class has a lot of methods that must be called after setting some crucial data, every one of those methods has to check that the necessary data is set.  It’s better to pass that into the constructor, and have the created instance always contain that data.

Given a class that wants to maintain a valid state for all its methods at any given time, how do setters fit into that class?  It needs to provide not individual setters, but higher-level methods that change “all” relevant state at once.

So, I’m starting to see get and set methods as an anti-pattern.

I’ve run into this before.  Say I want an object with a position and size, that keeps its bounding rectangle within the bounds of the canvas, in that it doesn’t let itself go fully offscreen.  If the object allows updating x and width separately, it enables invalid state.  Some caller could narrow the width before moving it closer to being on-screen, but due to a bug, not perform the second update.

Maybe detecting (or trying to draw) this only wastes time in the draw call, but by then, all draw can really do is either skip drawing or crash, and the stack won't point to the problem any longer.

But validating the object when each individual property is updated means that the state between “old x” and “old width” and their new values must also be a valid state.  Either the caller gets stuck worrying about it, or making extra assignments, or the API needs a “mass update” method… and at that point, why keep individual set methods?

The main exception I make to the “no setters” structure is for builder-like objects.  Once an email message has all its metadata, body, and attachments, it really needs a method to create the text on the wire, and there’s no way around having that method depend on the ones that came before.

Another way to look at that is, a builder object is a collection of independent setters and a single, final stage where validation happens: during building.  From the perspective of code outside, it is immaterial whether the builder or the built thing does the validation.  (And potentially, not even observable.)

I’ve found something else interesting by banishing set methods.  The approach also leads to a lack of get methods, because none of the callers are interested in “what are these private variables you’re hiding from me?” any more.  They’re interested in the operations they can perform on the object. I suspect that’s a better design, overall.

No comments: