Einführung in die Gestaltung von Programmen für automatische Rechenanlagen im Rahmen der Lehre der Programmierung. [] (Softwaregestaltung, Software, Architektur, Entwurf, Softwareentwurf, Softwarebau, Softwarearchitektur), Lektion, Seite 722274
https://www.purl.org/stefan_ram/pub/programmgestaltung (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Programmgestaltung

(Diese Lektion enthält vorwiegend Stichworte und ist daher außerhalb einer Lehrveranstaltung nicht vollständig verständlich.)

Literatur: Code Complete, Refactoring von Fowler
By the age of 20, the elite performers had all totalled 10,000 hours of practice over the course of their lives. The merely good students had totalled, by contrast, 8,000 hours, and the future music teachers just over 4,000 hours.
Wiederverwendung gegen Abhängigkeiten
Lernhilfen (Norvig)

Regeln, Muster, Prinzipien und Empfehlungen zur Entwicklung

Literatur: Gamma et al; http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod; Applying Patterns, GRASP (Expert pattern)
Nicht alle dieser Prinzipien und Muster stellen Empfehlungen dar. Sie stellen Möglichkeiten dar, die in speziellen Fällen geprüft werden sollten. Oft wird bei einem Refaktor, mit dem ein Prinzip umgesetzt werden soll, ein Vorteil zu einem bestimmten Preis errungen. Es geht daher nicht darum, möglichst viele Muster einzusetzen, sondern nur darum sie zu kennen, um beurteilen zu können, wann ihr Einsatz vorteilhaft ist. In einem konkreten Einzelfall kann ein Verstoß gegen ein Prinzip, eine Regel oder eine Empfehlung genauso gut und richtig sein, wie deren Befolgung. Es gibt allerdings „harte“ Regeln (hier mit „++“ oder „+“ gekennzeichnet), die fast immer befolgt werden sollten, wie etwa das Gebot der Korrektheit oder Nützlichkeit eines Produktes, und subtiliere oder umstrittene Regeln („=“ für „neutral“ oder „fallabhängig“, „?“ für „umstritten“), von denen noch nicht ganz klar ist, ob sie (immer) vorteilhaft sind. Außerdem gibt es sogenannte „Gegenmuster“ (“anti patterns ”), das sind Muster, die vermieden werden sollten („-“ oder „--“).

Benennung (+)

Benannte Einheiten (Methoden, Klassen u.s.w.) sollten passend benannt werden.

Allerdings kann die “name paralysis ” manchmal Zeit kosten.

Dokumentation (+)

Benannte Einheiten (Methoden, Klassen u.s.w.) sollten gut dokumentiert werden.

Hohe Kohäsion (+)

Benannte Einheiten (Methoden, Klassen u.s.w.) sollten hohe Kohäsion, also eine einzige Aufgabe haben (einen Grund, sich verändern zu müssen)
Prinzip des „Spezialisten“. Man weiß sofort, wer zuständig ist oder an wen eine Informations weiterzugeben ist. Hohe Kohäsion bedeutet hohe Ordnung (aufgeräumter Quellcode hat hohe Kohäsion: Alles ist an seinem Platz und es ist klar, wo dieser Platz ist.)

Wenn der Name lautet „A und B“, dann kann die Kohäsion oft durch Zerlegung verbessert werden.

Geringe Kopplung (+)

Kopplung bedeutet: Wenn sich eines ändert, muß sich auch das andere ändern (Änderungs-Änderungs-Abhängigkeit).

Benannte Einheiten (Methoden, Klassen u.s.w.) sollten geringe Kopplung untereinander haben, damit eine geändert werden kann, ohne daß dies eine Kaskade weiterer Änderungen nötig macht.

Einheiten sollen sich an abstrakte Schnittstellen binden und nicht an konkrete Objekte.

Jedoch gilt: Schnittstellen haben ihren Preis (failure of SOA)

Geringe statische Kopplung (+)

Bei der statischen Kopplung wird eine bestimmte Beziehung zwischen Teilen des Quelltexts (beispielsweise zwischen Klassendeklarationen) zur Schreibzeit etabliert.
Bei der dynamischen Kopplung wird eine bestimmte Beziehung zwischen Objekte zur Laufzeit etabliert.
Wenn man fordert, „die Kopplung“ zu verringern, meint man meistens die statische Kopplung, da sie das Ändern eines Quelltextteils erschwert.

Möglichst kleiner Gültigkeitsbereich von Bezeichnern (+)

Ist übersichtlicher, da sofort klar wird, wo ein Bezeichner verwendet wird. Daher sollten „globalen Variablen“ und „globale Bezeichner“ vermieden werden. In seltenen Fällen kann hiervon abgewichen werden, wenn dies überwältigende Vorteile bietet oder ein Bezeicher tatsächlich überall in einem Programm verwendet können werden soll (dann ist der globale Gültigkeitsbereich sein kleinster möglicher Gültigkeitsbereich.)

Reifizieren (“Objectify”) (=)

Reifizieren einer Methode durch ein Objekt.

Nutzen Die Methode kann dann in mehrere Methoden zerlegt werden, wobei die geteilten Variablen nur einmal in dem Objekt gehalten werden müssen. (Wenn man eine Prozedur in mehrere kleinere Prozeduren zerlegt, dann sollten geteilten Variablen sonst als Argumente übergeben werden, weil man „globale Variablen“ vermeiden will. Eine „Methodenklasse“ bietet für solche Variablen den genau richtigen kleinstmöglichen Gültigkeitsbereicht).

Nutzen Durch solch ein Objekt kann eine darin enthaltene Methode auch als Argument übergeben werden.

Geringe Kopplung: Trennung von Schnittstellen und Implementation (=)

Erlaubt leichten Austauschon von Implementationen

Geringe Kopplung: Trennung von Schnittstellen (=)

Jeweils kleine Schnittstellen hoher Kohäsion benutzen.

Eine große Schnittstelle kann oft besser als Zusammenfassung mehrerer kleiner Schnittstellen dargestellt werden.

Geringe Kopplung: Offen-Geschlossen-Prinzip (+)

Einheiten sollten erweitert werden können, ohne verändert werden zu müssen.
(Beispiele: Einerseits Signaturen, wie »toString()«, andererseits Klassen durch Delegation/Vererbung)

Geringe Kopplung: Umkehrung der Abhängigkeit (dependency inversion) (=)

Von Schnittstellen abhängen, nicht von konkreten Implementationen: Die hierarchisch übergeordnete Klasse A (also die aufrufende Klasse) richtet sich nicht nach dem, was die von ihre benötigte Klasse B gerade anbietet, sondern definiert eine Schnittstelle C, die dann von anderen Klassen implementiert werden soll (abwärtsgerichteter Änderungsstrom)

Geringe Kopplung: Beobachter (“Observer”) (=)

Ein Objekt B meldet sich bei einem anderen Objekt M als „Beobachter“ an und erhält dann Nachrichten über bestimmte Ereignisse über eine Schnittstelle O. Daher muß M das konkrete Objekt B nicht kennen, sondern nur die Schnittstelle O.

Einfügen von Abhängigkeiten (dependency injection)(=)

Einfügen einer benötigten Implementation (Abhängigkeit) von außen, nachdem die Abhängigkeit schon umgekehrt wurde.

Nachteile Die Beziehungen zwischen verschiedenen Klassen und Objekten können teilweise nicht mehr an Hand ihres Quelltextes nachvollzogen werden, da sie in getrennten Quellen (teilweise in anderen Sprachen) festgelegt werden.

Konstanten nicht als Variablen deklarieren (+)

Erleichtert Verständnis. Verhindert Fehelr.

Strukturierte Programmierung (+)

Vermeiden von „Spaghetti-Code“. Die Prinzipien der strukturierten Programmierung gelten teilweise auch weiterhin in anderen Paradigmen wie in der objektorientierten Programmierung.

Gesetz von Demeter: „Sprich nicht mit Fremden!“ (+)

Ein Objekt, das Beziehungen mit anderen Objekten (Bekannten) hat, kommuniziert direkt nur mit diesen Objekten (Bekannte), aber nicht mit anderen Objekte (Bekannten von Bekannten), mit denen diese Objekte vielleicht Beziehungen haben.

So wird festgelegt, in welcher Klasse Methoden ihren Platz finden: nämlich dort wo alle von der Methode angesprochene Objekte als direkte Bekannte verfügbar sind. Teile der Methode in der sie Bekannte einer bekannten Klasse anspricht werden dann in diese Klasse als Methode ausgelagert (durch einen Refaktor).

Kontrakte (DbC, Design by Contract) (+)

Der Kontrakt einer Operation charakterisiert diese durch: benötigte Voraussetzungen (Vorbedingungen), gerantierte Nachbedingungen und Invarianten. Er sollte in der Dokumentation angegeben werden.

Korrekte Implementationen von Schnittstellen/Oberklassen (++)

Eine Implementation einer Schnittstelle oder einer Oberklasse muß deren Kontrakt erfüllen, indem jede Operation der Implementation den Kontrakt der entsprechenden Operation der Schnittstelle beziehungsweise Oberklasse erfüllt.

LSP, Liskov-Substitutions-Prinzip (?)

Das LSP ist ein bekannte, aber etwas weniger klare, Formulierung einer Anforderung, die in etwa die zuvorgenannte korrekte Implementation von Schnittstellen/Oberklassen verlangt.

Umkehrung der Aufrufrichtung (Inversion of Control, IOC) (=)

Programm registriert seine Teile (Plug-Ins, Applets, Servlets, hook methods, concrete strategy) bei einem Rahmen (Java, Eclipse, Netbeans, host, template method), der diese dann später aufruft, vgl.: Muster “Strategy”, Muster “Template Method”.

Dies ist dann vorteilhaft, wenn der Rahmen etwas darstellt, daß sich bei verschiedenen Anwendungen nicht ändert und die eingefügten Teile sich von Anwendung zu Anwendung ändern.

“Strategy” (“Hook Method”, “Template Method”) (=)

Eine mögliche Realisierung von IOC, bei der zur Laufzeit Teile („konkrete Strategien“), die eine Strategie-Schnittstelle implementieren in einen Rahmen („Kontext“) eingefügt werden.

Rahmen (“framework”) verwenden (=)

In vielen Fällen kann ein schon existierender Rahmen genutzt werden: (wie Eclipse, Netbeans, Servlet-Behälter, Applet-Behälter, u.s.w.)

Delegation statt Vererbung (=)

Bei der Delegation kann der Beauftragte (das Delegat) zur Laufzeit ausgetauscht werden. Es sind auch mehrere Beauftragte möglich. Es können Schnittstellen eingesetzt werden. Die Delegation ist in manchen Sprachen mehr Schreibarbeit als die Vererbung.

Delegation kann auch als dynamischere Variante der Vererbung aufgefaßt werden (Relation zwischen Objekten statt Klassen). Volldynamische Delegation wird aber nicht von allen Sprachen unterstützt

Delagation: Dekoratierer (“Decorator”) (=)

Ein Dekorierer delegiert die meisten Aufrufe zu einem Beauftragten. Die nichtdelegierten Aufrufe fügt er dem Beauftragten quasi hinzu, insofern dekoriert er diesen als eine Art von dynamischer Erweiterung.

Fabrikmethode (“Factory Method”) (=)

Eine Methode erzeugte bei jedem Aufruf ein neues Objekt. Solche Methoden können in manchen Fällen eingesetzt werden, in denen entsprechende Konstruktoren nicht möglich sind.

Fabrikobject (“Factory Object”) (=)

Ein Objekt, das eine Fabrikmethode reifiziert, ist ein Fabrikobjekt. Es kann etwa im Rahmen des Einfügen von Abhängigkeiten an Objekte übergeben werden, die damit benötigte Objekte erzeugen können.

Architekturen

Ein Architektur stellt den Aufbau eines (meist größeren) Programms im Großen dar. Hier werden nur einige Beispiele vorgestellt.

MVC (=)

Das Modell (“model”) enthält den Teil einer Anwendung, der von der Ein- und Ausgaben unabhängig ist.

Die Steuerung (“controller”) aktiviert die Model-Operationen entsprechend der Benutzereingaben.

Die Anzeige (“view”) stellt Informationen dar, sie erfährt als Beobachter, wann sie das Modell ändert.

Steuerung und Anzeige sollen nichts enthalten, das im Modell sein kann.

SOA (=)

Service-oriented architecture: Ein Programm wird in verschiedene Dienste zerlegt, die sogar von unterschiedlichen Organisationen bereitgestellt werden können. 2009 klingt einer Art von euphorischer Umstellung auf SOA ab, da die Kosten oft zu hoch sind.

Methodiken der Softwareentwicklung

Wahl eines Paradigmas

Verschiedene Programmiersprachen unterstützen verschiedene begriffliche Vorstellung der Programmierung (imperativ, prozedural, objekt-orientiert, funktional, logisch, …). Bestimmte Paradigmen passen unter Umständen gut zu bestimmten Aufgabenstellungen.

Einerseits soll die Programmiersprache möglichst gut zum Problem passen, andererseits möglichst gut zum Programmierer. (Wenn eine vermutlich „optimale“ Sprache für ein Problem gefunden wurde, nützt sie nichts, wenn der Programmierer mehr Zeit braucht, um sie zu erlernen als für das Projekt zur Verfügung steht, oder sie sein Spezialisierung verletzen würde oder es allgemein schwierig ist gute Werkzeuge und Entwickler für diese Sprache zu finden.)

Refaktorieren (++)

Literatur: Fowler

Quellcode erhalten, der die gewünschten Prinzipien und Muster

Planung mit Entwurfsprache (?)

„Entwurfssprachen“ (wie UML, Struktogramme, Programmablaufpläne)

Planung mit Implementationssprache (?)

Jack W. Reeves: Code as Design

Abwärtsgerichteter Aufbau (top-down) (=)

„Oben“ ist der Aufrufer, „Unten“ ist das Aufgerufene. Beim Abwärtsgerichteten Aufbau wird der obere Teil eines Programms zuerst geplant. Diese Planung bestimmte dann die darunterliegenden Programmteile.

(Beispiele mit Durchsuchen von Verzeichnissen)

Verwandt: Dependency Inversion Principle: Hauptklasse bindet sich nicht an eine konkrete fremdbestimmte Hilfsklasse, sondern an selbstbestimmte Schnittstelle

Vorteil: Das Ziel bestimmt den Aufbau des obersten Programmteils, dieser den Aufbau untergeordneter Programmteile – so soll es ja eigentlich auch sein

Nachteile: Tests und Nutzung sind schwieriger, solange noch nicht alles fertiggestellt ist. Vorhandene Bibliotheken können eventuelle nicht genutzt werden, wenn sie nicht zufällig so sind wie benötigt.

bottom-up (=)

(Beispiele mit Durchsuchen von Verzeichnissen)

Nachteil: Wenn noch nicht festgelegt wurde, was insgesamt eigentlich getan werden soll (oberer Programmteil), dann ist nicht klar, was eigentlich dafür benötigt wird (untere Programmteile)

Vorteile: Die beim „abwärtsgerichteten Aufbau“ beschriebenen Nachteile entfallen.

Klassische Individualsoftware

Erstellen eines Pflichtenheftes gemäß einer Systemanalyse (erster Vertrag), dann gegebenenfalls Umsetzung (zweiter Vertrag), Mehrere Durchläufe durch einen Zyklus aus Planung, Umsetzung (Schreiben von Programm und Handbuch), Erprobung und Fehlerbeseitigung, schließlich Auslieferung.

Agile Developement (?)

„schlank“ und „flexibel“, Individuen und Interaktionen gelten mehr als Prozesse und Werkzeuge; funktionierende Programme gelten mehr als ausführliche Dokumentation; die stetige Zusammenarbeit mit dem Kunden steht über Verträgen (Ein Mitarbeiter des Kunden sollte möglichst stets als Ansprechpartner bei den Entwicklern bleiben); die Offenheit für Änderungen stehen über dem Befolgen eines festgelegten Plans. (nach Wikipedia)

Literate Programming (?)

Ein Programm wird als ein lesbarer Text geschrieben in dem die Programmteile eingebettet sind wie Formeln in einem mathematischen Text. Durch Transformationen wird daraus entweder eine lesbare Programmbeschreibung erzeugt oder ein ausführbares Programm.

The solution is to maintain both a stable and an unstable branch, both of which can be installed, used, and developed, independently. Ideally the new unstable branch should be created after the previous unstable branch has become stable.

YAGNI

Warum ist es ein Fehler mehr  zu machen als verlangt wird?

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722274 stefan_ram:722274 Softwaregestaltung, Software, Architektur, Entwurf, Softwareentwurf, Softwarebau, Softwarearchitektur Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722274, slrprddef722274, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/programmgestaltung