Refactoring A Mountain: Late-Binding for Paint.NET v4.0

Like I stated earlier, I’ve finally started work on Paint.NET v4.0 (yay!). To that end, I’ll be focusing most of my development time throughout 2008 on this effort. There will still be Paint.NET releases in the meantime, as there is plenty of life left in the 3.xx architecture. There are a lot of small features that Paint.NET lacks and needs to catch up on that are feasible even without the changes I am slowly architecting into the 4.0 branch. I’m also hoping and planning to integrate some of the more popular or useful community plugins (with permission of course!).

A major change in the Paint.NET architecture for version 4.0 is the introduction of a pervasive late-binding and inversion-of-control mechanism. At a glance it looks like a localized version of COM: “give me an object that implements this interface, btw I don’t care what concrete class actually implements it” (actually, in COM you quite often do specify which implementation you want, but bear with me).

It isn’t anything revolutionary, but refactoring it into a code base that weighs in at 150,000 lines of code is a lot of work. It’s being very educational to me to go through and see all the poor code choices that have been made over the last 4 years. If only I had been psychic back then, I would have known the right way to do things! :)

PdnResources is a static class that contains all the logic for retrieving the string resource from the appropriate language file. However, having this as a static class is a very very bad thing, and this pattern is repeated for many other classes that need to be accessible throughout the application. Any piece of code can load resources, even if it shouldn’t be able to. For example, there is code in the Layer and Document classes to load the default name for a layer, and this is not appropriate for code that is in the data layer of the application. This text should be supplied by whoever is creating the objects, or hooked in at the UI layer somehow instead.

To that end, this code will be moved out of a static class and into a public interface and a concrete, private implementation. A bindings provider (or scope) will serve as the mechanism for accessing it.

At first glance, this may not appear to buy much, and is more code that is less expressive. But it allows me several things, including the freedom to move the resource loader, or to hide it from pieces of code (such as plugins, or from the data layer). I can do more granular versioning so that if plugins do need access I can at least continue to change it with more freedom (just introduce a “IResourcesServiceV2″ or whatever). I can implement things like pseudo-localization by way of chaining together two implementations of IResourcesService. Logging and fault injection can be made easier, as they can also be implemented as a chained implementation. By “chained implementation” I mean that the normal implementation stays as-is, but a second implementation is registered so that it passes-through method calls while also performing some other service, such as logging or transformation. The primary implementation can then be kept clean and simple.

The “bindings” object would never be available globally or statically. Any object that needs to use it must take it via its constructor. This is proving to be a lot of work to enforce, but I believe it will be worth it. It makes it easier to analyze things like layering and dependency flow if code can’t just magically go and grab resources from static locations, and is instead forced to go through an object that is supplied by its creator/owner. For example, if I don’t want the data layer to be able to load resources, then I simply remove the IResourcesService from its bindings object. The only way to access the concrete class at that point would be via reflection, which is easy enough to detect and combat against.

I plan on extending this pattern to all of the static classes in the SystemLayer assembly as well. Instead of just bluntly calling into the PaintDotNet.SystemLayer.FileSystem static class, for instance, you would ask for the IFileSystemService via your local bindings provider object. The SystemLayer DLL would then have a static class with one function that would be responsible for registering all of the bindings.

This may even make things easier for Miguel de Icaza and his Mono port, Paint-Mono, although it’s too early to tell for certain. Done properly, there could be a PaintDotNet.SystemLayer.NetFxWin32.dll and a PaintDotNet.SystemLayer.MonoLinux.dll, and a simple configuration or command-line switch of some sort would choose which one to use at startup. I’ve paid attention to his blog posts and other documentation and it has brought to light some areas that still need to be separated into the SystemLayer DLL.

And while this could be a boon for Miguel, it’s also good for me and isn’t something I’m necessarily doing to directly help out his porting efforts. What if the differences between the XP and Vista code paths grew so great that I needed two different versions of the SystemLayer DLL? Or, what about a Mac OS version?* Moving to a late binding system and avoiding static classes will make things much, much cleaner. Who knows, maybe I can stuff Windows Forms itself behind an abstraction layer and decouple Paint.NET from it. That’s actually one of the things that the new IndirectUI system in v3.20 has allowed me to practice with: I could port the thing over to WPF or even to a console window, and the effects/plugin code wouldn’t know the slightest difference.

* This should not be taken as an announcement of a plan to release a Mac OS version.

Blogroll

13 comments

Hi, I am learning texturing for the Oblivion game I am pleased paint.NET supposrts .dds files but I have a suggestion for paint.NET for even greater ease of use especially for noobs like me.

I suggest if you could add a new feature that when one opens a .dds file if one could select a base colour and the image changes to the base while still retaining the original shades and picture except in the colour one selects. This would be of immense benefit to the Oblivion modding community and to other gaming communties.

Yeah I know it sounds lazy somewhat but for modders of games this would cut out months and hours and hours of work that goes into our mods and trying to balance much wanted mods with real life responsibilitys such as children and loved ones. Sometimes one doesn’t have much time in this busy world we live in.

Just a suggestion though, love it if it can be done. Thanks for a great program, I love using paint.NET!

Lex Y. Li — true. But I would not consider that to be a “native” Mac OS port. It would be using the lowest-common-denominator code paths that Mono uses to support both Mac OS and Linux. Plus, it would still be a “catch-up” port, and not an official Paint.NET supported platform. Paint-Mono, for instance, is still using the Paint.NET v3.0 codebase which is almost a year old. With a separate SystemLayer DLL for Mac OS, things could be kept always up-to-date and optimized for it.

Rather than an extension language like Emacs/Elisp, I would rather have an object model like the Office (e.g. Word) object models that would be available both from inside the application and from the outside (say, from a PowerShell script). This would be much more powerful.

Rick – Interesting to hear that you’re integrating IoC into Paint.NET. If you haven’t read Michael Feathers “Working Effectively With Legacy Code”, I would highly recommend it before diving too deeply into the refactoring. Michael provides a wealth of information on refactoring codebases with few to no unit tests (which is his definition of “legacy code” – and a good definition, I might add). To ease your refactorings, you may also want to consider the Static Gateway pattern:

Rick
Thanks for sharing. I’m an avid supporter of Paint.net, and think it’s an absolutely excellent piece of software, that just “works”!!
I’ve recently made the shift to OSX, and unfortunately, the one thing that makes me wish for Windows, (and when I say this, I can’t even believe I would ever say a sentence where I “asked” for Windows, ever!) is so that I could run Paint.net, simply because of how easy, simple and straightforward it was to use.

Been exploring mono options, and other alternatives, but everything else, so far, just falls short of the ease of use, and simplicity of Paint…

If you do ever release an OSX version, I can guarantee, I’ll be ur number one fan, and make a very generous donation, as soon as I can…
Thanks for taking the time to put this together ;)