On design processes

Ursula Dreier Ursula.Dreier@ruhr-uni-bochum.de
Thu, 03 Dec 1998 02:04:22 +0100


I feel that I should say something about what I think is important in a
design process like that we're in at the moment. I apologize if it gives
the impression to be rather philosophical (is that the right word?), but
it's important nevertheless. I try to give some examples to clarify the
motivation for all this.

I've done some complete design cycles from systems analysis to
implementation and customer support, and I can tell you: If you start
implementing too early (that is, before you got everything laid out and
double-checked in your mind), not only the time you invest in that early
code will be wasted, but chances are that in the end even the quality of
your design will suffer, because you will be tempted to reuse the code
you already wrote, even if its design is flawed (nobody likes to start
over from scratch repeatedly).

If you already got everything laid out, then you should communicate that
knowledge to us before starting to implement. This would require to
write it down, and I bet that you'll find some more or less serious
design flaws during that process.

Even if you are quite sure that you arrived at a solution that you like,
openmindedly discussing this with others is likely to show you weak
points that you overlooked, or orthogonal dimensions you didn' think of
before.

This project has the advantage of not having a tight deadline. Although
we should not waste time, this is (at least for me) one of the rare
chances to do some "meta-thinking" in order to learn something about the
design process as well as about language design and implementation (the
latter being the most uninteresting part for me).

Let me first describe how I like to do a design. Maybe you find some
similarities or differences in your way of doing it.

First I get a vision what approximately I want to achieve (the design
goals). Then I analyse that, categorize it and distill common principles
out of it. At some point I arrive at the most abstract layer of my
design, and I derive a set of assumptions from that (let's call them
"axioms" because I won't let go of them easily) , which should be as
small as possible to keep the mutual dependencies clear. Then I go the
opposite way (from abstract to concrete) and look what can be achieved
within the limits set by the axioms. Two things (among others) can
happen to make me rethink about my axioms:
1. There is something I desired that cannot be implemented sufficiently
easy and performant,
2. There are possible configurations which make no sense, or worse, that
cause trouble.

For our discussion to be fruitful, everyone should try to determine his
set of axioms and communicate it. This will reduce the chance of
misunderstanding the other's implicit assumptions.

For me, the most important design goals are:
1. Be as easy to use as possible and suited for small projects as well
as large ones. This is because I think existing tools are not as good at
these issues than they should or could.
1.1 For small projects to be feasable, there should be as little fixed
overhead as possible.
1.2 For big projects, safety, scalability and clear structuring (of the
source) are of  of premier importance.
2. It must be as simple to implement as possible. This reduces coding
time, bugs, complexity.
3. Be as flexible as possible.

Less important design goals (at least for the next future) are:

1. Performance (as long as the road to improvement is open)
2. Portabality (presents no headache to me, given a proper design)
3. Being useful for each and every purpose (if someone wants to write
real time apps, action games, device drivers, they should use other
tools).

I arrived at this set of axioms:

1. Everything that uses memory should be contained in typed objects, so
it is subject to garbage collection. For optimal results, that includes
code (be it source code, executable, byte code, or whatever). Maybe an
exception can be made for the very kernel (VM / memory management / boot
loader) because that won't ever be discarded.

Each object that is accessible as a separate entity should be contained
in its very own memory block.
To clarify this: If you can store a reference to an entity at source
level, that is an object. If you can access it only using some built-in
method, that need not be an object. From this follows: References always
reference objects, never parts of them.

These restrictions always in mind, there still can be a "compressed"
from of a general tree residing in one object. This is useful for
semicompiled "threaded" code (to be executed by a VM), which consists
primarily of object references with some directly interpreted parts in
it (jumps, for example).

Object layout is most important: What cannot be assembled within the
assumptions made regarding memory layout, cannot be implemented. It will
have to say more on that in another thread.

2. I want to do every task at the highest possible abstraction level.
For example, not hardcoding the operator list, but rather keeping it in
some list object, together with binding powers and the like.
Objects should be describable in terms of theirselves (recursively) as
much as possible.
I really like the idea to store source code in an already structured
from, as precompiled as possible without reducing the freedom of the
programmer more tham necessary.

This will help to keep the kernel small and clearly laid out and reduce
the overall amount of code.

3. Be as independent of other software systems as possible, especially
at places as crucial as the kernel.

Hans-Dieter