A lot of time is spent on developing and using good refactoring tools, so that if we do decide to change something, we can, with minimal effort. However, refactoring doesn't generally work between codebases, encouraging large, monolithic codebases. The old wisdom about always programming to interfaces, in case you want to change which implementation to use later, seems a bit flawed in the presence of refactoring IDEs. When I'm teaching, to keep things simple I let students use ArrayList references, rather than List ones. Of course, that breaks down with subList, because subList doesn't return an ArrayList. They'll never ever want to change their ArrayList to be a LinkedList, or a Vector, and even if they did, it would be a simple change with a refactoring IDE. Ok, so the difference is that my students aren't generally writing APIs.. Well, Josh Bloch suggests that writing internal code as if you're writing APIs is a good idea. After playing with a dynamic language again, I can see the benefits of that straight away, if you don't have a refactoring IDE. I can also see that the refactoring IDE encourages you to use one codebase. My main project, a network simulator, relies on a set of functional programming utilities (functionalpeas) that I keep as a separate project. If I want to change the API of the FP utilities, I open the FP project, edit, build a jar, then go back to the simulator, copy the jar across into lib/, rebuild and fix the errors. This experience makes me less likely to split my work into several separate modules. I can, at any point in the networking code, get access to any part of the GUI. Obviously I don't do this very often, maybe just during debugging, but it would be nice to be able to actually prevent myself from doing that by accident. I see two solutions to this: either I can get it right first time, every time, or IDEs can start to support refactoring between projects, possibly even pushing the refactor out to remote developers. Imagine this: "A library that this project uses has been updated. Would you like to review the refactoring script that comes with the update?".
CodeSOD: Magical Bytes
1 day ago
6 comments:
This is a fantastic idea. The Netbeans Jackpot tool sort of does this already - it lets you codify refactorings as scripts.
They've even built-in some standard refactorings like you've suggested - converting StringBuffer to StringBuilder for example.
If someone was interested in making Jackpot callable from say, an ant script, we would have a very compelling tool on our hands.
I agree that cross-project refactoring could be a useful tool, but there's another solution.
In addition to either getting it right the first time and waiting for IDEs to support cross-project refactoring, you can decouple your modules with adapters, insulating one module from changes to a dependency's API. Instead of changing every call to the dependency, you only have to change it in your adapter.
This definitely feels like overkill, especially if you own all of the code and the APIs are fluid, but it's a useful technique in a lot of cases.
This doesn't actually reduce coupling any further. It just adds indirection and complexity, for no real gain.
If your APIs need to change, then there are reasons, and you'll end up propagating the API change through the intermediate layer too.
Actually, the purpose of the indirection is just that - to reduce coupling.
In the trivial example, a dependency's API states that one of its method takes or returns an ArrayList, but your adapter could use List. Then the consumer (which actually talks to the adapter, instead of directly to the dependency) would be insulated from the dependency switching to a LinkedList or Vector.
Obviously it's more complex, but if loose coupling is what you're after, patterns like adapter (and facade) can be helpful.
The purpose of the indirection may be to reduce coupling, but it doesn't actually do so.
You're still coupling to List, the difference is that you're no longer coupled to the behaviour of ArrayList. You still are, but the coupling is somewhere else, in your DI framework, in your setup code, etc. So you've actually added coupling - you're now coupled to both List and ArrayList, but the ArrayList coupling is in one place only.
Now you can swap out the implementation of List whenever you want, but you could, in a more flexible language, change what ArrayList means instead.
I'd imagine you could do that in Java too, via classloaders, but I doubt it's worth the effort.
I'd rather have loose coupling as part of the language, rather than having to write indirect code to achieve it.
I don't bother with indirection until there's a need for it, i.e., when I actually use more than one implementation of an interface. This makes my code easier to follow.
This is why you don't see a comprehensive Ruby refactoring tool yet - because there isn't really a need for one for a lot of common refactorings.
However, not everyone, in fact very few, has the luxury (assuming it is one) of writing or using code written in a language like Ruby. That's where programming-to-interfaces and design patterns become useful tools for decoupling components. What's the point of decoupling? To minimize the impact of a change in one component on other components. Sometimes it makes sense to have a little indirection. It's a legitimate option.
Post a Comment