Hacker News new | past | comments | ask | show | jobs | submit login

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:

    1 -> $10   2^0 
    2 -> $20   2^1
    3 -> $40   2^2
    4 -> $80   2^3
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.




Thanks a lot for your in depth explanation and the examples. Gives me a lot of motivation to keep working on keeping my codebase clean.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: