Software engineering perspectives
\seindex{perspectives}
Of the various phases of the software life cycle,
the design phase is perhaps the most interesting one
since it aims at reconciling the requirements resulting from the analysis
phase and the restrictions that are a priori imposed on the implementation phase.
On the one hand a design must specify the structure and functionality of a system
in a conceptually clear way and on the other hand it must take into account the feasibility
of an implementation.
To conclude this introductory chapter, we will investigate what support the three
paradigms just treated provide with respect to the design of a system,
that is to which problems they promise a solution
and which questions they leave unanswered.
Logic programming
\seindex{logic programming}
The contribution of logic programming as a program design formalism
lies primarily in its declarative nature.
Designing a program in a logic programming formalism
enables an easy transition to an actual implementation in a language
such as Prolog.
Prolog is a general purpose language based on first order logic
with a simple semantics. It uses backward chaining inference as a computation mechanism.
Due to its declarative semantics a Prolog program may be read
as a logical theory concerning a particular domain.
This aspect has contributed to the popularity of Prolog for AI-applications.
It has been extensively used to implement expert systems. See [Bu83].
Further, it has been used to model British legislation,
drug design and even systems programming.
In addition, in [Webster] Prolog is characterized as a potentially powerful
design representation tool, since it may be used to specify the functionality
of systems in a concise and formally precise way.\ftn{
In a similar vein, constraint logic programming languages can be thought of
as providing an even more powerful formalism,
since these languages have knowledge of a particular (formal) domain
built-in, which allows them to solve equations over these domains.
}
For the representation of knowledge, however, Prolog has a number of drawbacks.
It lacks first of all facilities to modularize knowledge,
that is to partition the knowledge in module-like entities.
It lacks, secondly, a means to construct hierarchies of concepts
in a structural way.
A number of extensions have been proposed to deal with these deficiencies,
extensions with hierarchical frame-based representations,
with inheritance hierarchies
and object oriented features.
According to [Su85], none of these extensions have matured sufficiently
to gain wide acceptance.
Promising approaches seem to be those aiming at incorporating object oriented features
in the logic programming paradigm, that
allow to create logical theories dynamically as first-class entities.
Object oriented programming
\seindex{object oriented programming}
Object oriented software technology has major impacts
on the traditional software life cycle.
One of these impacts is the shift of emphasis
brought about in the relation between the design and the implementation phases of a software
development project.
The design phase has become increasingly important
since the availability of object oriented programming languages
allows to regard the implementation as a process of refining
the decisions made in designing the system.
The popularity of object oriented programming languages
is partly due also to their successful use in prototyping systems
of medium complexity.
According to [Meyer88] the introduction of object oriented programming
has meant the beginning of a revolution in the program development process,
since these languages allow to design a system bottom-up, instead of in
the traditional way, top-down.
The end result of developing a system, bottom-up or top-down,
is a set of classes that specifies abstractly the functionality
of the corresponding objects by means of external method interfaces.
After a while, designing a system comes down to selecting a number of
already existing components and assembling them according to the requirements
resulting from analysis.
Although, undeniably, the object oriented approach promotes
the reuse of software, there are many problems to be solved
before this view becomes reality. See for example [Meyer90] and [BV90].
One of the major problems that we encounter here is the problem
of granularity. In many cases, a class will be a too low-level
entity to serve as the unit of reuse.
A visual metaphor that illustrates this problem is given by the term ravioli-code
to characterize a large collection of well-structured objects with highly complex
interrelations.
To overcome problems of granularity, one has to think in terms of application
frameworks to capture the functionality of subsystems.
Even then, the specification of a system in an object oriented
programming language falls short of providing insight into the conceptual structure
of the system, that is the design underlying the program.\ftn{
The plethora of constructs that may be encountered in actual object oriented
programming languages makes this insight even more difficult.
}
Experience has shown that prototyping is advantageous in situations where the requirements
are likely to change.
To enhance the conceptual understanding of the system embodied by the prototype,
the language in which the prototype is implemented needs to be of a sufficiently
high level to allow the code to be read as a formal specification of the system.
An object oriented approach is only fruitful then, if the objects
implementing the system provide an abstract view that corresponds
with the way we perceive the problem domain.
Parallel/distributed programming
\seindex{parallel/distributed programming}
The choice for a parallel or distributed implementation may be motivated either
by the application domain, in which concurrency is a natural phenomenon,
or by efficiency considerations.
From either perspective,
designing a distributed system involves a partitioning of the system
into separate components and an allocation of these components to (possibly distinct)
processes. See [Shatz87].
In order to distribute the computation among a number of concurrently
executing components, the design will have to indicate a partitioning of the functionality
among a number of distinct logical modules.
Further, the design must specify the synchronization and possibly the communication that occurs
between these logical modules.
Logical modules, in the sense used here, may be active objects,
single-threaded processes or even multi-threaded processes.
For the actual exploitation of parallelism, an allocation of these modular entities
to processes must be given,
taking into account possible precedence relations that may hinder
the independent execution of these modules.
Allocation may be done statically, before actually starting a system,
or may be determined dynamically by an allocation algorithm
that takes system parameters such as the workload and the availability
of processors into account.
Using a performance oriented cost function to determine
an optimal allocation, further factors that may be taken into account are
the amount of interprocessor communication and the total completion time.
One of the principal difficulties in designing a distributed system
is to ensure the correctness of the program.
Verifying a distributed system is difficult because of the non-deterministic
behavior of such a system,
that results from the independence of the concurrently executing components.
This non-determinism makes dynamic testing almost infeasible.
First of all, it is hard to reproduce the state of the system that has
resulted in an error.
Secondly, since there is no global state there is no way
to freeze the execution in order to inspect the states of the individual components.
Before freezing takes effect, the relevant components may already have changed
their local state.
As a more fruitful approach, [Shatz87]
recommend to employ static testing,
an analysis that may be performed without executing the program.
In a static test all syntactically specified control-flow paths must be
considered.
Analysis may then reveal the potential for deadlock,
or the occurrence of particular communication events.
Given the difficulty of applying a verification method
to ordinary sequential languages, the difficulty of applying such
a method to a distributed program will be clear.
Distibuted logic programming
\seindex{distributed logic programming}
In the previous discussion we have indicated some of the problems that
arise in designing a software system.
Whatever formalism is used, the partitioning of a program into modules and
the specification of the interaction between modules will always remain a non-trivial
problem that can only be solved by creative thinking.
Our effort of introducing a new language is not meant as a promise
to make this task any easier
but to provide the means to encode a solution in a high level formalism,
including all relevant aspects of a system (including its physical distribution).
Such a high level description promotes the conceptual understanding
of the system specified and makes the task of verifying the program easier.
In developing
the language DLP, which will be the subject of the rest of this book,
we have made choices to combine the paradigms introduced in this chapter.
With this discussion we have made an attempt to motivate these decisions
from a software engineering perspective.