Meta Refactoring

Say you have a function something like this:

  char andGate(char a, char b) throws InvalidInput
  {
    if (a=='0' && b=='0') return '0';
    if (a=='0' && b=='1') return '0';
    if (a=='1' && b=='0') return '0';
    if (a=='1' && b=='1') return '1';
    throw new InvalidInput();
  }

You might also have some tests:

  void testAndGate()
  {
    assertEqual(andGate('0', '0'), '0');
    assertEqual(andGate('0', '1'), '0');
    assertEqual(andGate('1', '0'), '0');
    assertEqual(andGate('1', '1'), '1');
  }

When you do an OR gate, then an XOR, then...; then you start to notice that they all look rather similar. OnceAndOnlyOnce should suggest then common structure should be localized!

Its difficult to see how to refactor this in most languages. Sure, you could go to a data driven approach, but then you might lose performance.

An approach I find useful is to refactor into a meta-domain. The 'and' gate can be can be re-written as

  AND
  a b f
  0 0 0
  0 1 0
  1 0 0
  1 1 1

(or something similar). I have found it nice to put the table in a Word/Framemaker document, and extract it via HTML -- though more recently I've turned to XML. I can thus apply OnceAndOnlyOnce to the documentation as well as the code. Also, the documentation then becomes part of the source code. (I do tend to have a rather extreme approach to translation based programming).

It is then very easy to write simple Perl scripts (or whatever) to generate both the tests and the implementation. (this is not as silly as it sounds, because the translation scripts may have bugs which are detected by the tests). Note that, if the Perl script is non-trivial, it will need its own UnitTests. For any change in that script, use the previous version of its output as a GoldenLog? for regression testing.

Once you have a simple minded implementation in a script, then you can use the scripts on multiple datasets. When you find a better implementation, then you can make the change in just one place (you don't change the tests).

If you ever decide that you want to stop using translation then the refactoring to remove the scripts is a simple matter of telling your build system (and possibly the version control system) then the derived files are now source files (again).

In general the transition into any GenerativeProgramming technique will lead to more cohesive source code. And sometimes you need to go the other direction.

-- DaveWhipp


It is then very easy to write simple Perl scripts (or whatever) to generate both the tests and the implementation. (this is not as silly as it sounds, because the translation scripts may have bugs which are detected by the tests).

But what if the source data used to generate both the tests and the implementation has errors? Potentially, your test will be bogified in exactly the same way as the implementation. The bogus test could erroneously pass when run against the bogus code. E.g. I might accidentally change a value in the result column of the AND data table mentioned above. Tests should be implemented separately and independently of the code-under-test. -- yes, but that would be an AcceptanceTest, which should be invariant over any refactorings of the implementation

Note that, if the Perl script is non-trivial, it will need its own UnitTests. For any change in that script, use the previous version of its output as a GoldenLog? for regression testing.

It follows that if the source data being translated by the Perl script is non-trivial, and used to generate both test cases and production code, then the source data must have a unit test of its own as well...

-- WylieGarvin


CategoryRefactoring, TableOrientedProgramming


EditText of this page (last edited December 13, 2004) or FindPage with title or text search