I have a very data-centric application, written in Python / PyQt. I'm planning to do some refactoring to really separate the UI from the core, mainly because there aren't any real tests in place yet, and that clearly has to change.
There is some separation already, and I think I've done quite a few things the right way, but it's far from perfect. Two examples, to show you what kind of things are bothering me:
When the user right-clicks on the representation of a data object, the context menu that pops up is created by the data object, although this data object (essentially the ORM representation of a database row) should clearly have nothing to do with the UI.
When something is written to the database, but the write fails (e.g. because the database record is locked by a different user), the classical "retry / abort" message box is presented to the user. This dialog is created by the data provider*, although the provider should obviously not have any UI functionality. Clearly, the provider can instead raise an exception or otherwise indicate failure, and the UI can catch that and act accordingly.
* that's the word I use for the object that basically represents a database table and mediates between its data objects and the database engine; I'm not sure whether that's what is usually called a "provider"
I don't have experience with testing, so I don't easily "feel" testability problems or the like, but before I get into that, some reorganizing has to be done.
There is no complicated business logic involved (it's mainly just CRUD, yes, even without the D), and this would be much more reorganizing than rewriting, so I'm not really concerned about introducing regression bugs like discussed in this question.
My plan is to start refactoring with the idea in mind that the UI part could easily be ripped out to be
replaced by, say, a web frontend or a text-based interface instead of the Qt interface. On the other hand,
Qt itself would still be used by the core, because the signal/slot mechanism is used in quite a few places,
e.g. data objects emit a changed
signal to indicate, well, you know what.
So, my question: Is that a feasible approach to increase testability and maintainability? Any other remarks, especially with Python in mind?
I have done refactoring for large legacy code aiming UI/backend separation before. It's fun and rewarding.
/praise ;)
Whatever pattern one calls it or be it part of MVC it's invaluable to have a very clear API layer. If possible you may route all the UI requests through a dispatcher that would offer you greater control over UI<->Logic communication eg. implementing caching, auth etc.
To visualize:
[QT Frontend]
[CLIs] <=======> [Dispatcher] <=> [API] <==> [Core/Model]
[SOAP/XMPRPC/Json]
[API Test Suite]
This way
API designing can happen well before you start coding for API layer. Depending on the application you might want to take help of packages like zinterfaces. This is general approach I take even while writing very small apps and it has never failed for me.
Do look at
One distinct advantage of this approach is after you have API layer and new UI, you can now go back to legacy code and fix/refactor it in perhaps smaller steps.
Other suggestions is to have your testing suite ready. See interstar's advice at What are the first tasks for implementing Unit Testing in Brownfield Applications? .