Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Enhancing the Actor Development Environment


OCT91: ENHANCING THE ACTOR DEVELOPMENT ENVIRONMENT

Steve is a senior systems analyst at Tetra Tech Data Sytems Inc., a software development and systems integration house in San Diego, California. He has worked in applications development for nine years, using object-oriented languages in the MS-Windows environment for the past two-and-a half years. He can be reached at 14352 Mussey Grade Rd., Ramona, CA 92065, or through CompuServe at 70304,1423.


As object-oriented languages move into the mainstream of the software industry, developers are expressing their desire for tools to facilitate multiproject team development. We were faced with this issue when we began our second project using Whitewater's Actor language. An international airline had asked for a system that would automate the scheduling of airport personnel in services such as passenger check-in, baggage handling, and other tasks less visible to the public. We chose to develop the project as a Windows application written in Whitewater's Actor language, using an off-the-shelf database engine. The choice of interface environment was an important consideration because for most users, this would be their first computer experience. We chose to develop a Windows application because we felt the standard graphical interface would make the user's job as simple as possible and give us the ability to design a pleasing and informative interactive display. We had used the Actor language on a previous project, and picked it again because of the much-shortened development time we experienced. We also expected its object-oriented nature would ease the development of the major original algorithms.

The resulting application was delivered and installed on networked PCs at the airline's main airport and now schedules the work of 900 people daily. The airline relies on the system to determine all the jobs that need to be done each day by applying user-defined work standards to the ever-changing flight schedule. The complex determination process includes queueing theory, predicted passenger arrival profiles, and profile smoothing. An allocation algorithm assigns the jobs as efficiently as possible to the available staff to ensure all jobs will be covered. The system deals with schedule changes as they occur, including dynamically updated displays. Supervisors have the ability to interactively override the computer's decisions at any time.

Early in the project, I decided to invest some time extending the Actor development environment to better handle the needs of multiproject team development. We wanted to easily share portions of code developed during the previous project. We also wanted a way to easily coordinate and combine the work of each person. Many of the concepts behind the changes and enhancements I made are not unique to Actor and could be applied to other object-oriented languages as well.

Actor Environment Overview

Actor is an object-oriented language that provides a powerful system for rapid development of Microsoft Windows applications. Despite this power, it gives only limited support to aid the development of multiple projects by multiple programmers. After installation, the Actor executable and image files are placed in their own subdirectory (C:\ACTOR, for example) along with various source load files. From this directory there are subdirectories for source .act files, source backups, class source files, resources, and temporary work source files. A typical directory setup is shown in Figure 1. All projects can be developed within these directories or the entire directory group, and the files contained therein can be duplicated for each project.

Figure 1: A typical Actor directory setup

  XC:\ACTOR\
          ACT\
          BACKUP\
          CLASSES\
          RES\
          WORK\

Development Environment Shortcomings

The two simple approaches to organizing development of Actor applications do little to address the needs and problems of multipleproject development. If all projects are developed from a common directory, classes can proliferate to staggering numbers. No easy scheme exists for identifying general classes from those for specific projects. A common solution resorts to cumbersome class naming conventions.

Developing projects in separate directories forces duplication of code and the associated problems. Changes or bug fixes made to heavily used classes must be manually copied and integrated into each project. The lack of configuration control can lead to situations where classes are separately modified by different projects, resulting in divergent versions of classes. These same problems express themselves when updates of the language come out. Every project must be separately merged with the new versions.

The problems described so far are increased if more than one programmer is doing development. When working on separate computers with local drives, code duplication occurs within projects as well as across projects. The effort involved in merging the work of the various programmers hampers efficiency. Working on a LAN can alleviate code duplication within a project, but there are no safeguards to keep programmers from stepping on each other's work. This maintenance headache is made worse by the lack of identification within classes and methods of when and by whom changes were made. The time savings of common classes are soon consumed by the effort expended in manual configuration control.

Actor comes with a rich set of classes, easing the programmer's job. Occasionally, it is appropriate and necessary to add methods to these existing classes. This can be done by modifying the class source directly, using the Browser, or by creating and loading an .act file containing the source code for the additional methods. Both choices have their drawbacks.

Using the Browser is the most convenient way to add or modify methods in Actor's standard classes. Changes made this way are automatically placed in the source file for the affected class. When new versions of Actor are released, all changes of this type must be extracted and reapplied to the new class source files. There is no inherent identification of the methods that have been added or modified, so this can become a tedious task. Problems can also arise from a buildup of methods added with each project. Many of these added methods won't be required by a particular project, yet all will end up in the image when the class source files are loaded.

If changes to the Actor classes are segregated into .act files, it becomes much easier to deal with Actor updates. The buildup of code can be prevented by making separate .act files for different purposes. Unfortunately, the Browser can only find source code in the .cls class source files. When an .act file that has been loaded contains a modified version of a method existing in the class source file, a confusing condition occurs. If the method is selected in the Browser, the source code from the class file is displayed, even though the image executes the method from the .act file. Source code for methods loaded from .act files will not be found by the Browser, and must instead be maintained using a text editor.

Configuration Inheritance

After identifying the inconveniences just described, we looked for a way to enhance Actor's development environment to better support multipleproject development by several programmers. The resulting system is centered around the concept we call configuration inheritance. By extending inheritance into the dimension of project development, related projects can be organized in such a way that they inherit access to common classes, methods, and resources.

Project relationships are defined by their organization in the directory hierarchy. An example of such a hierarchy is shown in Figure 2. The top-level directory for Actor development (the Actor directory, with its act, backup, classes, res, and work subdirectories) contains the Actor language and its associated files in their original form. General additions and changes we make to Actor occur in the \ACTOR\GENERAL directory and its supporting subdirectories. Project subdirectories descend from the GENERAL directory. We modified Actor so it will resolve references to source files by searching the directory tree from the point of current development up to the original Actor directories. If you were working on "project2," for example, and you asked Actor to load a class source file, it would look first in C:\ACTOR \GENERAL\PROJECT2\CLASSES\. If the file wasn't found there, the C:\ACTOR\GENERAL\CLASSES\ would be searched next. If the file was still not found, Actor would finally look in C:\ACTOR\CLASSES. This same mechanism works with all source, resource, and include files.

Figure 2: Project relationships are defined by their organization in the directory hierarchy.

  C:\ACTOR\
          ACT\
          BACKUP\
          CLASSES\
          RES\
          WORK\
          GENERAL\

                 BACKUP\
                 CLASSES\
                 RES\
                 WORK\

                 PROJECT1\

                        BACKUP\
                        CLASSES\
                        RES\
                        WORK\

                 PROJECT2\

                         BACKUP\
                         CLASSES\
                         RES\
                         WORK\

The project hierarchy can be arbitrarily deep, allowing related projects to be grouped under common subdirectories. Thus, they may inherit access to common code and resources from the parent directory. Projects can have descendant subdirectories for testing and experimenting. Changes made in these subdirectories do not affect the parent directory. Source code and resources from a descendant subdirectory can be made a permanent part of the project by moving them to the parent directory.

Class Extensions

By itself, the directory hierarchy does not address the need of adding or changing methods for classes defined in parent directories. We implemented this capability by introducing a new type of source file we call the "class extension file." Actor class files have filenames ending in .cls. Our class extension files have filenames ending in .clx. Class extension files only contain code for methods, and class initialization code. Only normal class files can actually define a class. The power of this feature can be demonstrated using Actor's String class as an example. The original Actor class source file (C:\ACTOR\CLASSES\ STRING.CLS) defines the String class, and contains the String methods provided by Actor. Generally useful methods added to String are placed in the C:\ACTOR\GENERAL\CLASSES\ STRING.CLX class extension file. String methods specific to an individual project are contained in a STRING.CLX file in that project's CLASSES directory. Using this system, changes to source files are only made in the CLASSES directory of the project where work is being done. Because the original Actor source code remains unchanged, installing updated versions of Actor becomes a simple matter of replacing the original files.

Only one .cls file can exist for a class. This is where the class is actually defined. The directory level containing the .cls file is said to "own" the class. Descendant projects can only change or add methods in their own .clx files for the class. Changes in the definition of the class, such as ancestor class, instance variables, and class variables can only be made at the level owning the class. This protects the class from being redefined by descendant projects.

To implement the class extension feature, we made changes to Actor's Browser. When the programmer selects a method from the Browser's list of methods defined for a class, it looks successively up the directory tree until it finds the method in a .clx file or the .cls file. If the method is found in one of the project's parent CLASSES directories, the Browser notifies the programmer that the method is not "owned" by the project. This notification appears in the title bar as brackets around the method name. If the method is not owned by the project, and a change is made, the modified method is written into a .clx file for the class in the project's WORK subdirectory. The original .cls file remains unchanged. Methods added to classes defined higher up in the directory hierarchy are also placed in a .clx file for the class in the project's WORK directory. When a snapshot of the image is performed, the .clx files in WORK are copied into the project's CLASSES directory, as they are for .cls files.

To use the class extensions, you'll need the file EXTEND.LOD (Listing One, page 125) to load the development environment extensions and EXTSOURCE.CLS (Listing Two, page 125), which allows the source code for a class to be contained in more than one file. Other files, such as TOOLWIND.CLX in Listing Three, page 127, modify the existing Actor development classes to use or support the features I've described. Because of space constraints, not all of the class extension files are presented here. However, all are provided electronically, including BEHAVIOR.CLX, CLASSDIA.CLX, CLVARDIA.CLX, FUNCTION.CLX, METHODBR.CLX, SOURCEFI.CLX, STRING.CLX, SYMBOL.CLX, SYSTEM.CLX, BROWSER.CLX, and WORKEDIT.CLX; see "Availability" on page 3.

Resource Inheritance

A few changes were made to allow resources to obey the configuration inheritance system. In the GENERAL\RES directory, the actor.rc file was broken up into separate files for each type of resource. We created actor.acc for accelerators, actor.dlg for dialogs, actor.mnu for menus, and actor.str for strings. A skeleton .rc file was written to pull together the actor resources using the rcinclude directive. A batch file was written to invoke the resource compiler using an include path that looks first in the RES directory of the current project, next in the RES directory of the project's parent directory, and on up to ACTOR\RES. Using this system, all projects descending from GENERAL inherit the component actor resource files in GENERAL\RES. Projects need only copy the skeleton .rc file into their own RES directory, set the application name, and rcinclude the additional resource files specific to the project. By doing this, we avoid duplication of resource definitions and simplify maintenance.

Multiple Programmer Support

The final changes in our enhancement to Actor's development environment were made to support multiple programmers working on a LAN. Within a project directory, each programmer has their own image file, usually bearing the programmer's name with an .ima extension. Normally this would not be feasible because Actor uses only one file to save the dirty classes list and another for the changes log. We overcame the problem by changing Actor to use separate dirty classes and change log files for each image.

Automatic time stamping of method source code changes was implemented to allow simple tracking of modifications. When a method is accepted in the Browser, a time stamp is automatically inserted as the first line (overwriting the previous time stamp if it exists). The time stamp includes the date, time, programmer initials, class name, and method name, as in the following example: /*Stamp 1990/10/21 15:48 swh String:findPath */.

The grep utility can be used to extract a list of all the /*Stamp lines in a CLASSES directory. By using the DOS sort command, the list can be ordered by date, programmer, or class. The time stamp also makes it easy to differentiate between original Actor code and methods we have added.

Conclusion

In using these enhancements to Actor, we found that the concept of configuration inheritance adds a powerful and fundamental dimension to object-oriented development. The result is an environment that enhances the ability to create and utilize reusable code. This implementation of the concept was born out of our immediate needs, and constrained by limits of time. I hope the ideas expressed in this article will serve as a step in the direction of a standard, publicly available enhancement to Actor that fully addresses the problems I've described. This will make Actor an even more productive environment for applications development.


_ENHANCING THE ACTOR DEVELOPMENT ENVIRONMENT_
by Steve Hatchett


[LISTING ONE]
<a name="0241_000e">


/* EXTEND.LOD - Actor development extensions load file
 * Copyright(C) 1991 Steve Hatchett.  All rights reserved.
 *    Steve Hatchett          14352 Mussey Grade Rd.
 *    CIS: 70304,1423         Ramona, CA 92065
 * Use this file to load the development environment
 * extensions for use with the Actor programming
 * language.
 *     load("extend.lod");
 *     load();
 */
#define MAXSOURCENEST 5;      /* max directory nesting */
Actor[#Programmer] := "   ";  /* set this in workspace */
Actor[#StampText]  := "Stamp";/* time stamp header.  Another
                               * useful stamp header would be
                               * "Copyright (C) 1991 XYZ, Inc."*/
LoadFiles :=
{
/* Your path may be different. */
  load(new(SourceFile),"c:\actor\general\classes\string.clx");
  load(#("classes\extsource.cls"
         "c:\actor\general\classes\symbol.clx"));

/* The minimum code necessary to navigate the directory
 * hierarchy has now been loaded, paths are no longer needed.  */
  do(#(Behavior Browser ClassDialog ClVarDialog  Function
       SourceFile System ToolWind WorkEdit), {using(cl)
    loadExtensions(cl);
  });
}!!




<a name="0241_000f">
<a name="0241_0010">
[LISTING TWO]
<a name="0241_0010">

/* EXTSOURCE.CLS - Actor development extensions
 Copyright (C) 1991  Steve Hatchett. All rights reserved.
 Provides access to class source code. Allows the source code for a class
 to be contained in more than one file ordered hierarchically by directory
 structure. There is one .cls file containing the actual class definition.
 Additional .clx files may be exist at lower levels of the hierarchy. They
 can contain methods and class initialization code. */!!

inherit(Object, #ExtSourceFiler,
        #(clName      /* Name of class being handled. */
          ownsClass   /* True if the .cls file for class is
                         in this session's directory. */
          fileNames   /* Source code file names with paths. */
          ownsLast    /* True if we owned the last method
                         we read. */), 2, nil)!!

now(class(ExtSourceFiler))!!

/* Return a new  ExtSourceFiler, initialized to handle source code for the
 class whose name Symbol was given. */
Def openClass(self className)
{
  ^init(new(self),className);
} !!

now(ExtSourceFiler)!!

/* Load class extension files (.clx) for the class, but don't load the class
   file (.cls). This is useful for loading extensions to the standard
   Actor classes. Looks for all files in CLASSES subdirectories.  */
Def loadExtensions(self | work fNames fnm)
{
  work := loadString(332);
  if size(fileNames) > 0
     cand subString(first(fileNames),0,size(work))=work
    fileNames[0] := loadString(331) + getFileName(self);
    if not(exists(File,fileNames[0],0))
      removeFirst(fileNames);
    endif;
  endif;
  if size(fileNames) > 0
    fNames := copy(fileNames);
    fnm := last(fNames);
    if fnm[size(fnm)-1] == 's'
      pop(fNames);       /* removes the .cls file */
    endif;
    ^do(reverse(fNames), {using(fnm)
       load(new(SourceFile),fnm);
     });
  endif;
  ^nil;
}!!

/* Recompile the class by loading all its source code.
   Looks in WORK for own source file if class is dirty.  */
Def recompile(self)
{
  ^do(reverse(fileNames), {using(fnm)
     load(new(SourceFile),fnm);
   });
}!!

/* Return open SourceFile with given file name (which should include path). */
Def openSourceFile(self fName | sFile)
{
  sFile := new(SourceFile);
  setName(sFile,fName);
  if not(open(sFile,0))
    errorBox(loadString(311), clName
    + loadString(312)+fName+".");
    ^nil;
  endif;
  ^sFile;
}!!

/* If no source file exists for the class at the directory level of this actor
  session, then create an extension file in WORK, and mark class as dirty.  */
Def mustHaveOwn(self | sFile)
{
  if size(fileNames) == 0
     cor first(fileNames)[0] == '.'
    add(DirtyClasses,clName);
    insert(fileNames,loadString(332)
                  +subString(clName,0,8)+".clx",0);
    makeClassExtFile(new(SourceFile),self);
  endif;
}!!

/* Replace the the fSym method text with methtext, or add method text if it
  wasn't already in source file. Mode determines whether method is a class or
  object method. These changes will only be made in the source file owned by
   this actor session. Returns nil if not successful. */
Def saveMethText(self methtext fSym mode | rFile wFile)
{
  mustHaveOwn(self);
  if (rFile := openClass(SourceFile,self))
    wFile := saveMethText(rFile,methtext,fSym,mode);
    reName(wFile,(fileNames[0] := condDelCFile(rFile,self)));
  endif;
  ^rFile;
}!!

/* Replace the the class initialization code, with given code. Change will
  only be made in source file owned by this actor session. Returns nil if
  not successful. */
Def replaceClassInit(self text | rFile wFile)
{
  mustHaveOwn(self);
  if (rFile := openClass(SourceFile,self))
    wFile := replaceClassInit(rFile,text);
    reName(wFile,(fileNames[0] := condDelCFile(rFile,self)));
    close(rFile);
  endif;
  ^rFile;
}!!

/* Return the class init code for this class as a TextCollection. */
Def readClassInit(self | rFile text)
{
/* look through source files until some class initialization text is found.  */
  ownsLast := false;
  do(fileNames,{using(fnm)
    if (rFile := openSourceFile(self,fnm))
      text := readClassInit(rFile);
      close(rFile);
      if size(text) > 0
        ownsLast := (fnm[0] <> '.');
        ^text;
      endif;
    endif;
  });
  ^new(TextCollection,5);
}!!

/* Remove the fSym method text if it is found in source file owned by this
  actor session. Mode determines whether method is a class or object method.
  Returns nil if not successful or if method was not in owned source file. */
Def removeMethod(self fSym mode | rFile wFile)
{
  if size(fileNames) > 0
     cand first(fileNames)[0] <> '.'
     cand (rFile := openClass(SourceFile,self))
    if wFile := replaceMethod(rFile,nil,fSym,mode)
      reName(wFile,(fileNames[0] := condDelCFile(rFile,self)));
    endif;
    close(rFile);
  endif;
  ^wFile;
}!!

/* Load the class by loading all its source code. Looks in CLASSES for
  all source files.  */
Def load(self | work)
{
  work := loadString(332);
  if size(fileNames) > 0
     cand subString(first(fileNames),0,size(work))=work
    fileNames[0] := loadString(331) + getFileName(self);
    if not(exists(File,fileNames[0],0))
      removeFirst(fileNames);
    endif;
  endif;
  ^do(reverse(fileNames), {using(fnm)
     load(new(SourceFile),fnm);
   });
}
!!

/* Return true if the last method read using loadMethText was from a source
  file at directory level of this actor session.  */
Def ownsLast(self)
{
  ^ownsLast;
}!!

/* Initialize a new ExtSourceFiler.  */
Def init(self nm | metaNm)
{
  if class(nm) <> Symbol
    nm := name(nm);
  endif;
  if (metaNm := isMetaName(nm))
    clName := name(value(metaNm));
  else
    clName := nm;
  endif;
  getFileNames(self);
}!!

/* Return true if the .cls file for the class this filer is handling exists
  at the directory level of this actor session.  */
Def ownsClass(self)
{
  ^ownsClass;
}!!

/* Return text of aMethod. Note accordingly if source code is missing. Mode
 indicates type of method, either class (BR_CMETH) or object (BR_OMETH). Looks
 through all the source files for the class.  */
Def loadMethText(self aMethod mode | text rFile)
{
/* look through source files until the text for the given method is found.  */
  ownsLast := false;
  rFile := new(SourceFile);
  do(fileNames,{using(fnm)
    if not(rFile := openSourceFile(self,fnm))
      errorBox(loadString(311), clName
               +loadString(312)+fnm+".");
    else
      text := findMethod(rFile,aMethod,mode);
      close(rFile);
      if text
        ownsLast := (fnm[0] <> '.');
        ^leftJustify(text[0]);
      endif;
    endif;
  });
  ^aMethod + loadString(310);
}!!

/* Return the class's name the way Behavior would do it.  */
Def name(self)
{
  ^clName;
}!!

/* Return the class's filename the way Behavior would do it.  */
Def getFileName(self | dir)
{
/* return name + ext */
  ^subString(clName,0,8)
   + if ownsClass
     ".cls" else ".clx" endif;
}!!

/* Get the names of the files containing this class' source code.  */
Def getFileNames(self | dir fRoot base)
{
  fileNames := new(OrderedCollection,MAXSOURCENEST);
  dir := loadString(if clName in DirtyClasses
                    332 else 331 endif);
  base := subString(clName,0,8);
  fRoot := dir + base;
  do(MAXSOURCENEST, {using(i | fnm)

/* if the original .cls file or a .clx file is found for class, add it to list
  if it's above this session's directory.  */
    if exists(File,(fnm:=fRoot+".cls"),0)
      add(fileNames,fnm);
      if i==0
        ownsClass := true;
      endif;
      ^fileNames;
    endif;
    if exists(File,(fnm:=fRoot+".clx"),0)
      add(fileNames,fnm);
    endif;

/* construct the path name of the next higher level. */
    if i==0
      fRoot := "..\classes\"+base;
    else
      fRoot := "..\"+fRoot;
    endif;
  });
  ^fileNames;
}!!

/* Return the names of the files containing this class' source code
 (including own source file).  */
Def fileNames(self)
{
  ^fileNames;
}!!




<a name="0241_0011">
<a name="0241_0012">
[LISTING THREE]
<a name="0241_0012">

/* TOOLWIND.CLX - Actor development extensions
 *
 * Copyright(C) 1991 Steve Hatchett.  All rights reserved.
 */!!
now(class(ToolWindow))!!

/* Used by the system to initialize the DirtyClasses
  Set backup file.  DirtyClasses is assumed to be
  empty on entry.  For each class found in the backup
  file, ask the user whether to re-load the dirty
  class file or use the old class file.

  swh modified to support extended source files.
  swh modified to support unique dirty class file
  names between images.
 */
Def loadDirty(self | dName)
{

/* mod for unique dirty file name - base the dirty
 * file name on the image name.
 */
  setName($DFile,subString(imageName(TheApp),0,
                 indexOf(imageName(TheApp),'.',0))
                 +".drt");
/* end mod */

 if open($DFile, 0)
  then
...
  if size(DirtyClasses) > 0
  then do(copy(DirtyClasses),
    {using(clName)

/* mod for extended source file support - if the dirty
 * class exists in the system, let it tell us its file
 * name (.cls or .clx), otherwise the class was created
 * since the last snapshot, so it should be a .cls file.
 */
      dName := loadString(332) + subString(clName,0,8)
               + if not(Classes[clName])
                    cor isOwnClass(Classes[clName])
                  ".cls" else ".clx" endif;
/* end mod */

      dName := loadString(332) + subString(clName,0,8) + ".cls";
...
}!!

now(ToolWindow)!!

/* Insert/overwrite a time stamp as the first line of
   the method text.
 */
Def stampMethText(self text fSym mode | stampHead)
{
  stampHead := "/*"+StampText;

/* if method doesn't already have a stamp, insert
 * a line for it.
 */
  if left(text[0],size(stampHead),' ') <> stampHead
    insert(text,"",0);
  endif;

/* construct the time stamp. */
  text[0] := stampHead+" "+timeStamp(System)+" "
             +programmer(System)+" "+name(selClass)
             +if mode == BR_CMETH
                "(Class)" else "" endif
             +":"+fSym+" */";
}!!

/* Save the new text for current method into the source
  file. The first argument, text, is the method text.  The
  second, fSym, is the symbol with the name of the method,
  e.g. #print.  The new or revised method text ends up in a
  class source file in WORK.  Also, write the text to the
  change log.

  swh modified to support extended source files
  swh modified to support automatic time stamping
 */
Def saveMethText(self, text, fSym | textEnd, nowCl, rFile, wFile)
{...
    changeLog(ew, "now(" + nowCl + ")" + Chunk +
      subText(text, 0, 0, textEnd, size(text[textEnd])));

/* mod for automatic time stamps
 * place time stamp in method text.
 */
    if text
      stampMethText(self,text,fSym,mode);
    endif;
/* end mod */

/* mod to support extended source files */
    saveMethText(openClass(ExtSourceFiler,selClass),
                 text, fSym, mode);
/* end mod */

    makeDirty(self);
  endif;
  ^fSym;
}!!

/* ToolWindow class initialization
   Rewritten to override ToolWindow.cls class
   initialization to support unique dirty file
   names between images - base the dirty file
   name on the image name.
 */
$DFile := setName(new(TextFile),
                  subString(imageName(TheApp),0,
                     indexOf(imageName(TheApp),'.',0))
                  +".drt");


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.