Saturday, 16 Jul 2005
Method inlining as a macro system
Programmers have been using macros since the early days of computing
to make writing code more convenient. Sometimes the macros are part
of the language (such as the C preprocessor, C++ templates, or Lisp
macros), sometimes they're part of the editor (such as IntelliJ's live
templates), or code might be generated by a wizard.
Macros that are built into the language can be very powerful and
concise, but they're often difficult to write, understand, and debug,
because you're working at a higher level of abstraction. Code that's
generated by the IDE is very transparent and customizable (you see the
generated code immediately and can edit all of it), but the
duplication leads to maintenance problems.
I've found that IDE's that support "inline method" can be used in a
way that combines some of the advantages of both types of macros.
Here's a simple example from a class that generates HTML. (There many
ways to generate HTML and this is probably not your favorite, but bear
with me.)
I might start out by writing a paragraph of text:
out.textP("This is a short paragraph about tribbles.");
The textP method is very simple. All it does is surround some text
with <p> tags:
public void textP(String text) {
startTag("p");
text(text);
endTag("p");
}
That's only a three line method, but it happens often enough that it
was worth writing. The textP() method is in no way essential to the
HTMLWriter class; it's a convenience method that I deliberately made
suitable for inlining. (It can be inlined because it only calls
public methods.)
Suppose that I now want to make the word "short" bold. If this were a
print statement, I could just embed "<b>" and "</b>" into
the string literal. However, this won't work in HtmlWriter because
the text() method automatically escapes angle brackets. (We
consider this a feature because you can't forget to escape
anything, which avoids a common bug and security hole.)
So instead, I expand textP using "inline method", which results in:
out.startTag("p");
out.text("This is a short paragraph about tribbles.");
out.endTag("p");
Next, I split the text string into three:
out.startTag("p");
out.text("This is a ", "short", " paragraph about tribbles.");
out.endTag("p");
The three-argument version of text is another method that was written
for inlining, so it doesn't use a loop as you might expect. After
inlining it I get:
out.startTag("p");
out.text("This is a ");
out.text("short");
out.text(" paragraph about tribbles.");
out.endTag("p");
Next, I change the text to textB:
out.startTag("p");
out.text("This is a ");
out.textB("short");
out.text(" paragraph about tribbles.");
out.endTag("p");
By using "inline method" three times and a small amount of editing,
I've generated a fair bit of code in a few keystrokes.
We could take this further. If you had to generate Java code for lots
of similar but annoyingly-different page templates, you might start
out writing an out.myTemplateDocument() method that in turn
calls other inlineable methods, and drill down until you get to the
things you need to change. Unchanged boilerplate sections remain
abbreviated (reducing code size and duplication) while still allowing
you to change anything you want.
Would this work? I've had good luck so far, on a small scale, but I
suspect that clutter will become a problem. There will be lots of
trivial methods, enough that they threaten to overwhelm the class's
core functionality.
Another issue is that there's obviously a lot of potential for
duplicate code. IDE's don't support "find duplicates" as well as they
support "inline method", so you need to be on the lookout for things
that should be abbreviated.
Using methods as typing abbreviations changes your thinking somewhat.
In an Extreme Programming project, you normally delete dead code on
sight. However, if you're using methods this way, it starts being
reasonable to have methods that should never be called in checked-in
code, just because they're handy while editing. You wouldn't want to
call out.myTemplateDocument() in production when it's there
only as a typing abbreviation. Also, there's the question of how much
these extra convenience methods should be unit tested.
For external libraries there are further issues. You need to have the
source code available to support inlining, which isn't a problem for
open source projects, but you still have to be careful about
licensing. Furthermore, someone who's unfamiliar with a library may
be unclear on which methods are intended for inlining and which ones
aren't. Also, too many methods may be overwhelming unless there are
clear naming conventions.
Perhaps tool support would help. In an ideal world, the inlining
policy would be part of the method's meta-data. Some possible
policies:
- No source code - obviously can't be inlined.
- Look but don't copy - you can see the source code and step through
it, but you shouldn't inline it, or maybe it can't be inlined because
it accesses a private method or variable.
- Optional inlining - designed for inlining, but you can also rely on
the unexpanded method.
- Inline-only. The method appears in the IDE, but only the source
is provided and you can't call it without expanding it. This might be
good for providing sample code and convenient abbreviations without
any backward-compatibility worries; if an inline-only method changes
or goes away in a new release, no code breaks, but some programmers
might have to change their habits.
In the meantime, it seems like macro methods are best shared within a
code base or by copying in source-only form, not as part of an
external API. In Java, maybe a jar file provides the API and
convenience methods come as java files that you copy into your
project. Many projects already provide sample code, so this would be
an expansion on that idea that takes advantage of modern IDE's.
respond | link |
/code
|