For many years, I've struggled to effectively quantify this. It has just seemed "obvious" to me very often where shit goes.
I think talking with a remote team over the past couple of weeks has helped my try to think through this a little more.
Give what's to be received, take only what is given
Here's a part I was thinking of today. It's the concept of things that are provided or given, and things that are required or received. You don't get things from an object that requires them, and you don't give things to an object that provides them.
Here's an example:
MyBusinessObject o = new MyBusinessObject(); o.setThingyDao(localThingyDao); MyOutputFormatter f = new MyOutputFormatter(): f.setThingyDao(o.getThingyDao());When I see this, I want to reach through my screen and slap someone. I liken it to using an apple to make an Apple pie, then trying to use the apple from the the pie in a glass of cider! You can tell me that this analogy is wrong because it's an object, a thing in memory that multiple places can use, like an apple that exists in parallel universes where it is a pie in one and cider in another. I believe this counter-argument is mistaken. When your apple exists in two different contexts, the results can be unpredictable, even catastrophic when one context changes the apple, or the apple's previous existence is now removed.
If we remove MyBusinessobject from the code above, the assignment to MyOutputFormatter will then fail. My IDE will catch this, as will compilation, but it's a correction that should never have needed to be corrected, and you spend time correcting that syntax error that didn't need to be spent.
The reverse situation is even clearer. You don't give things to a provider. It's like taking an orange out of a bag and putting it on an orange tree.
FruitProvider orangeTree = new FruitProvider(); Fruitrovider appleTree = new FruitProvider(); FruitConsumer bakery = new Bakery(); FruitConsumer caterer = new Caterer(); FruitConsumer footballer = new Footballer(); bakery.setFruit(orangeTree.getFruit());
Then to the astonished onlookers:
It may seem laughable when written like this, but I see code like this from time to time. It's also an example of why strong typing can save your ass (more on that later I think). If you were writing 'good' code, this would never compile. A "Tree" should simply not have a setter for its fruit. How many of us fire and forget in the IDE though? Just hit "generate code" and not think twice about it? How often does a framework force us to create setters we don't want to? Just like this fruit tree, if we are retrieving an object from a database, the ORM may use a setter to build our object from the database. The setter ends up being public because of it, and now any fool can put data in our object where it shouldn't be permitted.
There are some solutions to this that I've seen, though few good ones. I've seen the setter marked as @Deprecated so that at least the IDE and Compiler give you a big nasty warning if you try and use it, though it's not forcibly prohibited. It does seem like it should be a pretty fundamental feature to have. And in some ways, it is. This is what constructors are often used for. But, in the case of an ORM, a very common need is to initialize data lazily. We can't half create an object all that easily, so, the object gets created, then properties get set later. Almost like an orange tree. We can't have oranges until the tree is grown and the season has come. The problem is that the ORM is acting kind of like god in this scenario, who ends up being personally responsible for putting oranges on the tree. In fact, I think I like this analogy, I'm going to call this kind of code God-Code.
The problem with God-Code, is that it means the worshipers end up rather lacking in willpower and loose the sense to control their own destiny, so any old god can come along and mess with them.
Don't do for yourself what somebody else can do much better
or: don't Let the Plumber fix your Car
This is another instance of the "Put Shit where it Goes" rule. Let's say you want to get a car, make it a blue car, with tinted windows and a really great sound system. The dealership has the car for sale, but it doesn't come off the lot with tinted windows and an upgraded sound system. You don't buy the car from the dealer, then take it to your local plumber to get the windows tinted, or have the librarian put a premium sound system in. You have it done either by the deanship, or by a general mechanic, or by a specialist in that kind of installation.
The same principle applies to helper methods. If you ever intend to put tinted windows in more than one car, don't have your plumber tint the windows. If you have a Service Bean that sends XML, but gets given an object, you don't build XML Serialization into your Service Bean. You're gonna want to serialize to XML more than this one service call, so you make another bean that is responsible for serializing XML, or better yet, use someone else's bean/library.
This is a good example of a separate concern. XML Serialization is largely a concern that is independent from serving up data over HTTP. When you want to test your HTTP server, you don't want to end up having to test XML Serialization as part of that test. Vice-versa, you don't want to test HTTP functionality from your XML Serializer. You might ask, why not? It's simple math. If you have an object t that does two things, f and g which may be combined, potentially tightly coupled, you have to write test for the following for a given test fixture x:
t.f(x) t.g(x) t.f(g(x)) t.g(f(x)) !t.f(x) !t.g(x) !t.f(g(x)) !t.g(f(x))
Don't forget to write those test cases that demonstrate the functionality fails when it's supposed to!
If we have separated concerns that are decoupled in two objects a and b, we end up writing only:
a.f(x) !a.f(x) b.g(x) !b.g(x)We've now tested our system with the same efficacy with four tests instead of eight. Now work out the situation when you have an object that does three things, or four things. It's exponentially more work to test as you get more tightly coupled concerns within an object.
In additional thinking, you can sometimes fix the car yourself. Save all that money on labour, spent it on something fun right? If you're not a mechanism, think again. The mechanic has the right tools to do the job. He has contacts for getting the materials, and this is his job, he's good at it and can get it done more quickly than you (if he's any good). You might be able to buy an off-label turbo cheaper, which is great, until you break off some fitting when you try to install it and have to buy another. Or until you realize you need some special set of wrenches that costs twice as much as the turbo itself. Then you end up spending a month getting it installed instead of getting a mechanic who could have done it in an afternoon. You end up spending four times as much doing it yourself than it would have cost to buy brand-name and have the mechanic install it.
Don't reinvent the wheel is the usual metaphor. If somebody else has built an XML serialization library, check it out first. Be sure it can't meet your needs before you dive into writing your own.
Save yourself a permanent headache, and put shit where it goes!
Of course, if your goal is purely fun, or you need XML serialized in a special magical kind of way, you roll your own. If you're nice, you make it free software so we can all have a magical XML serializer. If everyone writes their own XML serializers, the world only ends up with a whole bunch of esoteric serializers that aren't any good to anyone but the people who made them, and they are probably going to end up wishing they'd spent their time building code that did something truly unique and useful.
I'm gonna cover tangling and scattering at some point I guess too.