Incremental Code Rewrite

Developers are never satisfied with something they’ve written. There’s a perpetual urge to throw it out and rewrite software using whatever new and shiny technology has caught our fancy. I have been at this business long enough to recognize this as a trap. Fortunately, I have a tool that will let me do partial rewrites. This lets me take the advantages of shiny new technology and apply them to strategic areas.

In my case, there was a flaw at the heart of Genesys Adversaries. The components that define a unique world in the Genesys RPG are stored in linked lists. Each type of component lives in its own list. This gave me the advantage of having to write one linked list implementation in C, and use it over and over for each data type. The downside is that things live in discrete lists. There was nothing to prevent different types of items from having duplicate names.

This is a very easy problem to solve. By replacing the multiple discrete lists with a dictionary type structure that only allows one entry with the same key. The C++ map abstraction fits the bill perfectly. The original program is written in C, and C can call C++ functions that conform to the C calling and naming conventions.

Strategy

The first step is to create the C++ code that implements the data structure I need. I implemented this project with strict test-first TDD, and I continue that with the C++ code. This is pretty straightforward, especially because most of the functionality I’m looking for is provided by the well tested and understood code of the standard template library. I only need to test that the methods for populating specific data types do the right thing.

The second step is to create C compatible interface functions. I already created interface functions for adding and finding the game components. I needed to move their implementation into C++. The functions must have the appropriate extern "C" {} wrapper to keep their interface C compatible. Just for grins, I tried running the existing tests against these newly implemented functions. The tests made it clear that I needed a set of tests specific to the C++ implementation.

The next set of challenges was tracking all the places that accessed data structures directly. They manifest themselves as crashes when I tried to run tests or programs. Examination of stack traces did a good job of locating these failures in the code.

Lessons Learned

First, this proved the value of test driven development. Because I had a comprehensive suite of tests that verified the software did what I wanted it to do, I could move on with high confidence. Where the tests failed made shortcomings in the implementation obvious. The tests allowed me to correct them quickly.

The second lesson has to do with access to data structures. It’s convenient to access underlying data structures. That creates brittleness in the program. Every place where a test or program implementation directly accessed data structures, I created an interface that hid the data structure. The interfaces provided the same access, but hid the implementation. With these new interfaces in place, changing the implementation to use the new C++ data core became trivial.

Conclusions

Now that I have validated this technique, I’ll use it to continue improving the program’s capabilities. The program uses a hand-rolled lexer, as well as the lemon parser. Neither are unsatisfactory, but every time I add new tokens or data objects, there is more effort than I like. Flex and Bison have solved these problems much more elegantly, and I would like to use them. Small extensions to the language would open many options to simplify record keeping for my Genesys RPG games.

I was able to do this because the C abi is generally accessible to any programming language that generates binaries. If I were dealing with a Java program I could use any language that supported the jvm, such as kotlin or clojure. Likewise in .NET, I could easily rewrite parts of my program in F#. Knowing how to cross the language boundaries in your runtime environment gives you a nift new tool you can use to avoid full rewrites, but still getting shiny new language features.

Comments

comments powered by Disqus