Chapter 11. DSLs and documentation

In this chapter

  • Essential documentation for your DSL
  • Writing the user guide
  • Documenting the language syntax
  • Creating an implementation guide

Documentation is a task that most developers strongly dislike. It’s treated as a tedious, annoying chore, and it often falls to the developer who protests the least. Furthermore, developers trying to document their own work often don’t do a good job. There are too many things they take for granted in their own code, and they tend to write to developers, in a way that makes little sense to non-developers.

At least, that’s what I say when I’m asked to write documentation. It doesn’t usually get me out of the task, but the problems are real.

Solid documentation is an important part of quality software. If the developers of the system are needed to handle routine matters, something is wrong. DSLs are no different in this regard. In fact, documentation is extremely important for DSLs, because you don’t have a friendly UI that you can explore using trial and error.

You also need to keep in mind that DSLs are still rare in the industry (although they’re a rapidly growing trend). Handing another developer a real-world DSL implementation will require some effort if the DSL implementation isn’t documented.

11.1. Types of documentation

The anticipated users of your DSL will determine the level of the documentation and the assumptions you can make in that documentation. If the users are expected to be developers (or hobbyist developers), you can assume they’ll know some things that non-developers would not.

The documentation for a DSL can be divided into two main parts:

  • User documentation— The Getting Started Guide and User Guide, and perhaps some executable documentation
  • Developer documentation— The Developer Guide and executable documentation, discussing the actual language implementation

Note

There are whole books written about documentation approaches, and I’m not going to attempt to cover the whole subject. Instead, I’ll touch on several approaches that I have found useful when creating documentation for DSLs. This is the tip of the iceberg in terms of proper documentation, so take that into account.


In this chapter, we’ll assume that the DSL will be used by business users with minimal programming experience. This is one of the toughest audiences to write documentation for. Teaching someone who doesn’t know even the basics of programming to understand and use a DSL is a challenge. I haven’t seen any DSL that has managed to avoid this issue (nor have any of the graphical tools I’ve seen).

Let’s consider the Quote-Generation DSL. At first glance, it seems that no programming knowledge is required to write a script—it’s a simple listing of requirements. The problem is that when you’re writing such a script, you need to understand the execution environment and how the scripts will interact with each other.

There isn’t a lot of programming knowledge required. You need to understand how the if statement works and that your script might not be the only one to run (so it might not produce the final results), but that’s about all.

Let’s consider what information we’d need to give someone who doesn’t have a lot of programming knowledge in order for them to use our DSL:

  • The syntax of the DSL
  • The conventions used (if the folder structure has meaning, this should be spelled out)
  • A list of commands that can be issued
  • An overview of the execution semantics of the DSL—when and in what order the scripts are executed
  • Tooling support—what kind of tools are provided for use with the DSL (IDE, trace viewer, GUI, and so on)
  • Samples that shows how to do common things (for the express purpose of allowing copy, paste, and modify cycles)
  • An explanation of how to deploy scripts
  • An explanation of how to execute DSL scripts
  • An explanation of how to get the results of a DSL execution
  • An explanation of how to handle common errors and issues

This list isn’t final, but it’s still pretty big—each DSL is likely to have a slightly different list of things that need documenting. The list also uses some technical terms that users probably wouldn’t understand. This is the kind of information you need to give users, but not how you should present it.


Ensure a positive first impression

I can’t emphasize enough the importance of making a good first impression with your DSL. It can literally make or break your project. You need to make a real effort to ensure that the user’s first impression of your system will be positive.

This includes investing time in building a good-looking UI with snappy graphics. Such features might not have a lot of value from a technical perspective, and perhaps not even in day-to-day usage, but they’re crucially important from a social engineering perspective. A project that looks good is pleasant to use, easier to demo, and generally easier to get funding for.

From the documentation perspective, we want to give users some low-hanging fruit that they can grab easily—making the user feel that using the DSL is easy and that it can produce results quickly is important to gaining acceptance. The first stage should be an easy one, even if you have to specifically design to enable that.

Nevertheless, you should be wary of creating a demoware project—one that’s strictly focused on providing a good demo, but that doesn’t add value in real-world conditions. Such projects may look good, and get funding and support, but they tend to become tortureware rapidly, making tasks harder to do instead of easier.


11.2. Writing the Getting Started Guide

The purpose of the Getting Started Guide is to get the user past the Hello World stage (or its equivalent) as quickly as possible. It’s a highly focused document, meant to give someone who already understands the domain the basics of working with the DSL. It should be focused on giving clear instructions for achieving a specific set of tasks, for example, how to add a rule to calculate a new quote. In-depth discussion should be reserved for the User Guide.

The Getting Started Guide is the first thing that most users will see. Giving them a good first impression will create a lasting effect, and it can help tremendously in generating acceptance for the DSL.

The Getting Started Guide consists of an introduction to the DSL and its usage, and a set of examples that users can go through to familiarize themselves with the DSL at a high level.

11.2.1. Begin with an introduction

Ideally, the Getting Started Guide should begin with a short introduction explaining why you’re utilizing a DSL. This introduction should not go beyond two or three paragraphs. Here’s an example of how you could start the Quote-Generation DSL Getting Started Guide:

The Quote-Generation Language aims to simplify the way that you set up and manage the quote-generation rules. It was built to express both the business and technical constraints involved in creating a quote for a customer.

Using this approach, you will gain more control over how you generate the quote, and you’ll see why certain items were added to the quote (by what rule, and based on what logic).

The following screenshot shows a generated quote, with tracking from each item to the specific rule that added it ...

I like to use relatively informal language when writing the Getting Started Guide. I find that it forces me to speak in terms that are more readily understood. Note that there is no mention of a “DSL” or any other technical term in the introduction.

Interspacing your documentation with figures, screenshots, and other visual aids helps create a document that is more readable. It divides up the text, making it seems more approachable, and appropriate use of images can also significantly help readers. Appropriate images can clarify concepts better than any amount of text.

11.2.2. Provide examples

Following the introduction, it’s a good idea to take users through the process of building a couple of example scripts from start to finish. You can talk about concepts for as long as you like, but until you take users through the motions of performing a task, it won’t get through to many of them. Even those who are interested in the concepts and high-level overview will find it useful to see how things work.

I generally like to start the first example with something that can stand on its own. This doesn’t include discussing the language in isolation. It’s often better to show the language in use. If you can demonstrate how the DSL eliminates pain points in the current approach, do so. Relieving pain is great for acceptance.

The next step is to demonstrate how to create this script in the system, where to put it, how to get the application to accept it, and how to see that it’s working. This part is important, and it should be as detailed as you can make it. Ideally, this is also an idiot-proofed scenario; try to anticipate all the mistakes a user might make.

There will always be something you’ll miss, but do dedicate some time to addressing the obvious issues. For example, a simple common mistake is attempting to upload a Word document with the script in it. This is likely to fail, so outline that in the documentation and check for it in the code. I suggest accompanying each step in the walkthrough with a screenshot that shows what needs to be done. Text alone will not suffice.


Tip

Don’t be shy about using screenshots to demonstrate functionality. A user is unlikely to be interested in the syntactic purity of the language, but they’re most certainly interested in the tools and how they interact with it.


I tend to give three to five such examples, all at the same level of detail. They should be progressively more complex, although you should make sure that the first example is the most attractive one. You can skip the setup for each example if the setup is the same for all tasks; if it isn’t the same, you should point out the differences explicitly.

Make sure you end each example with an explanation of how users can check that it worked. It’s important to give users feedback that whatever they did produced the expected results.

You should also include examples that the user can copy, paste, tweak, and run. It’s likely that you’ll have several commonly repeated themes in the DSL, and providing an example of each of them that the user can immediately start experimenting with will be helpful.

That’s it for the Getting Started Guide. Because the main purpose of this guide is to be short and to the point, it skips over a lot of details, most of which are important to users. That level of documentation belongs in the User Guide, our next topic of discussion.

11.3. Writing the User Guide

The Getting Started Guide is focused on taking users through their first steps, but the User Guide has a far bigger role. It’s tasked with helping users understand how to use the DSL, the reasons behind the semantics of the syntax and behavior, and the full capabilities of the DSL. It also needs to cover the model and how to approach it, because you can’t assume that anyone who learns to use the DSL is also a domain expert. (In fact, the DSL often is the way to become a domain expert.)

There are four main things that need to be documented in the User Guide:

  • The domain and model
  • The language syntax
  • Other language aspects
  • User-level debugging

Let’s look at them each in turn.

11.3.1. Explain the domain and model

I usually start the User Guide with a discussion of the domain and the problems that the DSL is trying to solve. For the Quote-Generation DSL, I would write something like this:

Generating quotes has always been a chore. The problem is that the dependency matrix between the various components of a system has exploded exponentially ever since [some business event]. As a result, the process of turning a client’s requirements into a quote (the list of items and components needed, the amount of onsite work, the support contracts and ongoing maintenance) became a laborious, error-prone, and manual process.

At the end of 2006, a quote for [client name] missed a dependency on [system name], causing over $250,000 in additional costs during system installation. As a result, the need for an automated and reliable quote-generation system became obvious.

This isn’t part of any real-world documentation, but that’s the style I would use. It gives users the relevant background on the business conditions that led to the development of the system. This is necessary for them to gain a good understanding of the system.

I then describe the model that’s exposed to the user. This doesn’t necessarily match perfectly with the way the DSL works, but it’s the way I want the user to think it works. This is the mental model they’ll develop in order to work with the system. I like to think about this part as telling a story that will affix the mental model of the system in the user’s head.


Note

Although it’s possible to diverge from the model in the implementation, I don’t recommend it. It’s best to give users an understanding of how the system really works, even if it’s only at a very high level.


I also recommend explaining the model in the context of the business problem. Here’s another snippet of the Quote-Generation DSL documentation:

A quote is generated using the concepts of components, dependencies, and constraints. With those concepts, you can build all the requirements for the Voice Mail system. When you generate the quote, you give the system all the relevant information (number of users, existing infrastructure, required features, and so on) and it will read all the dependencies and constraints of the components, resolving them to a valid quote.

For example, consider the Voice Mail component. It can support up to 5,000 mail boxes per machine (constraint) and it requires the IVR component (dependency). This is obviously a simplified example, but it will do. You can specify these requirements like this:

specification @VoiceMail:
requires @IVR
users_per_machine 5000

This specification is readable, and it specifies the constraints on the Voice Mail component and its dependencies. It can be written by the Voice Mail team without regard to constraints that other components have. It is the system’s responsibility to generate the full quote for the customer based on the component specification that each team provides. Using this approach, you can unambiguously express all the requirements for the whole system.

It’s important to understand that each component owner will have to write their own requirements specification, which is then rolled into the Quote-Generation System. The system is smart enough to be able to ...

The next step is to take a few examples from the Getting Started Guide and explain what is going on in detail. I suggest picking at least two examples, with enough differences to show different aspects of your language usage, or show things from different angles. Once you’ve worked through those, the user should have a good understanding of how things are supposed to work.

At that point, I usually move on to a reference style, instead of storytelling. And one of the first things that needs to be covered is the language syntax.

11.3.2. Document the language syntax

What makes for a good language reference? You’ll need to cover the following topics at a minimum:

  • Code file structure
  • Keywords
  • Notations
  • Actions and commands
  • Basic syntax rules
  • Common operations

You will probably want to include additional documentation relevant to your specific scenario. For each of these points, you should also include examples, examples, examples. Let’s look at them each in turn.

Code File Structure

The code file structure is what a script file should look like. This section should outline whether users should include documentation comments, whether there are expected elements in the files, and so on. Let’s look at how you might document the Routing DSL’s file structure.

A typical file structure for the Routing DSL includes a documentation header, a filter, and one or more handle sections. A typical example is shown here:

"""

This is the documentation header;
Discussing what this file is doing
"""

# This is the filter; it decides whether
# we should handle this message
# or not
return if msg.type != "NewOrder"

# This is a handle section
HandleWith NewOrderHandler:
# Inside the handle section we transform the
# external message to its internal representation
Return NewOrderMessage(msg.customer_id)

# There can be more than a single handle section
HandleWith LoggingHandler:
# We can return the message in its raw form
return msg


Should the User Guide contain the syntax for Boo?

That’s a good question, and it usually depends on how technical your target audience is expected to be.

In almost all cases, I would include the syntax for common operations such as if, unless, and for statements. Those are too useful to keep from your users. Error handling (try, except, and ensure), resource management (using), and things of that nature should generally stay out of the DSL scripts, and I would avoid pointing them out.

Boo also has the notion of statement modifiers, and I recommend documenting and using them where it makes sense. For example, you can write a statement like this:

apply_discount 5.percent unless user.is_high_risk

Statement modifiers are applicable for if and unless. List comprehensions can use a similar syntax, but that’s not usually something that you would want to use in a DSL.


After showing users an example file, you should go over each section and explain what they do while the user has the sample script in front of them.

The Routing DSL is a very technical DSL, so a lot of its syntax is derived from the Boo syntax, but that isn’t always the case. The Quote-Generation DSL is a good example of a DSL that doesn’t look much like a programming language. I would document the Quote-Generation DSL file structure as shown in listing 11.1.

Listing 11.1. The Quote-Generation DSL file structure
"""

This is the documentation header, where we document what
this file is doing.
"""

# A specification refers to a single module
specification @moduleName:
# A specification denotes actions about
# the system as a whole
requires @anotherModuleName, "reason for requiring module"
same_machine_as @anotherModuleName

# We can have multiple specifications
# in a single file
specification @anotherModuleName:
users_per_machine 150

Like in the previous example, you need to explain the structure (how things are partitioned) but you don’t need to discuss the actual details. The high-level details were already discussed in the Getting Started Guide, and the nitty-gritty stuff should be discussed when we talk about each keyword in isolation.

Keywords

You need to document the keywords in the language. I don’t mean only things like if, try, and using. I’m talking about the DSL keywords. In the Quote-Generation DSL, for example, that would include keywords such as specification, requires, same_machine_as, and so on. Those are the keywords of the language.

Here is how I would document specification for the Quote-Generation DSL:

specification @moduleName:
<< specification actions goes here >>

The specification keyword is used to set up a context for all the requirements of a particular module. The module is specified using the @name notation, described in the next section. Valid module names are names of components as specified in the ERP system.

The specification ends with a colon (:) and is followed by a list of requirements and constraints (see the Constraints section below). Those let the Quote-Generation system ...

I generally find that it’s good to display the syntax of each keyword before discussing it. It gives the user something to glance at while reading the explanation.

Notations

A notation is a way of writing down something. Usually it allows a shorthand way to refer to something you use often, or to permit a better syntax. The @moduleName notation in the Quote-Generation DSL is a good example of that.

In the Quote Generation example, I would discuss notations as follows:

The @name notation is a shorthand for a component. This notation allows using component names (as defined in the ERP system under System > Administration > Setup > Components) directly in the text (replacing the hard-to-read Component IDs)

The @name notation is used whenever we want to refer to a component, in the specification, requires, and same_machine_as keywords.

Although we documented the @moduleName notation in the keywords example, it is important to also give them their own place, since users might want to refer to them specifically. This is especially true if you have a limited set of valid values in certain places, and they want to look at a particular value.

Actions and Commands

Actions and commands are the operations that you allow the user to specify using the DSL. In the Quote-Generation DSL, requires and same_machine_as are operations (exposed as keywords), as are the Deny and Allow calls in the Authorization DSL.

For the Quote-Generation DSL, here is how I would document the requires keyword:

requires @moduleName

The requires keyword can appear inside a specification block. It has a single argument, a module name.

This keyword creates a dependency between the component specification it appears on and the components specified as required. This dependency is taken into account ...

I usually like to make action documentation terse, because it is mostly used only for reference. Discussing the usage of each keyword in the context where it is used is usually left for the common operations section (discussed next).

Basic Syntax Rules

Don’t forget to document basic syntax rules, such as using indentation in the code (or using the end keyword, if you choose to use the whitespace-agnostic version of Boo). Or remembering to put the colon (:) character at the end of the line when you’re creating a new block. And so on.

Common Operations

Take a look at common scenarios and show users what they look like. Show users how they can write their own scripts by giving them a wide range of examples to peruse.

Here’s an example of documenting a common operation:

This example will specify the required dependencies for a single component. In the Quote-Generation system, constraints are set at the component level. We can set the following constraints on a component specification:

  • requires @moduleName
  • same_machine_as @moduleName
  • users_per_machine [numeric: number of users]

The following specification for the Voice Mail component sets dependencies on IVR and SMS components and specifies that 500 users are supported per machine.

specification @VoiceMail:
requires @IVR
requires @SMS
users_per_machine 500

As you can see, there is a direct translation between the way we think about the dependencies of the Voice Mail component and how we specify it. This is a fairly simple specification. A more interesting one would be handle conditions ...

So far we’ve only talked about the syntax, and the syntax is only a small part of a language. There are other things in a language implementation that need to be documented. We’ll discuss those in the language reference.

11.3.3. Create the language reference

The difference between the language syntax and the language reference may sound artificial, but I consider it important. A language isn’t just its syntax. The way a DSL is used is also part of the DSL, and you need to take that into account when documenting the DSL.

Usually, when the time comes to document a DSL, there is a lot of focus on the syntax, and some focus on how the engine works. There is little focus on how the environment and the usage of the DSL affect the DSL itself. Here are a few examples of topics that tend to be forgotten when documenting a DSL:

  • Naming conventions— How to name script files and elements inside them
  • Script ordering— How the engine determines the order of scripts to run and how to modify this order
  • Execution points— What events will run the scripts

All of those are important in many DSLs, not only for the scripts’ execution, but for how the scripts are written.

In chapter 9, we modified the Quote-Generation DSL to include a string that explains why a certain module is required by another, and the language reference should explain how we can get to that data from the final result of executing the script. After executing a set of scripts, we want to be able to examine the messages that the scripts generated. The documentation ought to contain detailed instructions about how to get them and include several references to the fact that you can get this information.

A final topic for documentation is the tools that are provided for the DSL, such as the IDE, the output viewer, and so on. I have seen users miss out on important tools because they weren’t aware that they existed. It is preferable to avoid that.

And now, once you’ve documented how the DSL is supposed to work, you need to explain how to deal with the unexpected.

11.3.4. Explain debugging to business users

One of the key things you need to cover in the User Guide is how to deal with the unexpected. I generally think of this as debugging, but the actions users will perform are different from developer debugging (which is not a user-level concept).

When I talk about user-level debugging, I’m talking about looking at the result of a script’s execution and understanding what has caused that particular result. If users can figure out what is happening on their own, it means one less debugging session that a developer has to go through. Increasing transparency in the application should be a key goal. These features should be documented prominently and referred to often.

Let’s take the Quote-Generation DSL as an example again. The end result of the Quote-Generation DSL is a quote. It’s a list of items and their prices (and also taxes, discounts, bundles, and offers, which we’ll ignore here for the sake of simplicity). The Quote-Generation DSL figures out all the complex dependencies and configurations, and a common problem is figuring out why a particular item appears in the resulting quote.

We deal with that by adding the reason parameter to the requires action. The UI should also display all the reasons why an item was selected. This small feature alone provides a lot of value to the system. Expanding on that, you could allow the user to go from the quote item to all the scripts that mandated the item’s selection. That would give the user a high level of control over what is going on in the application. (We’ll look at this in more detail in chapter 12.)

That’s the basics of the User Guide. The language syntax, the language reference, and the debugging and troubleshooting information are the key highlights. Now it’s time to move on to documenting the language for other developers.

11.4. Creating the Developer Guide

Unlike the user documentation, you’re on familiar ground when writing documentation for other developers. You can discuss things much more concisely, and you no longer have to deal with the pesky business details. You can discuss pure technological details to your heart’s content. Okay, maybe not that last one.

But it’s easier to write documentation for developers because there’s a lot of ground you don’t need to cover—they’re already familiar with programming. That said, DSLs are still a niche topic, so they do require some explanation.

I strongly believe that the best documentation is the code itself, but not knowing which code to read can be a problem. That’s why I recommend focusing your documentation on what the DSL does and where it happens, rather than on how it’s implemented.

I usually partition the developer documentation using the following scheme:

  • Prerequisites— Outline what you expect the developer will already understand when approaching the system.
  • Implementation— Explore all the moving parts in the DSL implementation.
  • Syntax— Document how the syntax for the DSL was implemented.

11.4.1. Outline the prerequisites

I like to start the Developer Guide by identifying the prerequisites for understanding what is going on. Usually, this comes down to having at least some understanding of the problem domain and the foundations of DSL building.

There are usually enough external resources on the problem domain that will give the developer a good idea about what you’re trying to do. As for learning how to build DSLs ... that’s what this book is all about.

Once you’ve covered the prerequisites, you can move on to the real meat: discussing the DSL implementation itself.

11.4.2. Explore the DSL’s implementation

As I’ve explained elsewhere in this book, thinking about a DSL only in terms of syntax doesn’t make sense. A DSL is composed of several parts, the syntax being the front end. The engine, the model, and the API are also intrinsic parts of the DSL and should be included in your documentation.

I don’t intend to describe how to document the model or the API; those are fairly standard and there is nothing new that I can add here beyond outlining standard development documentation techniques. The syntax and the engine require special attention, because they are closely tied to the way the DSL works.


Using tests as documentation

I have several DSLs that have no documentation beyond their source and tests. They’re usable, useful, and have been helpful. But I’ve run into situations where I, as the developer, could not answer a question about the language without referring to the code. I strongly recommend investing the time in creating good documentation for your DSLs.

Even behavior-driven design tests aren’t quite enough. Those types of tests can help make it clear what the language is doing, but they aren’t the type of documentation that you can hand to an end user to get them started using the language. Even if your users are developers, it’s not a good enough approach.

It’s your responsibility to make the system easy for users to use, and documentation is a key part of that. Handing users the tests is a good way to handle complex cases if your users are developers, but it’s not a good way to reduce the learning curve.


Before diving into the details in the Developer Guide, it’s important to provide a broad overview of how the different parts of the DSL work together. For example, if you’re using the model directly in your DSL, you should make it clear that changing the model will also change the DSL.

On the other hand, as we’ve seen in chapter 9, this approach suffers from versioning problems, so we tend to use a facade layer between the model and the DSL to allow for easier versioning. In that case, we need to document that, outlining the versioning concerns and explaining how to add new functionality using the facade.

Giving developers a good grasp of how everything comes together is important in enabling them to work effectively with the DSL.

11.4.3. Document the syntax implementation

Documenting the syntax implementation is different from documenting the syntax (which we did in the User Guide). When we’re talking about the syntax, we’re mostly concerned with what to write and how to write it. When we talking about the syntax implementation, we’re focusing on how to map the text to the language concepts.

When you have an internal DSL with a host language like Boo, you don’t have a lot of work to do in this area, but you still have some. For example, your language will have keywords, behaviors, conventions, notations, and external integrations that you’ve built into it to talk about the problem domain in a meaningful way. You need to document how they work.


Developer documentation isn’t just Word documents

Although the term documentation usually brings to mind wordy specifications and long sessions with a word processor, that isn’t necessarily the best way to provide documentation when the target audience is developers.

Well-structured code with inline comments, accompanied by tests, is usually the best form of documentation that a developer could ask for.


We’ll cover each of the syntax implementation parts, starting with the language keywords.

Keywords

When documenting keywords, I tend to list each keyword, along with a short description of what it is responsible for and how it is implemented (method call, meta-method, macro, and so on).

That may not always be possible when you’re extending the language externally (using the model or a facade, for example), so in those scenarios, I point out explicitly that extending the language is done externally to the actual language implementation and try to provide a tool that can automatically generate a reference for the documentation.

Building a list of keywords and their implementation semantics is a good first step toward documenting a language, but it isn’t enough. You also need to document the implementation semantics—the way the DSL behaves.

Behaviors

The term behavior refers to how the system performs operations, often complex ones. There are plenty of cases where what the DSL script appears to do and its behavior during execution are drastically different.

In the Quote-Generation DSL, for example, you execute the DSL to get a data structure and then process it further, without referring to the DSL itself anymore. In the Scheduling DSL, you’re building both a description of how you want the system to behave as well as the condition and actions to be executed. Understanding the translation between the different parts of the DSL (the resulting model and the engine that processes it) and how a change in one can affect the other is a common cause for confusion in advanced scenarios.

Whenever the behavior of the system isn’t obvious, it’s a good idea to explain how and why it works. In chapter 12, we’ll cover some advanced techniques that allow you to play with the language at a far deeper level. These techniques allow great freedom and the creation of very nice syntax and semantics for your DSLs, but they can be daunting if you come across them without some prior warning.

For example, take a look at listing 11.2, which shows a DSL that represents a rule engine for order processing.

Listing 11.2. A sample of an order-processing DSL
when customer.is_preferred and order.total_amount > 500:
apply_discount 5.percent

In this example, we access both customer and order to make a decision. So far, it looks simple. But let’s assume that customer can be null (if the customer doesn’t have an account and makes a onetime purchase). How will we handle that?

One solution would be to rewrite listing 11.2 as shown in listing 11.3.

Listing 11.3. A sample of an order-processing DSL, with null handling
when customer is not null and customer.is_preferred 
and order.total_amount > 500:
apply_discount 5.percent

I’m pretty sure you’ll find listing 11.3 less readable than 11.2, because in listing 11.3 the error-handling code obscures the way we handle the business scenario.


Note

A simple alternative approach to the problem of null customers would be to use the Null Object pattern (a null object is an object with defined neutral, null, behavior). Instead of a null reference, we could pass an anonymous customer. That’s not always easy to do, however.


We could improve on this by checking whether the customer is null at the DSL level, instead of at the script level. We could state that if a rule references a variable that is null, we won’t run it. This is an example of advanced behavior that’s not self-evident in the code, but it’s an important part of the DSL. This should be documented, both to point out this behavior and explain how it is implemented. (The full details on how to implement this are covered in chapter 12.)

Behaviors concern how the system does things, and it’s closely related to how the system is organized. This is where the system’s conventions come into play.

Conventions

A convention is a standard way of dealing with particular aspects of your application.

Conventions result in common, well-defined structures for our software, and that’s a major plus, but it’s just the tip of the iceberg. The fun part starts when the application is aware of those conventions and can make use of them. The use of naming and directory structures are typical conventions in our DSL implementations.

I mentioned earlier that these conventions should be documented in the User Guide; in the Developers Guide you need to document how they’re implemented. For example, do you rely on external ordering? Is the ordering by filename?

I usually use the term convention for anything that’s outside the code; I refer to conventions in the code as notations.

Notations

A notation is a way to express an idea in another manner, usually to gain either clarity or conciseness or both. In the Quote-Generation DSL, for example, you can use @moduleName as a notation to reference modules. Another notation might be the use of underscores_between_words instead of using PascalCase.


Tip

Notations and conventions seem innocent when you start to use them, but it’s easy to forget about them. They’re there to make things friction free, after all. But if you do forget about them, you may have to dig deep to figure out why something isn’t working when you aren’t following the proper convention or using a notation properly. It’s important to make sure conventions and notations are documented explicitly.


As with conventions, notations should be documented in the User Guide, and the implementation semantics should be documented in the Developer Guide. In the two previous examples, those involve registering compiler steps to process the code during compilation, and usually you would use the UseSymbolsStep and UnderscoreNamingConventionsToPascalCaseCompilerStep compiler steps that are part of the Rhino DSL project. Spelling this out may seem redundant now, but it’s likely to help in the future, particularly for more-complex DSLs that may contain many such notations.

One topic you still need to document is when you reach outside the DSL implementation to get your information.

External Integration

One of the more interesting DSL approaches I have seen is reaching outside the source files and the compiler into external systems to get additional information to solve a business problem.

For example, imagine that you’re writing a Quote-Generation DSL and you have a typo in a module name. During compilation, the compiler interrogates the ERP system to check whether all the specified module names are valid. This allows it to give you a compiler error instead of a runtime error. Another example is generating code at the compiler level from external resources, such as a database, web service, and so on.

Those external resources are also part of the DSL, but they aren’t things that you would usually notice (until they break, or are broken). Make sure that you document them adequately; I usually consider a list of all the external integration points and how they’re configured to be adequate.

I mentioned earlier that I consider the code the best documentation. But that approach fails when you start to play underhanded tricks with the compiler.

11.4.4. Documenting AST transformations

An AST transformation is when you take a piece of AST (the compiler object model) and modify it during compilation, affecting the output of the compiler. A common AST transformation is the creation of the implicit base class.

Another example of AST transformation that you’re probably familiar with is creating an XML document programmatically by manipulating the DOM—the DOM is the AST of the XML document. The code for doing that is usually long and complex, not because of what it’s doing, but because it’s full of details on how to manage the programmatic representation of an XML document. AST transformations written using the AST API directly require a lot of code. (Using quasi-quotation, which is covered in chapter 6, tends to simplify this by an order of magnitude or more, but it’s only possible if your DSL is implemented in Boo.)

The main problem with code that performs AST manipulations is that it’s hard to look at the code and see what the end result will be. For that reason, I recommend documenting most AST manipulation code with the code that it’s generating. I usually do this as a comment directly in the code. An example of documenting AST manipulation can be seen in listing 11.4.

Listing 11.4. AST manipulation code with documentation
// compilerContext = BrailViewComponentContext(macroBodyClosure,
// "componentName", OutputStream, dictionary)
block.Add(new BinaryExpression(BinaryOperatorType.Assign,
new ReferenceExpression(componentContextName), initContext));

// AddViewComponentProperties( compilerContext.ComponentParams )
MethodInvocationExpression addProperties =
new MethodInvocationExpression(
AstUtil.CreateReferenceExpression(
"AddViewComponentProperties"));
addProperties.Arguments.Add(
AstUtil.CreateReferenceExpression(componentContextName +
".ComponentParameters"));

The code in listing 11.4 comes from the Brail DSL, which is a text-templating language. As you can see, there is little to compare between the resulting code (which is shown in the comments) and the code that’s used to generate the AST to produce the end result.

This is one of the few cases where I am in favor of verbose commenting, because there is such a gap between the concept and the code that creates it. Such commenting is particularly important for people who aren’t used to reading this type of code.

So far, we’ve focused on documentation in the form of documents and comments, but that’s not the only way to document code.

11.5. Creating executable documentation

Most documentation is inanimate words on paper (or on the screen). You can produce screencasts that provide much higher bandwidth communication, but they tend to be a poor way to discuss details. The details are usually left to documents, but they don’t have to be.

Executable documentation is documentation that you execute in order to learn from. I consider tests a form of executable documentation, because I can look at the tests (and step through them) to see how various parts of the system are implemented. In fact, given a choice between a system with documentation but no unit tests, and one with unit tests but no documentation, I would take the system with the unit tests in a heartbeat.

Documentation goes out of date, and there is no real way to verify that it’s accurate except by poring over the code. Tests tend to be up to date, and they can actively tell you if they aren’t.

Tests as a form of documentation are usually only valuable to developers, but there’s another option for creating executable documentation. You can create a set of examples that show how the system works. You can provide a tool that will allow users or developers to play with the examples and see the results of their actions. Microsoft did a good job of this with the 101 LINQ Examples. Not only did they provide a lot of examples, there’s a GUI that allows you to modify them and play with different options. (The C# version of the examples can be found at http://msdn.microsoft.com/en-us/vcsharp/aa336746.aspx, and the VB.NET version is at http://msdn2.microsoft.com/en-us/bb688088.aspx.)

This type of documentation is valuable for both developers and users, and it’s an approach you should consider when creating documentation for your DSL.

11.6. Summary

In this chapter, we’ve gone over the major points that should be documented in a DSL implementation, both from the end user’s perspective and from the point of view of a developer coming on board the project.

Again, this is by no mean a comprehensive discussion of documentation. I’ve merely pointed out the highlights, indicating what should be documented and what type of documentation you should strive for.

Like most developers, I find writing documentation boring and maintaining documentation even worse, but it’s important. You may never see the payoff of your documentation efforts yourself, because they usually come in the form of user acceptance of the DSL and an easier ramping-up process for new developers that are added to the team. But those are valuable results nevertheless.

We’ve now looked at documentation, user interfaces, versioning, and testing, all of which are high-level concepts in building DSLs. It’s time to investigate common implementation issues and the patterns that are used to deal with them. That’s the topic of chapter 12—DSL implementation challenges.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.122.4