- Seiteneffekt
-
In der Programmierung bezeichnet Wirkung (engl. effect) die Veränderung des Zustands, in dem sich ein Computersystem befindet. Beispiele sind das Verändern von Inhalten des Speichers oder die Ausgabe eines Textes auf Bildschirm oder Drucker.
Da solche Wirkungen in den meisten Programmiersprachen etwas ganz Gewöhnliches darstellen, wird ihr Vorhandensein außerhalb der theoretischen Informatik meist nicht weiter thematisiert. Der Begriff wird vor allem dort gebraucht, wo Wirkungen entweder ganz fehlen oder wo die Abgrenzung verschiedenartiger Wirkungen voneinander betont werden soll. Letzteres wird „Nebenwirkung“, häufig auch „Nebeneffekt“ genannt. Auch die Bezeichnung „Seiteneffekt“, eine falsche Rückübersetzung des englischen side effect, ist weit verbreitet.
Inhaltsverzeichnis
Sprachliche Aspekte
Da der Computer als streng deterministische Maschine immer nur genau das tun kann, wofür er programmiert wurde, wäre eine Unterscheidung zwischen Haupt- und Nebenwirkung eigentlich nicht zulässig. Die konkrete Definition ist daher vom Kontext abhängig. Zum Teil wird auf eine Unterscheidung bewusst verzichtet und die Begriffe synonym verwendet.
Die Bezeichnung „Nebenwirkung“ legt nahe, dass eine Wirkung unbeabsichtigt, schädlich oder eben nebensächlich sei, was aber meist irreführend ist, da die Wirkung einer Operation oft deren beabsichtigte „Hauptsache“ ist. Daher ist meist das Wort „Wirkung“ alleine zu bevorzugen.
Spezifizierte Wirkungen
→ Hauptartikel: Operationelle Semantik
Man kann sich nur auf die Wirkungen einer Operation verlassen, die im Handbuch eines Prozessors oder der Spezifikation einer Programmiersprache auch für diese Operation zugesichert wurden. Solch eine Wirkung wird daher auch als eine spezifizierte Wirkung bezeichnet. Bei einer Instruktion wie "PRINT 10" ist spezifiziert, dass "10" am Bildschirm ausgegeben werden soll, jede andere Wirkung (z. B. die Zehn über das Netzwerk zu übertragen) ist unerwünscht und würde als ein Fehlverhalten des Systems gewertet werden.
Die gängigen imperativen Programmiersprachen wie zum Beispiel C++ oder Java verwenden Variablen als wichtiges Ausdrucksmittel der Sprache. Eine Variable repräsentiert zu jedem Zeitpunkt des Programmablaufes einen ganz bestimmten Wert. Die Gesamtheit aller Variablen und ihrer Werte definiert den Programmzustand. Operationen werden immer auf einem solchen Zustand ausgeführt und können ihn verändern. Eine solche Zustandsänderung wird Nebenwirkung[1][2] genannt (engl. side effect).
Eine strenge Unterscheidung zwischen Nebenwirkung und Wirkung findet im Rahmen dieser Definition nicht statt – die Begriffe werden häufig sogar synonym verwendet. Die Bezeichnung als Nebenwirkung ist hier so zu verstehen, dass die „hauptsächliche“ Wirkung einer Operation beispielsweise in der Errechnung der Summe zweier Zahlen besteht, diese Zahl wird auch als das „Ergebnis“ oder als „Wert“ der Operation bezeichnet. Die „nebensächliche“ Wirkung besteht darin, das Ergebnis dieser Berechnung in einer Variable abzulegen.
Von diesen wichtigen spezifizierten Wirkung zu unterscheiden sind die meist weniger relevanten und weniger regelhaften unspezifizierten Wirkungen: So kann auch die Auswertung eines Ausdrucks ohne spezifizierte Wirkung unspezifizierte Wirkungen haben, wie beispielsweise die Erwärmung des Prozessors oder das Verstreichen einer bestimmten Zeitdauer (beides lässt sich meist nicht verhindern). Solche Wirkungen kann man tatsächlich auch als Nebenwirkungen bezeichnen, da sie für den Sinn einer Operation oft irrelevant sind. In der theoretischen Informatik spielen diese Effekte keine Rolle und werden entsprechend auch nicht berücksichtigt.
Paradigmen der Programmierung
In der Maschinensprache sind für alle Operationen die Wirkungen genau spezifiziert. So wird für jede Operation genau angegeben, ob und welche Statusregister anschließend einen neuen Wert haben, neben dem neuen Wert für das Zielregister (der Rückgabewert). Hier ist die Bezeichnung als „Seiteneffekt“ noch am einfachsten nachzuvollziehen, denn es ist auf den ersten Blick nicht ersichtlich, weshalb der Prozessor sich bei einer Operation wie
add eax,2
(addiere zum Register eax den Wert 2) im Statusregister − das in der Anweisung nirgends erwähnt wird − merkt, dass das Ergebnis ungleich null, positiv und gerade war (gegeben eax war vorher Null), was eine nachfolgende Anweisung indirekt beeinflussen kann.In den meisten prozeduralen und objektorientierten Programmiersprachen kann die Auswertung eines Ausdrucks eine spezifizierte Wirkung haben. Die Wirkungen werden hier üblicherweise abstrakter angegeben als bei Maschinensprachen, da einzelne Instruktionen sehr viel mächtiger sein können. Auch ein Funktionsaufruf kann Teil einer Operation sein, die einen Wert zurückgibt. Eine Methode ist eine Funktion, die keinen Wert hat. Beide, Funktionen und Methoden, können den Zustand des Systems verändern − Methoden werden das immer tun, weil wirkungsfreie Methoden keinen Zweck erfüllen.
In reinen funktionalen Sprachen hat die Auswertung eines Ausdrucks niemals eine spezifizierte Wirkung. Es gibt in solchen Sprachen keine Anweisungen im prozeduralen Sinn, sondern nur Ausdrücke. Eine eventuelle Wirkung (Ausgabe eines Ergebnisses) erhalten sie erst durch Unterstützung vom Laufzeitsystem.
Die Werte von Ausdrücken sind oft die Gegenstücke zu Wirkungen von Operationen, da man je nach dem Programmierstil ein Programm entweder durch eine Folge von Operationen (wie in der Maschinensprache) oder durch eine Verschachtelung von Ausdrücken (wie in der reinen funktionalen Programmierung) ausdrücken kann. In den meisten prozeduralen und objektorientierten Programmiersprachen geschieht beides gemischt.
Verwirrend kann sein, dass in prozeduralen Programmiersprachen die Wirkung einer Operation häufig wichtiger ist als ihr Wert. Eine Operation wie
a=3+4
hat den Wert 7 aber die Wirkung, diesen Wert der Variablen a zuzuweisen. Der Wert des Ausdrucks wird hier gar nicht gebraucht und sofort verworfen. (Anders sieht es bei Instruktionen der Arta=b=0
aus, wo der Wert der zweiten Anweisung tatsächlich nocheinmal verwendet wird.)(Neben)wirkung in der prozedurorientierten Programmierung
In der prozedurorientierten Programmierung wird ein Algorithmus in Module, sogenannte Prozeduren zerlegt. Diese werden als „schwarze Kästen“ Black Box) betrachtet, von denen nach Außen hin lediglich ihre Schnittstelle bekannt ist.
Eine Nebenwirkung wird in diesem Zusammenhang als Wirkung definiert, die nicht in der reinen Schnittstellenbeschreibung der Prozedur spezifiziert ist.[1] Typischerweise ist das der Fall, wenn eine Prozedur auf globale Variablen oder andere Speicherbereiche außerhalb des Schnittstelle entweder zugreift oder diese sogar verändert.
Prozeduren, die über eine „saubere“ Schnittstelle in diesem Sinne verfügen und ihre Umgebung unangetastet lassen, werden dementsprechend seiten- oder nebeneffektfrei genannt.
Die meisten Funktionen, die von Programmierbibliotheken in Hochsprachen angeboten werden, haben eine Wirkung, diese sollte jedoch genau spezifiziert sein, damit die Zustandsänderung nachvollziehbar bleibt. In manchen Fällen können, trotz eindeutiger Spezifikation, diese Wirkungen zu Programmierfehlern führen, weil die Wertigkeiten und Wirkungen verschiedener Operationen je nach Programmiersprache und Compiler verschieden oder sogar undefiniert sein können. Gewisse Programmoptimierungen können dazu führen, dass sich ein Programm im Entwicklungsmodus anders verhält als mit aktivierten Optimierungen.
Zur Gruppe der Funktionen mit Wirkung gehören alle, die mit der Ein- oder Ausgabe von Daten zu tun haben oder bewusst auf eine Hardwarekomponente zugreifen. Wirkungsfreie Operationen in Hochsprachen sind etwa die mathematischen Basisfunktionen wie Sinus, Kosinus oder Quadratwurzel.
Beispiel
Das folgende, vereinfacht dargestellte C#-Beispielprogramm enthält eine Prozedur, deren Schnittstelle mit dem Namen „FunktionMitWirkung“ sowie einem ganzzahligen (Integer) Eingabe- und einem ebenfalls ganzzahligen Ausgabeobjekt vollständig beschrieben ist.
int globale_variable = 2; int FunktionMitWirkung(int argument) //Schnittstelle der Prozedur { globale_variable = globale_variable + argument; return globale_variable; //Zugriff auf Variable außerhalb der Schnittstelle } int FunktionOhneWirkung(int argument) //Schnittstelle dieser Prozedur { return argument+10; } Console.WriteLine(FunktionMitWirkung(4)); //Erster Aufruf der Prozedur Console.WriteLine(FunktionMitWirkung(4)); //Zweiter Aufruf der Prozedur Console.WriteLine(FunktionOhneWirkung(4)); //Aufruf der Wirkungsfreien Prozedur Console.WriteLine(FunktionOhneWirkung(4)); //Zweiter Aufruf der Wirkungsfreien Prozedur // Dieses Programm erzeugt folgende Ausgabe: 6 10 14 14
Die erste Prozedur berechnet beim ersten Aufruf den erwarteten Wert 6 und liefert ihn als Ergebnis zurück. Gleichzeitig wird durch eine (im gezeigten Beispiel ungewollte) Nebenwirkung der Wert der globalen Variablen verändert, die nicht Teil der Schnittstellenbeschreibung ist. Der zweite, eigentlich identische Aufruf liefert einen anderen Wert zurück, in diesem Fall 10. Die Prozedur ist „seiteneffektbehaftet“. Die zweite Funktion ist wirkungsfrei. Egal wie oft sie aufgerufen wird, ihre Ausgabe hängt lediglich von der Eingabe ab.
Wirkungsfreiheit in der rein funktionalen Programmierung
In der rein funktionalen Programmierung wird – anders als in den imperativen Sprachen wie C++ oder Java – das Ergebnis eines Algorithmus allein mit Ausdrücken und Funktionen beschrieben. Es werden keine Variablen und keine Wertezuweisungen verwendet. In der rein funktionalen Programmierung treten deshalb keine Zustandsänderungen im Sinne der obigen Definition und somit auch keine Nebenwirkungen (side effects) auf.[1] Die Programmiersprache ist zustandslos und seiteneffektfrei.[2]
Diese Eigenschaft einer Programmiersprache wird als referenzielle Transparenz bezeichnet. Sie besagt, dass der Wert eines Ausdrucks nur von seiner Umgebung abhängt und nicht vom Zeitpunkt oder einer bestimmten Reihenfolge der Auswertung.[3] Das Fehlen von Nebenwirkungen dieser Art wird entsprechend „Seiteneffektfreiheit“[2], gelegentlich auch „Wirkungsfreiheit“ genannt. Beispiele für gänzlich wirkungsfreie Sprachen sind die rein funktionale Programmiersprache Haskell oder reines (pure) Lisp.
In anderen funktionalen Programmiersprachen wie etwa Scheme können Prozeduraufrufe die Werte von Variablen verändern oder Bildschirmausgaben auslösen. Diese Seiteneffekte sind ein wichtiges Ausdrucksmittel der Sprache, die referenzielle Transparenz geht jedoch verloren und Scheme ist damit keine rein funktionale Sprache mehr.[3] Um diese Eigenschaft in der für Lehrzwecke eingesetzten Sprache Scheme hervorzuheben, werden seiteneffektbehaftete Prozeduren mit einem Ausrufezeichen gekennzeichnet, zum Beispiel in der Variablenzuweisung
(set! a 2)
.Beispiel
Das folgende, mit seinen Klammern und der Präfixnotation für eine Sprache wie Lisp oder Scheme typische Beispielprogramm liefert abhängig von einer Bedingung eines von zwei möglichen Berechnungsergebnissen zurück.
(if (= a 0) (+ a 1) (* a 2))
Die Zuweisung des Ergebnisses zu einer Variable findet nicht statt. Insbesondere aber hat die Reihenfolge der Auswertung der einzelnen Funktionen (if, =, + und *) keinerlei Einfluss auf das Ergebnis. Jeder Ausdruck kann an jeder Stelle durch seinen Wert ersetzt werden. Das ist die referenzielle Transparenz.
Belege zu Begriffen und Aussagen
Belege zur Definition des Begriffs „Wirkung“
Die Bedeutung des Begriffs „Wirkung“ in der Informatik stimmt im allgemeinen mit der Bedeutung dieses Wortes in der Umgangssprache überein (wobei man aber stillschweigend immer nur eine spezifizierte Wirkung meint). In der Umgangssprache sind die Wirkungen eines Vorgangs ja die von diesem hervorgerufenen Veränderungen, also seine Folgen. Etwaige Definitionen in der Literatur der Informatik geben dies nur manchmal etwas formaler oder auf gerade relevante Spezialfälle eingeschränkt wieder. Oft wird der Begriff aber ohne ausdrückliche Definition verwendet, wenn die Bedeutung des Wortes gemeint ist, die es auch im Alltag hat.
- „Wenn ZP also die Menge aller möglichen Zustände ist, die bei der Ausführung eines Programms P durchlaufen werden können (formal: ZP ⊂ W(v₁) × W(v₂) × … × W(vn), wobei v₁, …, vn die in P auftretenden Variablen und W(vi) der Wertebereich von vi, d.h. die Menge aller Werte, die vi annehmen kann, sind), dann ist die Wirkung einer Anweisung a ∈ P eine Abbildung F [a] : ZP → ZP, die einem Zustand z ∈ ZP einen Folgezustand z' ∈ ZP zuordnet. Beispiel: Sei V = {x, y} und P = "…; x ≔ y + 1; …". Falls vor Erreichen der Zuweisung der Zustand z = (x = ⊥, y = 5) gilt, gilt nach der Anweisung der Zustand z' = (x = 6, y = 5). Formal: F[x ≔ y + 1] (⊥, 5) = (6, 5).“ [4]
Belege zur Verwendung des Begriffs „Wirkung“
- „Beschreibung der Wirkung, die Anweisungen auf Zustände (=Variablenbelegungen) haben. [...] Dann ist die Wirkung der Anweisung m := n + 1 wie folgt [...]“ [5].
- „Zu jeder Operation sind folgende Angaben zu machen: [...] eine Beschreibung der Wirkung der Operation“ [6].
- Auch in der englischsprachigen Fachliteratur der Informatik wird der Begriff „Wirkung“ (auf englisch „effect“, also nicht etwa nur „Nebenwirkung“, auf englisch „side effect“) oft verwendet. Beispielsweise findet man in der Norm der Programmiersprache C++ [7] Verwendungen wie: „the effect of dereferencing the null pointer“ („Die Wirkung einer Dereferenzierung des Nullzeigers“). Der Begriff wird zur Beschreibung der Wirkung von Anweisungen verwendet, wie in „A return statement in main has the effect of leaving the main function“ („Eine return-Anweisung in main hat die Wirkung des Verlassens der main-Funktion“). Er wird an einer Stelle für Funktionen ausdrücklich definiert: „Effects: the actions performed by the function“ („Wirkung: die von der Funktion durchgeführten Aktionen“). Danach ist bei der Beschreibung jeder Funktion ein Abschnitt mit „Effects:“ („Wirkungen:“) etikettiert, der die Wirkungen der beschriebenen Funktion angibt, wie in „atexit … Effects: The atexit() functions register the function pointed to by f ….“ („atexit … Wirkungen: Die Funktion atexit() registriert die Funktion, auf die f zeigt ….“).
Quellen
- ↑ a b c P. Rechenberg, G. Pomberger: Informatik-Handbuch. Carl Hanser Verlag, München/Wien 2006, ISBN 978-3-446-40185-3.
- ↑ a b c C. Wagenknecht: Programmierparadigmen. Teubner, Wiesbaden 2004, ISBN 978-3-519-00512-4.
- ↑ a b C. Schiedermeier: Funktionales Programmieren. Nürnberg 2002.
- ↑ Hans-Jürgen Appelrath und Jochen Ludewig, Skriptum Informatik - eine konventionelle Einführung, 1999-02-11, Abgerufen 2008-08-10.
- ↑ Prof. Dr. Alois Knoll, Einführung in die Informatik 2 (PDF), Sommersemester 2006, Seite 18. Abgerufen 2008-08-09.
- ↑ Udo Kelter, [http://pi.informatik.uni-siegen.de/musoft/lm/lm_ood_20031004_a5.ps.gz Objektorientierter Entwurf (ps.gz)], Universität Siegen, 2003, Seite 11. Abgerufen 2008-08-09.
- ↑ ISO/IEC 14882:2003(E)
Wikimedia Foundation.