An Abstraction Emerges
In fixing the test functions, you’ve introduced several new bits of duplication. Not only does each function have to include the name of the function twice—once as the name in the **DEFUN**
and once in the binding of *test-name*
--but the same three-line code pattern is duplicated between the two functions. You could remove the duplication simply on the grounds that duplication is bad. But if you look more closely at the root cause of the duplication, you can learn an important lesson about how to use macros.
The reason both these functions start the same way is because they’re both test functions. The duplication arises because, at the moment, test function is only half an abstraction. The abstraction exists in your mind, but in the code there’s no way to express “this is a test function” other than to write code that follows a particular pattern.
Unfortunately, partial abstractions are a crummy tool for building software. Because a half abstraction is expressed in code by a manifestation of the pattern, you’re guaranteed to have massive code duplication with all the normal bad consequences that implies for maintainability. More subtly, because the abstraction exists only in the minds of programmers, there’s no mechanism to make sure different programmers (or even the same programmer working at different times) actually understand the abstraction the same way. To make a complete abstraction, you need a way to express “this is a test function” and have all the code required by the pattern be generated for you. In other words, you need a macro.
Because the pattern you’re trying to capture is a **DEFUN**
plus some boilerplate code, you need to write a macro that will expand into a **DEFUN**
. You’ll then use this macro, instead of a plain **DEFUN**
to define test functions, so it makes sense to call it deftest
.
(defmacro deftest (name parameters &body body)
`(defun ,name ,parameters
(let ((*test-name* ',name))
,@body)))
With this macro you can rewrite test-+
as follows:
(deftest test-+ ()
(check
(= (+ 1 2) 3)
(= (+ 1 2 3) 6)
(= (+ -1 -3) -4)))