UML for C Programmers

The introduction of natural C concepts such as files, functions, and variables into UML let C programmers take advantage of Model-Driven Development's benefits.


May 01, 2005
URL:http://www.drdobbs.com/web-development/uml-for-c-programmers/184401948

The Unified Modeling Language (UML) is a Standard designed and maintained by the Object Management Group [1] that lets software developers and systems engineers graphically represent the requirements, specifications, structure, and behavior of the systems they are designing. Model-driven development (MDD) technology extends the UML, making productivity gains possible over traditional document-driven approaches. MDD does this by letting you specify systems and software design graphically, simulate and validate the system as you build it, and ultimately produce full production code from the model for the target system, resulting in shorter development periods and (presumably) higher quality designs.

While the object-oriented and C++ embedded community have rapidly adopted these technologies, adoption by C developers has been sporadic. C developers normally follow one of two design styles—functional decomposition or object-based design, which is essentially a subset of object-oriented design. UML is an object-oriented language and has a natural fit with object-based styles. Thus, for C developers who want to use the object-based approach, UML-based MDD is a natural fit. While UML and MDD have posed problems for functional C developers, these issues can be overcome by extending UML with concepts natural for C developers. Another barrier to MDD involves code reuse. Using code-visualization technology, C developers can reuse legacy code. With these innovations, UML-based MDD can bring the same benefits to the C community that the object-oriented and C++ community have been experiencing.

One of the main reasons that C developers have not adopted UML is because they are used to function-based programming, and UML was always designed for object-oriented programming. By letting you program functionally by placing files, functions, and variables on diagrams in UML, you can use familiar concepts while representing them in a UML diagram, letting you design and think the way you normally do. This enables C developers to adopt UML-based MDD with a shorter ramp-up time than switching to an object-oriented approach or another language.

It is not enough to only develop in a natural manner; the code produced from the model must be structured and have the same look and feel as typical hand-code would. In addition, there may be times when the code must be written or modified by hand to facilitate debugging, ensure proper interaction with the hardware, or achieve maximum performance. Thus, for C developers to extract the maximum benefit from MDD, they must be able to program and edit at either the code or model level while ensuring that the model and the code always remain in sync. Providing model/code synchronization and producing structured code further lowers risk by letting UML and MDD fit into existing processes and environments. In other words, there's no need to change current coding standards, compilers, debuggers, or target hardware.

Another important element of existing environments is legacy code that can be used "as-is" or incorporated into the model to be further developed using MDD. When you want to keep legacy code as-is without any edits or modifications, code visualization can link to and display the code in diagrams as a part of the model while leaving the code untouched. If you want to use code as a starting point with modifications and enhancements, simply reverse engineer the code into the model and edit at both the model and code level to receive the benefits of MDD.

Object-Based Design For C Development

Because the basic concept of the object is relatively easy to understand and adopt, C programmers generally have little problem with this. The idea of objects communicating via relations is less natural and often takes C programmers longer to assimilate and master. On the other hand, the concept of inheritance, or "generalization" as it is known in UML, is more complicated. Using it effectively requires a lot of training. The use of virtual inheritance and multiple inheritance can also mean higher demands for memory. So by "object based," I really mean using object-oriented design without inheritance. You can still get the advantages of object-oriented design, such as abstraction and encapsulation, yet keep the size of code to within reasonable limits.

To illustrate how UML and C work together, consider the example of a Timer object, which can keep track of time. It could have attributes, such as minutes and seconds, and perhaps methods or operations, such as reset and tick. The reset operation could initialize the attributes to zero, and the tick operation could increment the time by one second. In UML, you can create a simple object-model diagram such as Figure 1 that shows a single object called Timer, which has attributes minutes and seconds of type integer, as well as public operations tick() and reset().

Figure 1: Timer object.

In C++ you would see a class, but in C you can use a struct, so this is what the code could look like:

struct Timer_t {
   int mins;    /*## attribute mins */
   int secs;    /*## attribute secs */
};

/* Operations */
/*## operation reset() */
void Timer_reset();
/*## operation tick() */
void Timer_tick();

The attributes are contained inside the struct, and the operations that can be invoked on these attributes have been prepended with the name of the object. This is what most people would have expected. Using a tool to do this enforces the naming convention at all times without extra work.

Functional Development Within UML

You can program functionally with UML diagrams by using a UML element called a "file," which is simply a graphical representation of a source file. This file is capable of containing all the elements that C developers are used to dealing with, including variables, functions, types, and so on. But it can be entered on to a UML diagram the same way a class would be in classic object-oriented programming.

The code generated from the model appears similar to the structural coding styles C programmers are familiar with. Rhapsody [2], a UML 2.0-based MDD environment from Ilogix (the company I work for) represents .c and .h files that have the same name and couples them together as one element on a diagram. The result is called a File. If you do not use this .c and .h file coupling in your code, then you can represent just a .c or .h file individually on the diagram. This means that you do not need to learn how to do object-oriented design, but can bring the file concepts that they have always used into the next level of abstraction—the model.

Take, for example, a Timer File, which could have the responsibility to keep track of time. It could have variables such as minutes and seconds, and perhaps functions such as reset and tick. The reset function could initialize the variables to zero, and the tick function could increment the time by one second. In the UML, you can create a simple structure diagram (Figure 2) that shows a single File called Timer that has variables minutes and seconds of type integer, as well as public functions tick() and reset().

Figure 2: Structure diagram.

The C code for this file would look like you would expect a typical C program to look like:

extern int mins;
extern int secs; 

/*## operation Reset() */
void Reset();

/*## operation tick() */
void tick();

This code looks the same as what a C programmer might write, except it was generated from adding these elements into Figure 2.

You can type in the C code if you want to, or generate it from StateCharts or Activity Diagrams. Here is the C code for the tick function:

void tick()
   {
      secs++;
      if (secs > 59) {
         secs=0;
         mins++;
   }
   }

Moreover, in UML, you can depict functions and/or variables as private. In Figure 3, a minus goes in front of the element that is private. The question is, "What does this mean to the C code since there isn't a private keyword, as in C++?" Actually, it is pretty simple.

Figure 3: A minus goes in front of the element that is private.

A "private" function is one that can only be invoked from within the File, so in C it makes sense to not declare this function in the specification (.h) file and to declare it as static in the implementation (.c) file. So, if you wanted the reset function to be private, you could use this declaration:

Static void reset() {
mins=0;
secs=0;

A private "local" variable can only be used within a File, so in C it makes sense to not declare this variable in the specification (.h) file. Rather, declare it in the implementation (.c) file and no longer have it declared as an extern.

Reusing Legacy Code

Most projects have legacy code, and with MDD tools, you can reuse that code and receive the full benefits of MDD. One way to reuse legacy code is to let the MDD tool represent your legacy code as diagrams through a unique process called "code visualization." These diagrams (see Figure 4) are only representations of the code and all edits to these dynamically generated models take place on the code level only.

Figure 4: Generated code knows about external code automatically in Rhapsody.

The ability to generate these visualized diagrams lets legacy code seamlessly integrate with model-driven development environments while protecting the code's integrity. This method of using known good code without modifying it reduces the risk of having a bug and avoids wasted development and test time because no new code needs to be created or unit tested. The process simply creates a wrapper that acts as an interface or a facade to the legacy code; it can then be shown on diagrams and the legacy code can be linked in as a library. Figure 5 is an example of a model that has been visualized utilizing legacy Bluetooth code.

Figure 5: Visualization of all code artifacts in Rhapsody.

Another way to reuse legacy code is to reverse engineer the code right into a model. Once a part of the model, it is easy to modify, enhance, and further define the legacy system, and to gain all the benefits MDD and UML 2.0 provide, including full production-code generation, model-code associativity, and design for testability—which allows errors to be found and corrected earlier in the process and ensures that the code and the design captured in the model always remain in sync. The legacy code becomes part of the modeled application and is generated, updated, refined, and tested within the MDD environment. This can be done all at once or piece-wise in an iterative reverse engineering of the system. Incremental testing of the new pieces of the system through model execution fosters a better understanding of how the legacy will fit within the modeled application and how your system performs as you incorporate it into MDD.

There are times in which you have legacy code that you wish to keep as-is, and some code that you would like to modify within your MDD environment. This is easily achieved. Select the code you wish to keep as-is to be visualized and select the code you wish to modify to be reverse engineered. You can also start off by visualizing the code as-is and then, if you decide you need to make changes, you can reverse engineer the code into the model.

Validating the Design Using the Model

In a typical system design, it is never clear that the model is correct until it has been executed. Technology is available to allow graphical back animation, which basically means that code can be generated automatically from the model and instrumented so that, when executed, the model is animated. This means that the same diagrams used to describe the model can be used to validate the model. For example, you are able to see the value of the variables, see what each relation is set to, see what state each file is in, trace the function calls between files on a sequence diagram, and even step through an activity diagram. This animation can be done at any time during a project and lets you spend more time being productive doing design (the intellectual property) than spending time doing the tedious bookkeeping portion of coding. By being able to test and debug, you design at both the model and code levels. Problems are detected early on and corrected when they are cheaper and easier to fix.

Generating Code from the Model

C code can be generated directly from the model; all the code for the Timer File, for instance, can be generated automatically. In fact, for most models, between 65-90 percent of the code can be generated automatically. The remaining 10-35 percent is code that you write such as the bodies for the tick and reset functions. Code can be generated automatically for the dependencies, association, files, functions, variables, StateCharts and Activity Diagrams, and so on. You just need to specify the functions and actions on the StateCharts.

Keeping the Model and Code In Sync

Typically, programmers spend time creating a model, then generating the code. Later, the code is modified to get it to work and nobody ever has the time or energy to update the model. As time goes on, the models get more and more out of sync with the code and become less and less useful. Again, technology is now available to ensure that any modifications to the code can be "round tripped" back into the model, ensuring that the code and the model are in sync at all times. This is important during the maintenance phase as well as when features need to be added to a new version.

Conclusion

C programmers can now experience the benefits of UML and MDD, whether they want to follow an object-based or functional decomposition method. The introduction of natural C concepts such as files, functions, and variables into UML let C programmers receive all the benefits of MDD, while thinking and working the way they are used to. Through the process of visualization, you can incorporate legacy code into the development environment without changing a single line, letting you reuse legacy code, either as-is or as a starting point. Outputting production-quality structured code directly from the model further lowers risk by allowing UML and MDD to fit into your existing processes, reducing the risk of adopting these technologies and saving development time.


Martin Bakal is a senior application engineer at I-Logix. He can be contacted at [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.