"Developers who are constantly thinking about the subject of their next blog post. Nearly every somewhat interesting line of code they write is extracted into a blog post."I know the definition was written tongue in cheek but it has some truth in it. The main goal of the series was to document the development process of this particular project. Sometimes writing an application this way has felt a bit constraining. You just cannot go on and code whatever you want. :) It has forced me to keep the design digestable (or bloggable rather!).
I will take a look at individual parts of the series next and try to recap the main things covered in them. After that I will provide a list of lessons learned and finally I will construct a vision for future directions of the series.
Part 1, Getting Started
On retrospect not much of the work done at the first survived till the current version of the application. The behavior (basic math) is still there but stated somewhat differently. That's understandable considering most of the post dealt with setting up the development environment.
Part 2, Variables I
In this part I made it possible to set individual variables and get their values. I also determined that the value of a non-set variable should be "null" by default.
The inner class solution (class TestAccessVariable) I used at the beginning of the post makes me cringe a bit. Fortunately that was just a temporary brain fart. :)
Part 3, Variables II
In this part I extended the variable system by providing means to see all stored variables at once ("variables" command) and to get rid of them ("clean" command). I also defined "help" command so the application is more reasonable to use.
Part 4, Plugin Architecture I
At this point it was quite clear adding new commands to the system polluted the interpreter tests needlessly. I made a decision to separate commands as "plugins" and started sketching out the system. I introduced mocking (see Mock) to make it possible to test the plugins in isolation.
Part 5, Plugin Architecture II
As I implemented only the command part of the system in the previous part of the series I focused on the interpreter side in this one. Yet again it was time to mock and roll. At the end of the part it was apparent that a new component, PluginLoader was needed.
Part 6, Plugin Architecture III
Yet again I mocked up the implementation. Curiously a need for an abstraction of file system appeared as a result.
Part 7, File System
At this point the mocking approach used began to show its strength. It gave us nice specifications based on which to work on the file system abstraction.
Mocking the with statement gave me some headache and I'm still not entirely happy about the solution I managed to come up with but it will work for now. On retrospect it might have made more sense to handle patching "open" in a specific class (MockWith) that wraps the nasty part instead of the current solution.
The main relevation of this part to me was the introduction to patching. It proved to be surprisingly useful technique.
The part also showed how it can be quite painless to integrate some older code to provide needed functionality. In this case it served to provide some graph functionality to the file system considering a file system may be regarded as a tree.
Part 8, Application I
As I had no proper idea how to TDD the Application class in this case, I just whipped up one based on intuition. It didn't work initially and it took quite a bit of effort to fix all the remaining issues.
Even though mocking helped somewhat there were quite a few rough edges to sort out. I learned that mocks are more or less perfect in practice. The main problem is that they are somewhat dependent on your knowledge and it's really easy to neglect some tiny detail that might cause issues.
As a result of the post we had a working, or rather running, application.
Part 9, Application II
The great innovation of this part was the introduction of system tests. The idea is that system tests exercise the application as if an actual user was using it. This way we can describe the expected behavior from the perspective of the user exactly. System tests exercise all components of the system in unison and provide means to evaluate how they perform together.
I also separated the part dealing with Python interpretation to a command of its own in this part. This lead to the introduction of a priority system for commands. Priority may be used to determine when a command should be checked. This way it's possible to defer the execution of Python command for instance.
There were quite a few issues to fix yet again but eventually I managed to sort them out and have a proper way of testing the application.
Part 10, Eliza
In this part I focused on implementing an Eliza command. This meant I had to instrument the scenario tester to support ellipsis (...) and come up with concept of "context" that may be used to pass total control over input to a command.
This post reverted to a bug hunt but finally I managed to come up with a decent result.
Part 11, Quit
Even though "quit" was a really simple command to implement, testing it was not. I had to extend the scenario tester with "meta" functionality that allowed me to check the state of the program (is it running or not) and to restart it.
Part 12, Bounce
In this part I took my first steps towards providing simple visualization tools in Placidity. The main contribution of the part was the introduction of threaded main loop to the application. This was done to allow a VPython animation run while it's still possible to provide user input.
Lessons Learned
I admit the series has not followed Test Driven Development methodology faithfully on all occasions. That's not the point of the series. My main goal has been to show software development as a process. The current application could have been created in many different ways of which the one described is just one. Given some other person or a set of methodologies used it might have turned out to be totally different architecturally.
One of the main points to notice is the way the decisions you make affect the software later on. In my experience usage of tests helps you to cement these and make sure you won't accidentally break them later on. Tests work as a scaffolding. You can build without but it's sure going to be a different kind of experience.
Tests are not without their problems, however. Instead of having one possible place that can break (the code), you have two. There is no guarantee that your tests are correct. Hence when developing with tests, remember to check them out. Perhaps your code is correct but the test is not. :) Another to keep in mind is the fact that even if you test something, it doesn't mean everything works correctly. You may have very well missed some case that doesn't behave the right way, yet.
Even though tests are not without their faults, I do believe using them may be somewhat beneficial. This is especially true if you wish to grow software. As the development process of Placidity has shown, the whole is sum of its parts developed bit by bit. Of course the opposing view is that you should design first and implement only after you have proper understanding of what you are going to do.
This might work in some cases provided you have the required knowledge and happen to be a total guru on what you are doing. More often than not this is not the case, however. In this case you should thrive to develop software in a more organic manner.
One interesting side effect of this development method is that the design of the application is somewhat modular. Clear, separate modules have appeared. Partly this is due to the demands of testability, partly due to conscious effort to shape the codebase. As the abstraction level of the modules approach general it may very well to break them from the project and treat them as entities of their own.
Due to modularity it also becomes possible to replace certain components with external ones. This means some sort of adaptation layer or a facade may have to be built but it might very well be worth it as you get to offset the burden of maintenance to a project dedicated on the specific problem. You just have to make sure the layer between the other project and yours works properly.
Future Plans
There are a several components in Placidity that might very well earn to be projects on their own. Especially the file system, node abstractions and scenario tester are such. The plugin system is one as well, with some reservations, though.
Considering the file system its implementation has to be changed to be "lazy". It should not do any actual work till it's needed. Furthermore it should support various operations, such as renaming, moving, copying and deleting files. Also the distinction between files and directories should be clarified if possible.
The current node abstractions are adequate for now though it might be desirable to add some extra functionality to TreeNode later. I happen to have other projects that would benefit from treating the nodes as a package of their own.
The scenario tester is definitely something that might be useful beyond this project. It would be somewhat nice to be able to write tests for graphical user interfaces this way. I am working on a user interface library that might work as a good guinea pig for something along this. Furthermore I would prefer to extend it to contain proper test runner for both doctests and regular ones. This way I could replace py.test with it in my personal use.
I'm still a bit unsure about what to do with the plugin system. In principle it's very simple but it's somewhat tied to this project. I probably need to work on it further till it's on a nicer level of abstraction before considering separating it. For instance "execute" parameters have been split between the interpreter and plugin loader now. There probably has to be some other way (abstraction of parameters?) to handle that issue.
The user interface of Placidity could use some work too. It would be nice to offer numbered and colored line prefixes along ipython. Proper tab completion would be a big boon as well.
There is also some clean up to do in the way commands handle context. Current system expects that a command handles claiming and releasing it. Instead it would be beneficial to provide some abstraction that allows the developer to forget about context and just state that a command should be treated as a "program".
I have also been thinking about a graphical user interface in which it would be possible to edit the history and see how the changes made there reflect to the outputs of the future. Perhaps it should be possible to branch the output even and start anew while retaining the old branch(es).
Summary
As usual the development work of Placidity has been more like an avalanche. You begin with something small and end up something that just keeps on gaining momentum. The ideas just keep on coming.
I am going to get rid of the long prefix of the series and refer to the project in question at the prefix instead. I will edit the old posts to reflect this change too.
I will focus on separating the node related functionality to a package of its own in the next part of the series.