Monthly Archives: February 2018

DSLs: even more important for tests

Last week I wrote about the benefits of Domain Specific Languages (DSLs). Since then I’ve been thinking and realised that DSLs are even more important when writing tests. It just so happened that I was writing tests in Emacs Lisp for a package I wrote called cmake-ide, and given that Lisp has macros I was trying to leverage them for expressiveness.

Like most other programmers, I’ve been known from time to time to want to raze a codebase to the ground and rewrite it from scratch. The reason I don’t, of course, was aptly put by Joel Spolsky years ago. How could I ensure that nobody’s code would break? How can I know the functionality is the same?

The answer to that is usually “tests”, but if you rewrite from scratch, your old unit tests probably won’t even compile. I asked myself why not, why is it that the tests I wrote weren’t reusable. It dawned on me that the tests are coupled to the production code, which is never a good idea. Brittle tests are often worse than no tests at all (no, really). So how to make them malleable?

What one does is to take a page from Cucumber and write all tests using a DSL, avoiding at all costs specifying how anything is getting done and focussing on what. In Lisp-y syntax, avoid:

(write-to-file "foo.txt" "foobarbaz")
(open-file "foo.txt")
(run-program "theapp" "foo.txt" "out.txt")
(setq result parse-output "out.txt")
;; assertion here on result

Instead:

(with-run-on-file "theapp" "foo.txt" "foobarbaz" "out.txt" result
     ;; assertion here on result

 

Less code, easier to read, probably more reusable. There are certainly better examples; I suggest consulting Cucumber best practices on how to write tests.

Not every language will offer the same DSL liberties and so your mileage may vary. Fortunately for me, the two languages I’d been writing tests in were Emacs Lisp and D, and in both of those I can go wild.

Advertisement
Tagged

In defence of DSLs

I’ve often heard it said that DSLs make a codebase harder to understand, because programmers familiar with the language the codebase is written in now have to learn the DSL as well. Here’s my problem with that argument: every codebase is written in an embedded DSL. More often than not that DSL is ad-hoc, informally specified and bug-ridden, but a DSL all the same.

The syntax may be familiar to anyone who knows the general purpose language it’s written in, but the semantics are just as hard to grasp as any other DSL out there. Usually, harder to grasp, since there’s so much more code to read to understand what it is exactly that’s going on.

I can write C++. Does that mean I can download the source code for Firefox and jump straight in to fixing a bug? Of course not.

I really think that Lisp got it right, and that the next time I write any Emacs Lisp I really ought to think of what language I can express the problem domain better, then implement that language. It’s something that feels right but that somehow I’ve never actually really done.

It’s true that designing a DSL means designing a language, and that not all programmers are good language designers. But what’s the alternative? Use no abstractions? Let them write a giant mess for others to attempt to navigate?

In the end, isn’t the art of language design a way to state solutions to problems, simply? To capture the essence of what’s trying to be said/programmed elegantly?

I don’t know about you, but to me that just sounds like programming.

Tagged