A few years ago, we did some embedded control testing that got me seriously thinking about language. The system was specked out using signal flow diagrams, the kind I remember from digital signal processing in college. The code was white-boxed C poking and prodding at a lot of global variables. Our test tool let us define values in terms of other values, invoke the C code, then assert some values with our expected. Think of this as like using excel to model signal flow.
Here’s an example of a signal flow diagram from DSPRelated.com
The signal flow diagrams used a lot of sub components, some that were deeply nested. Our test tool doesn’t have any abstraction; no functions, no macros, and no components. This lead to repetition in our test code. If I had three calls to a sub block, I would copy/paste/rename upwards of 60 lines of code for each block. Code reviews were important and kind of intense. We couldn’t stand the tool, but the tooling surrounding it was worth the hassle. We had code coverage, logging reports, and most importantly we had decision coverage.
But it still felt like I was programming in the wrong language. At that time in my life I tended to think, “I should be writing in Ruby.” But even that’s only part of the story.
What I wanted to do was model the diagrams more directly. I wanted to define a component, wire five of them together, define my input ranges, and make some assertions. I wanted my test code to feel like you were looking at a diagram. This would make the code easier to maintain, reduce duplication, and make it easier to review.
I sat down with Ruby’s mutable syntax and tried to whip up a model of a few representative diagrams. I couldn’t make it perfect, but I could hit the 80% mark and leave enough “bare metal” to work around for the last 20%. All it did was spit out the code I would have written by hand, but it was much easier to write in my little language. The best part is all our existing tools worked just the same.
test 10, "4 step floating average" do inputs :in => [0.0, 10.0, 13.0] inouts :delay_1 => [0.0, 10.0, 13.0], :delay_2 => [0.0, 10.0, 13.0], :delay_3 => [0.0, 10.0, 13.0] delay_1 = instance :delay, :delay_1_expected, :input => :in delay_2 = instance :delay, :delay_2_expected, :input => delay_1.output delay_3 = instance :delay, :delay_3_expected, :input => delay_2.output double :expected, "(in + delay_1'IN + delay_2'IN + delay_3'IN) / 4" assert :delay_1 , delay_1.output assert :delay_2 , delay_2.output assert :delay_3 , delay_3.output assert :answer, :expected end
I am happy with the solution. Not because I get to write in Ruby, but because I can write the code that fits the solution. That’s when I started to really understand the power of language to clearly express the problem and solution at hand.
I’m presenting to IndyAlt.NET on Domain Specific Languages this week. This isn’t just programming language wankery, though I’ll admit that part is fun. It is about using the right tool for the job. I’m currently writing an application in C#. But the majority of my code should only superficially be called C#. I should bend C# to express the domain and let me program the solution, not the implementation.