Ich habe über die Jahre als Programmierer unterschiedliche Stile der Software-Entwicklung kennengelernt und selbst praktiziert. Gegenwärtig bin ich mehr für den Stil des schrittweisen Wachsens und einer Entwickeln aus einem Kern heraus.
Üben die Jahre hin habe ich die Erfahrung gemacht, dass Vorhersagen über die Zukunft sehr schwierig sind. Das allwissende Genie, dass ein Projekt am Anfang am Reißbrett plant und danach umsetzt, gibt es nicht. Dies ist zum einen eine naive Allmachtsfantasie, und zum anderen birgt es die Gefahr, dass man sich festfährt, weil man nur den Plan kennt und keine Veränderungen zulässt.
Meine Erfahrung ist vielmehr, dass man nur mit unverhältnismäßig hohem Aufwand und nicht sehr treffsicher vorhersagen kann, wie ein Projekt über die Zeit hin verlaufen wird. Die Ressourcen, die man bei der Planung versenkt, sollte man sich lieber für Korrekturen im späteren Verlauf bewahren. Statt am Anfang ein Projekt bis ins Detail festzunageln, sollte man mit der Einstellung, dass man während der Arbeit dazulernt, herangehen und sich die Freiheit bewahren, Änderungen am Projekt auch vorzunehmen.
Im Kern entspricht dies dem inkrementellen Entwicklungsstil der agilen Projektmethoden: Man setzt ein Ziel und steckt grob den Rahmen ab, aber die konkreten Details klärt man immer erst, kurz bevor sie umgesetzt werden. Uns Menschen fällt es nun einmal schwer, groß und weit in die Zukunft zu denken. Fragen der Umsetzung lassen sich leichter entscheiden, wenn man die konkreten Rahmenbedingungen vor sich liegen hat, als sie sich vorzustellen. Wenn ein Projekt nicht die fünfte Wiederholung ist und man auf Erfahrungen von vorangegangenen Projekten zurückgreifen kann, ist es daher besser, sich Schritt für Schritt voranzutasten und auch einfach Dinge auszuprobieren und Lösungen gegeneinander abzuwägen.
Für die Programmierung heißt das konkret, dass ich immer mit der main-Funktion beginne und dort mit ein wenig Pseudocode die groben Funktionen des Codes skizziere – dieses Stadium lässt sie nicht kompilieren, weil ich teilweise richtige Stichpunkte mit den Ideen formuliere. Wenn diese Programmskizze so grob steht, beginne ich, die Stichpunkt und Ideen zu konkretisieren und in lauffähigen Code zu überführen.
Dies passiert alles noch in der main-Funktion. Dabei merke ich dann schrittweise, wo es Redundanzen in der Struktur gibt und wo es sinnvoll ist, gewisse Codeteile in Funktionen oder Module auszugliedern, um zum Beispiel auch Möglichkeiten der Sichtbarkeit (public/private) zu nutzen.
Ein sehr guter Indikator für eine Funktion ist das kopieren von Code. Bei unterschiedlichen Fällen dupliziere ich oft den Code und passe ihn dann an die neuen Anforderungen an. Nach zwei oder drei solcher Kopien merkt man dann, wo die Gemeinsamkeiten und Unterschiede liegen und man kann daraus dann besser eine Funktion oder ein Modul gestalten. Man kann sich das Vorgehen ähnlich wie bei einem mathematischen Term vorstellen, bei der man aus a + b + c den gemeinsamen Anteil herausfaktorisiert: A * (x + y + z).
Auf diese Weise entwickle ich aus einer Funktion weitere Funktionen und führe diese dann zu Modulen fort. Innerhalb der neuen Funktionen bzw. Module wende ich das gleiche Prinzip an und lasse den Code wachsen und ziehe gemeinsame Teile heraus.
Dementsprechend entstehen Datentypen an der Stelle, wo sie verwendet werden und liegen nicht gleich auf globaler Ebene. Werden sie außerhalb des Moduls benötigt, kann ich sie mit public exportieren oder wenn mehrere Module damit inhaltlich verknüpft sind, hebe ich sie auf die kleinste gemeinsame Ebene, aber niemals sofort auf globale Ebene. Die gesamte Entwicklung verläuft mehr von unten nach oben (bottom-up).
Meine negative Erfahrung mit vorschneller und übermäßiger Abstraktion ist leider die, dass dadurch Fehler entstehen und der Code unübersichtlicher (komplexer) wird. Es gibt teilweise strenge Empfehlungen, wie Code und Daten zu kapseln sind, und wie alles schön in eigene Datenstrukturen zu verpacken ist. Aber mir ist dabei häufig begegnet, dass das Ergebnis weniger gut zusammenspielt und bis dahin dass es sich widerspricht und gerade dadurch zu Fehlern führt, weil man nicht sieht und kein Gespür dafür bekommen kann, wie die Funktion genutzt wird.
Ich halte auch Forderungen, dass Funktionen oder Dateien nur eine begrenzte Anzahl an Zeilen haben dürfen, für falsch. Wenn Code inhaltlich zusammengehört, sollte er nicht künstlich in Funktionen mit wohlklingenden Namen zergliedert werden. Das sind Vorstellungen aus Lehrbüchern, aber in der Praxis muss Code vielmehr stimmig sein. Wohingegen die Schachtelungs- und damit die Einrückungstiefe schon ein optischer Indikator für die inhaltliche Codestruktur seien kann.
Am Ende beachte ich meine eigenen Regeln auch nicht zu 100 %, sondern lasse auch den Spielraum für Abweichungen. Meine Idee des Ablaufs bietet mir eine gute Orientierung, aber ich will mich dadurch nicht einschränken, sollte es an einer Stelle auf andere Weise besser gehen. Es muss ein Blick fürs gesamte gewahrt werden, denn Programmieren hat mehr mit Kreativität, als mit mechanischer Produktion zu tun.