12. Mai 2008

Wege aus der kleinen Coderkrise

Jeder Coder kennt die kleine Krise, wennn man zu lange an einem Programmierproblem hängt, dass eigentlich schnell erledigt sein sollte. Manchmal ist es ein Algorithmus, der sich nicht schön in geschlossener Form ausdrücken lassen will. Manchmal ist es eine Lösung die sich standhaft weigert auch alle Grenzfälle abzudecken. Manchmal ist es eine Komplexität, die sich nicht beherrschen lässt. Die Lösung der Krise wäre für den Coder ein Riesenschritt, für den Rest der Welt nicht. Meistens ist es sogar so, dass der Rest der Welt das Problem nicht sieht, weil es "nicht wirklich ein Problem sei kann" oder es "am Ende ja sowieso geht, wozu also aufregen?". Trotzdem bereitet es dem Coder schlaflose Nächte, Haareraufen, Hirnzermartern, Überstunden.

Im Nachhinein gesehen ist die Lösung dann doch meistens entweder "ziemlich einfach", "offensichtlich" oder "ein Hack". Jedenfalls ist es gelöst. Der Coder ist glücklich. Aber sonst interessiert sich keiner dafür.

OK, das war bisher nur Gerede. Diese zwei Absätz helfen keinem. Damit das ganze noch Hand und Fuss bekommt also hier meine Lösungsansätze. Natürlich hängt die konkrete Lösung sehr stark vom Problem hab. Deshalb hier nur Lösungsstrategien, keine Lösungen. Wie immer bei "Patterns": bitte keine Wunder erwarten. Hier steht nur was der vernünftige Coder eh' schon immer gemacht hat.

Wolfspel'z Wege aus der kleinen Coderkrise:

1. Display State

Short: Replace the display of state transitions with a display of the state.

Description: If a system has a complex state and if the state is displayed, then state transitions can be shown by changing the display according to the state transition. But sometimes there are too many possible transitions, similar, but different transitions, transition variants, so that changing the display to reflect the change of the model might be too complicated. Also: testing is very difficult, because all transitions (e.g. by user input) must be tested. If anything changes, then testing all transitions must be repeated. It is difficult to keep a the display consistent with the model, because changes are applied to both. They do not depend on each other. A model-view architecture surely helps, but introducing MVC might be a disruptive architecture change. So, the simpler solution is to redraw the complete display on each model change. If a state transition changes the model, then just repaint the screen.

Variant: If repainting is not feasible, e.g. in case of HTML user interfaces, then rearranging all elements to reflect the model's state is equivalent.

Comments: The downside of this approach is that it might be bad for performance. Applicability depends, but it might give you time for a real solution, especially if there is a deadline. (Let performance issues come back as a bug report :-)

Example:
If the code reads:
model.changeSomething();
display.showChanges();
model.changeSomethingElse();
display.showOtherChanges();


Change it to:
model.changeSomething();
display.showAll();
model.changeSomethingElse();
display.showAll();


2. Perturbation Theory

Short: If special cases prevent a closed solution, then treat them separately.

Description: An algorithmic solution might be difficult, because of context dependencies, special cases, or edge cases. They can make the algorithm complex. Complex algorithms are not good. algorithms should fit on a small page. A solution is to create the core algorithm as if there were no special cases. Then fix the remaining cases separately. A typical solution then starts with the core algorithm. If that is understood and tested, then work around it. The core algorithms could be followed by an additional code section that "fixes" wrong results of the core for special cases. There can ba multiple consecutive such fix-sections. Try to identify the core and then classes of deviations from the core. Then make all individual code sections starting with the core. Do not try to mix special cases into the core algorithm.

Comment: This approach is known as Perturbation Theory in physics. The first order solution is linear and understandable. Higher order solutions add special detail, but are more difficult to comprehend. Therefore they are split fom the first order and added later when the first order works.

3. Simplification

Short: Reduce complexity if possible.

Description: Structure is usually a good thing. But structure also makes solutions more complex. A single layer of hierarchy might make a problem just the bit too complex for the coder to understand it easily enough to derive a simple solution. If you strip away complexity, then you use features or potential features. Even features you might need or find cool or need in the future or might need in the future. Ask yourself: could it be simpler? The answer "no" is forbidden, because anything can be simpler even though you might loose something. The question is: do you loose something that you really need NOW. It might be, that you can even generalize and undo the simplification later. The problem is now. It must be solved now. Do not solve future problems. Just try not to inhibit future extensions while doing the simple thing.

Application:
Ask:
- could it be simpler? - the answer is "yes, but..."
- what would be missing?
- does it hurt now?
- does it hurt later? if yes, does it damage the structure so sverely, that it can not be fixed later.
Typical questions:
- is the hierarchy required, could it be flat? (remember XML-namespaces? XML is simple, namespaces build on top of flat names.)
- do I really need the full implementation or is a solid API with a fake implementation behind good enough?
- does it really have to be a dynamic registry or could we hardwire the modules and just pretend to use a dynamic list?

Then, get rid of it.

4. Start Over

Short: Throw it away and do it again (applies only to small pieces).

Description: Sometimes we produce much code over time. Sometimes very quickly, because a problem has many special cases. Sometimes code accumulates slowly. Anyhow, the result is not understood anymore with all side effects. Each additional feature which must be added, can be a nightmare. Either for the coder who wants to guarantee that "the old mess + the new feature" is still working or for the user who gets random behaviour occasionally. All these are symptoms for a system that is out of control. The same applies to algorithms. This is about solving small problems, not about systems. In case of systems there is no easy way to redo everything. But algorithms and few pages of implementation can be recreated. A new implementation might even get rid of the ballast that was added, but is not used anymore.

Comment: Start Over is a valid last resort to comply to the holy principle of "understand what you are doing". You must completely understand what you are coding and what it does to the data and to users. If you do not understand what you are changing or trying to fix, then you should make it so that you understand, even if that means to write it again in your own words.

Application:
Select code, copy to temp file, press delete.
Kids: do not try this at home.

5. Be Quick

Short: Do not spend much time on the solution. Find a solution quickly.

Description: Do not spend more than 30 minutes on a single algorithm. Do not think too long about a solution. They do not pay you for thinking. They pay you for coding. Programmers spend their time writing I/O, error handling, initialization, synchronization, testing. There is no time to brood for hours, because there is so much else to to. Maybe you can find a solution instead of squeezing it out. A similar problem has already been solved. There is a library for it. There is a software pattern. There is a similar service, that must have had a similar problem. Find the similarity and find out how they have done it.

Application:
Generalize your problem. What is the core of it. What are you really doing. Abstract from your class names and marketing labels. Then try Google.

6. Problem Elimination

Short: Change the problem, if the solution is too difficult

Description: There are all kinds of difficult problems. Most can be reduced to a series of smaller problems. But some withstand reduction, because "everything depends on everything else" and "a small change makes a big difference somewhere else", especially if the "somewhere else" is the marketing department. If a problem has too many (or unclear) requirements, then it might end up in a complex solution after a long time that does not benefit anyone, especially not future coding performance. A complex solution is an indication for a difficult problem. The possibility to create a good code structure indicates a good problem. On the other hand, the lack of a cool code solution indicates lack of a good problem. The problem might not be understood. Simplify the problem. Check what you really need. Use external solution proposals as a description of the real problem, rather than an implementation guideline. Re-create the real problem and make up your own solution.

Comment: It might be necessary to convince the product owner that what she really wants is something different than she talked about.

Application:
Analyze proposed solutions.
Find the core of the problem or find a better problem.
Convince them to change the task.

_happy_coding()

Keine Kommentare: