Brian Slesinsky's Weblog
 
   

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.