Covering real life in a program shouldn't make it uglier than a
program covering only textbook cases. To handle the more numerous and
complex cases, you will _generalize_ the code. This will let you
avoid handling specific cases in specialized code, but instead cleanly
handle all the cases with a single rule.
That's the same principle in physics (and really in all sciences),
where from a lot of seemingly disparate and noisy experimental data,
you elaborate a general theory, with a model that's beautiful and
simple (ie. which reduces to a few simple and nice equations).
Some design patterns are useful to perform this abstraction, like the
interpreter design pattern or the emacs design pattern.
On the other hand, it means that most of the code you will write or
use, won't deal with the specific problem, but with more abstract
considerations (such as how to manage resources, or how to transform
code), like in physics, most of the theory don't deal with actual
physical phenomenon, but are actually mathematical theories that seem
quite remote.
Let's take a few examples.
Often, users specify more cases than needed. For example, they may
say that some price depends on some parameter, such as the number of
persons:
It is obvious that there's actually a formula:
price = 10*2^(nperson-1)).
Actually, there's always a simple formula, since any set of N points
can be extrapolated by a polynom of degree N+1.
The interpreter pattern let you decompose the operations you have to
perform in the different cases, in a set of simple operations that are
specific to the problem domain. The specific cases can then expressed
as simplier "programs" using those operations. In a way, this allows
to decompose the problem in two orthogonal parts, one set that
contains generic simple operations, and another that contains simplier
programs specific to the concrete cases. Since those real-life cases
often change, having a domain specific language to express them let
also write them more easily and quickly, and even dynamically
(ie. change the specific case programs without changing the software,
just changing the data that is intepreted).
Another pattern found in emacs, and similar to the garbage collector,
is the display engine.
In the case of the garbage collector, we decouple the memory
management from the actual program, by having a separate algorithm,
orthogonal to the problem specific algorithms, to deal with the
problem of memory allocation and release. Once the garbage collector
has all the information it needs to be able to release safely the
memory that is not used anymore, it can do its job without interfering
with the domain specific program.
Similarly, the display engine is entirely decoupled from the rest of
the editor in emacs. The display engines is able to detect by itself
when the contents of the buffers change, and to compute alone the
difference between what is displayed on the screen and what needs to
be displayed after the changes. It can then produce an optimized
update sequence for the screen or terminal. The rest of the emacs
editor routines can modify the buffers with absolutely no
consideration for the displaying, which simplifies greatly their code.
In conclusion, if write your program as some general rule performing
the same treatment to all cases, and encode the specific real-life
cases as specific data to be processed by the general rule, you can
obtain a program that still handle all the specific cases, but doing
that in the most general way, and therefore being as clean and as well
designed as you wish.
That's the same principle in physics (and really in all sciences), where from a lot of seemingly disparate and noisy experimental data, you elaborate a general theory, with a model that's beautiful and simple (ie. which reduces to a few simple and nice equations).
Some design patterns are useful to perform this abstraction, like the interpreter design pattern or the emacs design pattern.
On the other hand, it means that most of the code you will write or use, won't deal with the specific problem, but with more abstract considerations (such as how to manage resources, or how to transform code), like in physics, most of the theory don't deal with actual physical phenomenon, but are actually mathematical theories that seem quite remote.
Let's take a few examples.
Often, users specify more cases than needed. For example, they may say that some price depends on some parameter, such as the number of persons:
It is obvious that there's actually a formula: price = 10*2^(nperson-1)).Actually, there's always a simple formula, since any set of N points can be extrapolated by a polynom of degree N+1.
The interpreter pattern let you decompose the operations you have to perform in the different cases, in a set of simple operations that are specific to the problem domain. The specific cases can then expressed as simplier "programs" using those operations. In a way, this allows to decompose the problem in two orthogonal parts, one set that contains generic simple operations, and another that contains simplier programs specific to the concrete cases. Since those real-life cases often change, having a domain specific language to express them let also write them more easily and quickly, and even dynamically (ie. change the specific case programs without changing the software, just changing the data that is intepreted).
Another pattern found in emacs, and similar to the garbage collector, is the display engine.
In the case of the garbage collector, we decouple the memory management from the actual program, by having a separate algorithm, orthogonal to the problem specific algorithms, to deal with the problem of memory allocation and release. Once the garbage collector has all the information it needs to be able to release safely the memory that is not used anymore, it can do its job without interfering with the domain specific program.
Similarly, the display engine is entirely decoupled from the rest of the editor in emacs. The display engines is able to detect by itself when the contents of the buffers change, and to compute alone the difference between what is displayed on the screen and what needs to be displayed after the changes. It can then produce an optimized update sequence for the screen or terminal. The rest of the emacs editor routines can modify the buffers with absolutely no consideration for the displaying, which simplifies greatly their code.
In conclusion, if write your program as some general rule performing the same treatment to all cases, and encode the specific real-life cases as specific data to be processed by the general rule, you can obtain a program that still handle all the specific cases, but doing that in the most general way, and therefore being as clean and as well designed as you wish.