Lecture 11: Solving Problems & Software Development

We have previously looked at different kinds of software, how software can be used, and the relationship between software and hardware. We have said very little, however, about where software comes from or how it is developed.

Developing a software application is a complex process that requires some guiding principles or methodologies. Many aspects of the process resemble engineering practices, while others are more reminicent of mathematical problem solving. Fortunately, both of these fall under the broad umbrella of general problem solving, a topic pioneered this century by George Pólya. Pólya wrote How to Solve It, a book that has sold over a million copies. In this book he divides the problem solving task into four stages:

  1. Understanding the Problem
  2. Devising a Plan
  3. Carrying out the Plan
  4. Looking Back
This approach is echoed in what has become known as the Software Development Cycle, consisting of five stages:
  1. Analysis
  2. Specification
  3. Design
  4. Implementation
  5. Testing & Maintenance
Analysis is the process of understanding the problem, initially formulated as an English description of what the application should do. The client or end-user is consulted to determine requirements: what is the input? in what format should it be expected? what output is required? how should it be presented? what types of processing are needed? etc.

Specification can serve a two-fold purpose. English (or any natural language) is inherently ambiguous. To eliminate as much uncertainty, ambiguity, and lack of clarity as possible from the problem description it is necessary for it to be restated in technical or formal terminology. Formulating the problem in terms of software engineering language makes it clear to the designers and programmers exactly what is expected. On the other hand it can also serve as a means of verifying that the analysis produced a correct understanding of the problem.

The design phase is where the plan is devised. It is at this point that commitments are made as to what algorithms and data structures (ways of organizing data within the program) will be used. Whereas the first two steps focused on what the problem was, this step is mainly concerned with how to solve it.

Once the design is completed, the program is ready to be implemented. The algorithms, which may still be expressed in English or some combination of English and computerese, are translated into programming language code that can be compiled and executed.

Finally, the application is tested. Actually, testing is performed throughout the entire development cycle. The model of the problem and the tools used to express its solution are constantly evaluated against the understanding the developers have of the problem. Maintenance is what happens to the software after it is released for use. In many environments, as much as 80% of the development time is spent on maintenance. Usually this is because not enough time was spent on analysis, specification, or design.

Within this framework, there has been a multitude of approaches to the design task. The most commonly used of these methodologies is called top-down design. Top-down design begins by examining the problem as a whole, then recognizes that from this wholistic vantage the problem appears complex, and thus searches for a means by which the problem can be decomposed into simpler parts. In other words, top-down design seeks to reduce the complexity of a problem by organizing it in an appropriate way.

Top-down design has three goals:

  1. Break the problem down into subproblems
  2. Solve each subproblem (possibly by using top-down design)
  3. Combine the subproblem solutions to form a solution to the problem
The first goal is accomplished by finding a natural decomposition of the problem. The subproblems must be cohesive. For example it would not make much sense to divide the task of building a house into the subtasks of building the north half and building the south half. Why not? Well, although we have created smaller subtasks, neither of these subtasks is any simpler than the original. On the other hand identifying subtasks such as electrical, framing, excavation, painting, etc., is a more natural, cohesive decomposition of the problem. Two guidelines for breaking the problem down are: One of the powerful features of top-down design is that it can be performed recursively: each subproblem may need to be futher divided into subsubproblems or subsubsubproblems, etc. This process of division should continue until each fragment of the problem is simple enough that its solution is obvious.

If the decomposition is done correctly, there should be a direct, virtually obvious way that the fragment solutions can be combined into a solution of the entire problem.