Abstract
This is a short essay on the topic of teaching software construction to students of
computer science on a University level. We advocate an approach that neither takes the bait
of glorifying the features of a certain programming language (however attractive they may
be) nor looks down to reality from a formalistic or methodological podium. Instead, we
suggest software construction to be taught on the basis of a well-chosen set of generic
principles.
Introduction
With the emancipation of computer science as a discipline to be taught at Universities
and Polytechnics, and with the growing diversity of academic computer science curricula,
the questions of profile, соntents and approach arise. When browsing the shelves of any
bookstore that sells computer science literature, we soon recognize the premature state
of the still young constructive science of computing. In their large majority, textbooks
fall in one of two categories
- Voluminous and rather accidental 1000+ page accumulations of a choice of recipes based
on a more or less tricky application of specific features of some specific product
- Promotions of a certain methodology as a cure all, often driven by its own momentum
and adding one more layer to the problem set, as it is vividly
put in metaphoric form in Tony Hoare's famous Turing Award lecture "The Emperor's Old
Clothes"
Obviously, none of the two profiles qualifies for a serious academic education, mainly
because the major concern in both cases is imposing a certain tool or methodology on the
students, thereby putting on a symbolic corset and narrowing down the minds instead of
opening them up. Quoting philosopher and "oracle of the electronic age" Marshall
McLuhan: "We make our tools; then our tools make us".
If we address future computer scientists, we should much rather focus our teaching on
the generic competence of making tools in contrast to the competence of knowing, using
and mastering tools. Our educational efforts should be guided by the Chinese proverb
"Give a man a fish and you feed him for a day. Teach a man to fish and you feed him
for a lifetime."
So, good advice is urgently needed. My personal solution is teaching generic principles,
where a generic principle is an archetype of a principle that distinguishes itself by
the absence of inessential artefacts. Generic principles provide deep insight into
intrinsic aspects, leave ample room for generalization and encourage creative and
unexpected applications. Put differently, generic principles teach the art of
abstraction.
It is worth noting that any coherent set of generic principles describing a discipline
represents a design of the discipline on a meta-level. For example, the collection of
generic principles discussed in the next section represents my personal design of
the discipline of software construction. It is the quintessence of a 35+ years of
experience in the field. With a view to emphasize the overall choice of principles,
we leave it at an enumeration of the principles, augmented by a very short elaboration
on each principle in note form.
The Generic Principles
- Principle 1. Small-Scale Programming vs. Large-Scale Programming. The discipline
of programming splits up into two methodologically very different sub-disciplines:
Small-scale programming and large-scale programming. While small-scale programming is
primarily concerned with programming of algorithms in the classical sense, the main
topics of large-scale programming are global runtime structure, interoperability and
orchestration. Advanced programming languages implicitly cover both sub-disciplines,
with the result of an unfortunate blurring of concepts. However, separate frameworks
for large-scale programming exist.
- Principle 2. Programming by Stepwise Refinement (Small-Scale).
The development of an algorithm is simplified considerabiy when divided in phases of
successive refinements. Actions are typicaily specified merely by their name and
parameters at an early stage of the development and refined in terms of a procedure
including a local state space at a later stage. Recursion integrates naturally with
this process.
- Principle 3. From Specifications to Machine Programs (Small-Scale). The abitity of
proving program properties with mathematical rigor is indispensable in any safety-critical
scenario. The preferred approach to this goal is the use of a highly
architecture-independent form for the specification of the algorithm and for the required
proofs, and then to use an automated compiler for mapping the specification onto a given
architecture, under invariance of the relevant properties. Assertions, invariants and
non-determinism are powerful concepts in this field.
- Principle 4. Separation of Interface and Implementation (Large-Scale). The notion of
interface is of paramount importance in system design. The use of an explicit (and
preferably thin) interface between a servant object and its clients is the key to
reducing dependencies, with two beneficial consequences: The implementation of a servant
object (a) can be exchanged at any time without compromising the system's integrity
and (b) is fully protected against erroneous or malicious corruption. Another substantial
benefit of an interface-aware design is the fact that clients may ignore all but the
required facets of a servant object. Unfortunately, interfaces in current languages come
as method tables only. Generalized forms such as dialog protocols and XML schemata would
substantially enhance the power of the interface concept.
- Principle 5. Event Control as a Reactive Runtime Model (Large-Scale). This is a well
known runtime model or "design pattern" based on "inverse programming". Participating
objects subscribe to the set of events they are interested in. On occurrence of an event,
each of its subscribers gets notified by some "built-in" controller and thereby gets
a chance to handle the event property. This model has regained popularity in some specific
contexts, notably in graphical user interface programs (GUI), browsers and component
frameworks for the purposes of activating commands and scripts ("on-click" events)
and for "wiring" components (event --> reaction wire). One of the main benefits of the
event control model is a strict and clean separation of the event detection logic
(the controller) and the event handling logic (custom objects). In principle, it is
possible to deploy a highly customizable system consisting of a sophisticated closed
controller implemented in machine language plus a compiler for any suitable
application-specific scripting language. Another completely different application
domain for the scheme of event control is hot-pluggable system components such as
device drivers.
- Principle 6. Unification of Objects and Activities (Large-Scale).
The metaphor of object-oriented programming features a multitude of qualities, among
them conceptual support for (a) separation of concerns via encapsutation and
(b) abstract treatment of objects via late binding of method implementations. Amazingly
enough and in contrary to the original object-oriented language Simula-67, objects
in current programming languages are "passive" and "remote controlled" by concept
in the sense that they merely react on method calls issued by some external
thread of control. A unified and more advanced computing model includes "active
objects" that show some intrinsic behaviour in form of one or more encapsulated
activities (think of a watch) and interoperate with each other via "stateful"
dialogs (think of negotiating agents). Notice that the active object model in a very
precise sense is the exact dual to the scheme of event control.
- Principle 7. Introspection for the Support of Components (Large-Scale). Component
systems distinguish themselves primarily by the existence of а builder tool that
provides the functional support for assembling prefabricated and precompiled software
components. Therefore, building is an additional phase between compilation and runtime
that obviously depends on the availability of a universal mechanism called
introspection for inspecting the properties of the precompiled constituents. In
advanced component systems, the phases of development and building are completely
decoupled, and the builder cannot count on any knowledge of the programming language
and compiler used for the implementation of the different components involved in
a composition. The only viable concept of introspection in such an advanced
component system is a standardized mechanism of self-reflection built into each
participating component.
Conclusion
The primary goal of any academic education for future computer scientists be competence
in designing and implementing systems and in particular tools. We recommend an approach
that is based on teaching a well-designed set of generic principles, illustrating both
the art of abstraction and the "golden rule" of system design that could be paraphrased
as follows: "Unify concepts that differ merely in inessential details and separate concepts
that resemble each other merely in inessential details."
In concluding we notice that we do not advise the unchanged use of the method of
teaching generic principles for non-computer science students. An approach on a more
"literal" level may be sufficient and more effective in the cases of an audience
consisting of "applied" computer programmers and of computer users respectively.