Software Engineering
Home Planning Requirements Writing Hazard Analysis Requirement Analysis Config Control Software Design Software Testing Software Standards Basic Logic

Software Design - Paradigms of Software Engineering

Paradigms = 20 cents.

Actually, a Paradigm is a way of thinking. This section gives an overview of the major paradigms in software engineering:

UnStructured Programming

In the beginning, all branching was done using GOTO. Assembly language, Fortran, and BASIC are perfect examples of unstructured programming. An unstructured program might look like this:

The illiberal use of branch instructions like GOTO and GOSUB gave rise to the term "Spaghetti Code," so called because a flowchart could become as convoluted and twisted as a plate of spaghetti. As program size increased, defect rates skyrocketed.

The greatest example in my own experience was when I inherited a non-functioning assembly langauge program that spanned 55 pages! Not a single line break, not a single subroutine - just 55 pages of non-stop spaghetti! And, it did not work; nor could anyone understand it.

Ultimately, tool vendors responded by adopting structured methods. For example, Microsoft Visual Basic® now looks more like Pascal than it does the original GWBASIC.

Structured Programming

In the 1960's, Thomas McCabe identified two fundamental design structures: Decision Blocks and Loops. He also correlated high program complexity and high defect rates with the breaking of those structures. As a result, newer programming languages like Pascal implemented the concept of structured programming. A structured language has constructs like IF-THEN-ELSE and WHILE-NEXT, and possibly variants such SWITCH-CASE, DO-WHILE, and FOR-NEXT. (These variants are all simply variations of McCabe's two fundamental constructs.)

Example of a structured program. This program has one decision block and two loops.

McCabe also identified a major source of program complexity - branching into and out of the middle of a decision block or loop. Since this could only be done by using a GOTO,  software development forums soon became home to the "GOTO Wars." To GOTO or not to GOTO - that was the question.

Another valuable structured programming concept is the Function (or Procedure). These are defined blocks of code with a single entry node and a single exit node. They accept parameters and can return a value.

What If My Language Isn't Structured?

Structured concepts are a high-level language concept - they do not exist at the machine level. So, if the language is not structured, then we can still choose to create those same structures -we just have to do it explicitly. Most unstructured languages have some sort of conditional Jump instruction, which can be used to create a decision block (after all, that's how the compilers really do it under the hood when they create the machine code).

Structured Decision Block

IF (condition) THEN
  Do A
ELSE
  Do B
END IF

Unstructured Equivalent

JUMP (NOT condition) TO 1
Do A
GOTO 2
1: Do B
2: No Operation

Here's another example of explicitly using structured methods, for error handling in Basic. Lines 10 through 110 form a single decision block.

Structured Equivalent:

OK = Open File
IF (OK) THEN
  Read File
  Close File
ELSE
  Display Error
END IF

** Probable source of error, such as file missing, though actually an On Error Goto will be in force throughout the entire function. But that's not important for demonstrating the concept of introducing structure to an unstructured language.

Functional Decomposition

This is a top-down approach to software design. Functional Decomposition breaks a large problem into successively smaller chunks, until at last we reach the function level.

For example, one can decompose the types of operations needed for a word processor, such as editing, selecting fonts, printing, saving to file, etc. A software managers might also use Functional Decomposition to delegate major tasks among various programmers.

Functional Decomposition, which was introduced at about the same time as structured programming. A Structural Language made it easier to implement a Functional Decomposition design, so they complemented each other well. Therefore, while the two concepts are unrelated, they became merged in many minds.

One of the best uses I have seen of Functional Decomposition is to use it at the single function level and do design-by-comments. The programmer designs the function in comments, defining the subtasks in detail. This allows the logic to be debugged prior to coding. Then, the code is written below each comment. This way, the comments show the overall design of the function. For example (comment symbols omitted for easier reading):

Open File
IF File Open was successful THEN
   DO
     Read a line
     Store line in buffer
   UNTIL End of File
   Close File
 ELSE
   Give an error message
END IF

This method allows the programmer to capture the intent and the design of the function prior to writing the code. Once the programmer is certain that the logic will perform the desired task and is robust at handling errors, the code can be written beneath the comments.

Object-Oriented Design (OOD)

Many books on Object-Oriented Design begin with the ritual debunking of Functional Decomposition. Of course, Functional Decomposition was popular when people knew that they would never need more than 640K of memory! But as program size grew, defects grew even faster. A new paradigm was needed.

OOD was created primarily to overcome most of the problems that continued to create high program complexity and correspondingly high defect rates. While Functional Decomposition was not directly to blame for the problems, it did nothing to discourage many error-prone programming practices - such as the ubiquitous use of global variables. OOD also afforded a better breakdown of the program than did Functional Decomposition.

OOD is concerned with:
  • Identifying objects in the program (such as a Database or a File).
  • Identifying roles played by the objects (i.e. the Methods).
  • Hiding the internal data and code used to implement those roles (encapsulation).
  • Exposing only the Interface Methods to the outside world.
  • And so on... I will not regurgitate the many fine books on OOD.

The point of OOD is to first define the Interfaces to the Objects, then code to the Interfaces. As long as the Interface remains the same, the implementation behind the interface is free to change without impacting the rest of the program. And, if you add the polymorphism of a language like C++, you can make an object change its behavior on the fly without the rest of the code being any the wiser! Let's see Functional Decomposition do that!

By the way - you can still use Functional Decomposition within a Method, using the design-by-comments process discussed above. Within a single Method you are working at the low level of detail that was characteristic of the time when Functional Decomposition was originally developed - so I'm not surprised that it can still find a good home as a tool in OOD.

Object-Oriented Design and Object-Oriented Languages are two different things. An Object-Oriented Language (like C++) makes it easier to implement an Object-Oriented Design, but Object-Oriented Design can be done in any programming language - even Assembly language! Obviously, Object-Oriented Languages also use Structured Language constructs - not GOTOs. (Although the careless use of try...catch and throw can have the same destructive effect as an army of GOTOs.)