Die Motivation etwas Neues zu entwickeln basiert in der Regel auf der Faulheit, Dinge umständlicher mit den vorhandenen Werkzeugen zu gestalten: Man wünscht sich Werkzeuge, die vorgegebene Probleme schneller lösen. Wirtschaftlich gesehen, geht es darum Produktionsprozess zu verkürzen, bzw. preiswerter und mit weniger Aufwand an das gewünschte Endprodukt zu gelangen. Beide Anforderungen sind gerechtfertigt.
1954 startete mit Fortran die Entwicklung neuer populärer Programmiersprachen, in dessen Kreis 2003 das jüngste Mitglied „C#“ stieß. Unzählige weitere Programmiersprachen finden sich im Internet verteilt. Im vorigen Kapitel zählte ich ja bereits die Mindestanforderungen auf, die eine Sprache aufweisen muss, um eine erfolgversprechende Zukunft aufzuweisen:
Diese Funktionalität findet sich teilweise bereits in einigen Sprachen wieder. Eine Sprache, die eine direkte Unterstützung für alle Punkte bietet, ist mir nicht bekannt. Mein Alltag beim Programmieren liefert jedoch weitere Wünsche, die mir vorhandene Programmiersprachen nicht erfüllen konnten. Jede Programmiersprache ist ein persönliches Statement, über die eigenen Ansichten, so entstand die Wunschliste nach meinen eigenen Bedürfnissen.
Hier die „Wunschliste“, mit den Features, die die neu zu definierende eine Sprache leisten sollte:
In meiner Umfrage[9] zum Thema Programmiersprachen fragte ich nach Wünschen von anderen Programmierern. Hier konnte ich mich absichern, dass die Auswahl von Erweiterungs-Themen die meisten unerfüllten Wünsche anderer Programmierer abdeckt.
Im folgenden Abschnitt werde ich auf Erweiterungen eingehen, die im Vergleich zu C++ erforderlich werden. Die Umsetzung aller Punkte findet im Kapitel „Definition Sprache“ statt. Auf die Beschreibung von bekannten, unveränderten Konstrukten, wie Funktionen oder Mehrfachvererbung werde ich in diesem Kapitel verzichten und verweise auf die Definition der Sprache, um mich dort nicht zu wiederholen. Selbiges gilt für Themen, wie Orthogonalität und Kombinationsfähigkeit, die bereits als Grundprinzip erklärt im vorherigen Kapitel besprochen wurden.
Die zu entwickelnde Sprache kombiniert viele spezialisierte Sprachen in eine einzige. Der gedankliche Ursprung der Sprache liegt in C++, jedoch sollen viele Fähigkeiten hinzugefügt werden für die die C++-Operatoren nicht ausgelegt wurden.
Es gilt zunächst eine Übersicht zu erhalten, welche Fähigkeiten benötigt werden, um die gewünschten Features zu realisieren.
Mathematische Operatoren verknüpfen ein oder zwei Operanden miteinander und berechnen aus beiden den Rückgabewert: das Ergebnis der Verknüpfung.
Hier sind zunächst die Grundrechenarten (Addition, Subtraktion, Multiplikation, Division, Negation) erforderlich. Ein Operator zum Berechnen von Potenzen scheint ein beliebtes Thema zu sein, er tauchte gelegentlich in der Umfrage als Wunsch auf und auch Bjarne Stroustrup diskutiert ausführlich die Möglichkeit eines ‚**’-Operators für C++[1, S.316-320], lehnt ihn jedoch ab, weil er zu Verwirrungen bei Referenzen führt. Ein alternativer ‚*^’-Operator wurde als beste Alternative empfunden [1, S.318], da der ^-Seperator war bereits durch die XOR Verknüpfung belegt war. Trotzdem wurde von der Einführung eines Potenzoperators abgesehen. Doch der Wunsch nach einem derartigen Operator ist da und auch Stroustrup bewusst gewesen, sonst wäre keine derart ausführliche Überlegung erforderlich gewesen.
Um Berechnungen durchzuführen eigenen sich jedoch auch Operatoren, die direkt auf Bitebene arbeiten: Schiebeoperatoren, Und-, Oder und exklusive Oderverknüpfungen, so wie die bitweise Negierung.
Mathematische Operatoren liefern einen Wert, zum Beispiel eine Zahl, zurück.
Im Kontext der Programmiersprache haben vergleichende Operatoren den gleichen Stellenwert, sie liefern jedoch keinen Wert zurück, sondern eine Wahrheitsaussage. Das ist im Kontext der Sprache ebenfalls ein Wert, eben true oder false. Je nach Typsicherheit der Sprache können Wahrheitswerte anschließend verrechnet werden (z.B. C++) oder müssen zuvor konvertiert werden (z.B. C#). Mit einem vergleichenden Operator trifft man eine Aussage und erhält als Ergebnis die Bestätigung oder die Verneinung.
Mit der Einführung von Listen und verwalteten Arrays sollte die Möglichkeit bestehen, diese zu nach Elementen vergleichend zu durchsuchen. Ausgehend von C++ existieren hier noch keine Operatoren.
Als Getter-Operator wirkt jeder Operator, der einen Wert nicht ausrechnet, sondern aus dem Speicher ausliest. Dies kann ein einfacher Variablenname sein, aber auch der lesende Zugriff auf ein Element einer Datenstruktur innerhalb einer Berechnung.
Üblicherweise verfügt jede Programmiersprache über einen Setter-Operator: den Zuweisungsoperator. Er beschreibt eine Variable oder ein Element in einer Datenstruktur.
Unter Metadaten versteht man Daten über Daten. Eine Zeiger-Variable auf ein Integer, speichert einen Pointer. Gleichzeitig besteht auch die Möglichkeit Daten über die gespeicherten Zeiger zu sammeln.
Ausgehend von C++, finden sich in diesem Bereich wenige Operatoren, da C++ sehr darauf bedacht ist, in hoher Geschwindigkeit ausgeführt zu werden. Die Erzeugung und Verwaltung von Metadaten bremst den Programmablauf, so das lediglich wenige statische Metadaten können ausgelesen werden. Hier sind Adresse, Dereferenzierungsoperator und sizeof-Operator zu nennen. Die Adresse, an der sich die Zeiger-Variable im Speicher befindet, den Wert auf den der Zeiger zeigt, die Größe einer Zeiger-Variablen.
Für eine dynamische Typverwaltung (RTTI) nach dem Vorbild von C# werden weitergehende Metadaten-Operatoren erforderlich. Um alle bekannten Informationen zu erhalten (Wahrheitswerte, Rückgabewert, Datentyp, Fehlerwerte) sind weitere Operatoren erforderlich.
Bei der Verwendung einer RTTI muss ein Datentyp existieren, der als Referenz auf andere Datentypen fungiert. Ein Datentyp ist entsprechend vom Datentyp „Datentyp“.
Ausgehend von C++ finden sich beide Datenstrukturen nicht in der Sprache wieder. C++ unterstützt ausschließlich Zeigervariablen. Ein Array wird dadurch realisiert, dass auf die Adresse der Variablen entsprechend der Größe des Datentyps aufaddiert werden kann und sich so die Adresse des gewünschten Elementes ergibt. Die Information, wie groß ein C++-Array ist, liegt nur statisch vor.
int a[4]; // 4 32 Bit-Variablen int b[4]; int *c = a; a[2] = 3; a[4] = 4711;
Liegt das Array a auf Adresse 1000 im Speicher, so ergibt sich hier der erste Zugriff auf Adresse 1000 + ( 2 * sizeof( int ) ). Die Zahl 3 wird damit in einer Breite von 32 Bit auf Adresse 1008 geschrieben.
Im zweiten Fall wird ebenso verfahren: 1000 + ( 4 * sizeof( int ) ) ergibt Adresse 1016, auf die die Zahl 4711 geschrieben wird. Das Array a endet jedoch bei Adresse 1015, mit Adresse 1016 beginnt voraussichtlich (je nach Implementation des Compilers) das Array b.
Ein verwaltetes Array würde diesen fehlerhaften Zugriff verhindern.
Ebenfalls sind besitzen verwaltete Arrays eindeutige Größen. Der Zeiger c im oben gezeigten Beispiel besitzt den gleichen Wert wie a und darf auch als Array verwendet werden. Hier existieren überhaupt keine Informationen mehr über die Größe der Array-Datenstruktur. Eine Funktion, die ein Array unbestimmter Länge erwartet, muss zusätzlich eine Information erhalten, wie groß das Array nun ist.
Bei Zeichenketten und Zeiger-Arrays löst man das Problem einfach, in dem das letzte Element des Arrays das Nullbyte beziehungsweise der Nullpointer ist. Bei Integerwerten ist das nicht möglich, ebenso wenn ein Array aus Zeigern auch Nullpointer enthalten darf.
Selbige Funktion sollte dynamisch, also als Liste, verfügbar sein. Hier können dynamisch Elemente hinzugefügt werden und entfernt werden.
Die Realisierung in C# als ArrayList arbeitet jedoch mit beliebigen Objekten, eine Festlegung auf bestimmte Datentypen ist nicht möglich und muss vom Programmierer eigenverantwortlich und ohne Kontrolle durch den Compiler durchgeführt werden.
Diese Typsicherheit sollte, entsprechend zu den Arrays, ebenfalls möglich sein.
Die eXtented Markup Language (XML) wird eine immer wichtigere Beschreibungssprache in der Informatik. Zur Beschreibung von Daten, von Konfigurationen oder in HTML spezialisiert auf das Layout von Webseiten.
Das in XML beschriebene Datenformat wird als Datenstruktur im sogenannten Data Object Modell (DOM) abgebildet.
Es ist zu erwarten, dass der Umgang mit diesen Datenstrukturen (Baumstruktur mit Namen und Attributen als Daten) also zukünftig alltäglich wird. Entsprechend der Wunsch, diese Struktur direkt in der Sprache anzubieten.
Einer der großen Vorteile von PHP und Perl sind die Möglichkeiten, sehr einfach mit Strings arbeiten zu können. Dies ist in C++ deutlich aufwendiger durch eigene Algorithmen zu realisieren. Strings gehören allerdings zum alltäglichen Bedarf bei der Programmierung, entsprechend sollte hier ein Datentyp existieren, der vergleichbar mit einem verwalteten Array ist und entsprechende Funktionalität liefert, um einfach und schnell Zeichenketten schneiden und zusammenführen zu können.
Iteratoren erlauben das Durchzählen von Datentypen, die mehrere Datenelemente verwalten, zum Beispiel Listen oder Arrays. Hierbei werden die Datenstrukturen von vorne nach hinten durchlaufen und auf jedes Element kann eine Operation durchgeführt werden.
Dies sollte auch für eigene Objekte gelten, wobei es möglich sein muss, eigene Iteratoren definieren zu können. So sollte eine eigene Klasse „Text“ beispielsweise zeilenweise oder wortweise durchlaufen werden können.
In C++ selbst werden Komponenten im Quelltext nur unterstützt, in dem der Präprozessor über den „#include“-Befehl den dazugehörenden Quelltext einbindet und mitkompiliert. Dies ist eine unter C++-Programmieren zu Recht verpönte Methode, da Quelltext so mehrfach in einem Programm auftauchen könnte; der „#include“-Befehl sollte nur bei Deklarationen verwendet werden und keine Klassendefinitionen einbinden. Damit enden die Möglichkeiten von C++ leider auch schon.
Unter Zuhilfenahme des Linkers können Komponenten verwendet werden, die später zum ausführbaren Programm gebunden werden. Über das Betriebsystem können Komponenten als Bibliothek angesprochen werden.
Hier muss der Entwickler für eine wichtige, alltägliche Gegebenheit den Sprachumfang verlassen und selber Quelltext schreiben, obwohl der Compiler ihm dies abnehmen kann. Für statisch gebundene Bibliotheken, muss er sogar das die Programmiersprache wechseln und ein Bash oder Make-Skript in einer anderen Syntax schreiben.
Hier gilt es eine einheitliche Lösung zu finden, wie dies in anderen Sprachen, wie Java oder PHP, ja bereits gelungen ist.
Regeln haben die einfache Aufgabe, den Zustand des Programms zu überwachen und nach einer Zuweisung auf eine überwachte Variable den gültigen Zustand zu sichern.
Dies ist grade für sicherheitskritische Anwendungen interessant, da kritische Zustandsänderungen des Programms an vielen Stellen des Programms stattfinden können.
Als Beispiel sei eine Ampelschaltung an einer Kreuzung genannt. Der Programmierer ist in der Lage jede Ampel einzeln grün bzw. rot zu schalten. Sicherheitshalber muss er vor jeder Grünschaltung prüfen, ob für die kreuzende Straße beide Ampeln rot geschaltet sind.
Findet sich ein Fehler im Programm, wo diese Prüfung versagt, könnten die Ampeln der gekreuzten Straßen ein Grünsignal geben.
Eine Regel, ist abhängig von einer oder mehreren Variablen.
Wird also eine der Ampeln geschaltet, kann die Regel überprüfen, ob sich die Ampelanlage in einem gültigen Zustand befindet und im Fehlerfall die Anlage abschalten oder zurücksetzen.
Regeln entsprechen Funktionen, die aber nicht vom Benutzer gerufen werden können. Der Compiler ruft nach der Modifikation einer überwachten Variable alle Regeln auf, die diese Variable überwachen.
Der Vorteil von Regeln ist, dass sie an einer einzigen Position auftauchen, schnell zu testen sind und vom Compiler gerufen werden, so dass die Prüfung vom Entwickler nicht vergessen werden kann.
Der Wechsel von Programmiersprachen erfolgt in der Regel dann, wenn eine nicht spezialisierte Programmiersprache, ein Problem zu aufwendig lösen muss. Eine nicht spezialisierte Programmiersprache sollte über die Möglichkeit verfügen, auch diese Anforderung zu meistern, in dem sie sich lernfähig gegenüber Spezialanforderungen zeigt. Dies betrifft zum einen den Quelltext, der zumindest teilweise in einer anderen Notation vorliegen kann (zum Beispiel einem durch eine Weiterentwicklung des Compilers inkompatibel geworden Quelltext) oder um sich bei häufigen Spezialfall mit weniger Quelltext ausdrücken zu können.
Selbiges gilt für das Backend des Compilers. Um den Compiler als einfach zu bedienbaren Cross-Compiler nutzen zu können, muss der Codegenerator austauschbar wie ein Plug-In bedient werden können.
Dies macht die Sprache auch für Anwender interessant, die eine spezielle Hardware verfügen können, für die nur eine geringe Auswahl von Sprachen mit passendem Codegenerator existieren, wie beispielsweise die selbstgebaute Computer oder die Lego-Mindstorm-Steuereinheiten.
Wer eine nicht unterstütze Hardware besitzt, kann die Unterstützung selber umsetzen.
Die Antworten in der Umfrage[9] erlauben einen Einblick in die Wahrnehmung vieler Programmierer. Viele Programmierer programmieren nicht mehr in einer Sprache, sondern in einer Entwicklungsumgebung. Daher entfällt auch eine nicht geringe Anzahl von Vorschlägen nicht auf die Sprache, sondern auf Verbesserungswünsche in Entwicklungsumgebungen. Dies zeigt, wie fahrlässig es wäre, die Entwicklungsumgebung bei der Entstehung einer Programmiersprache außer Acht zu lassen.
Integrierte Entwicklungsumgebungen(IDE) bieten neben den häufig eingeschränkteren Textverarbeitungsmöglichkeiten auch Vorteile, die einfache Texteditoren nicht bieten. Moderne IDE bieten eine Projektverwaltung an, leisten Hilfestellung bei der korrekten Schreibweise von Enumerations- und Methodennamen (von Microsoft IntelliSense genannt). Es existieren Wizards, die automatisch Programmteile erzeugen, es existieren Editoren für grafische Benutzeroberflächen, ein schneller Zugriff auf die Dokumentation, Zugriff auf die Versionsverwaltung und vermutlich am wichtigsten den vereinfachten Zugriff auf den Debugger.
Andererseits zeigt die Umfrage allerdings ein für mich überraschendes Fakt auf: Es scheint, als wären die zufriedensten Benutzer nicht diejenigen, die eine hochmoderne IDE verwenden, sondern die Zufriedenheit steigt mit der Leistungsfähigkeit des Texteditors.
Dazu folgen eine Reihe sonstiger Texteditoren und der häufige Hinweis, dass die IDEs überladen sind und nur schlechte Textverarbeitungsmöglichkeiten bieten.
Der Grund lässt sich bei einigen Teilnehmern zwischen den Zeilen finden: Editoren konzentrieren sich auf eine mindestens brauchbare Textverarbeitung, sind selten überladen, daher einfach zu bedienen und vor allem: sie funktionieren. Effektivität durch weniger Probleme bei der Bedienung der Software scheint sich häufig gegen den Versuch der Effektivitätssteigerung durch aufwendige Entwicklungsumgebungen durchzusetzen.
Beiden - IDEs und Editoren - ist jedoch in der Regel gemein, dass sie den Übersetzer als Unterprogramm ausführen oder überhaupt keinen Kontakt zum Übersetzer besitzen. Es gibt zwei Möglichkeiten, hier Schnittstellen zwischen Entwicklungsumgebung und Compiler zu schaffen.
Zum Ersten kann der der Compiler dem Editor eindeutige Beschreibungen über die Symbole liefern: Ist es eine deklarierte Variable, eine Funktion und welche Parameter besitzt sie? Dafür wäre zum einen erforderlich, dass der Parserbaum des Compilers nach dem kompilieren nicht gelöscht wird, sondern der Compiler auch nach der Erzeugung des Compilerproduktes im Speicher verbleibt. Über eine Schnittstelle kann der Editor nun Parameter einer Funktion auflisten, so dass der Entwickler ohne zusätzliche Suche in der Dokumentation die geforderten Argumente in richtiger Reihenfolge übergeben kann.
Der Editor kann seine Darstellung durch die exakte Bedeutung der Symbole optimieren und zum Beispiel farblich anpassen.
Eine Fehlerstelle, zum Beispiel ein nicht deklariertes Symbol, könnte rot hinterlegt werden und würde so dem Entwickler die Suche erleichtern. Der Editor kann den Datentyp jedes Symbols und jedes Operators erfragen und so genaue Hinweise geben, ab welcher Position ein Ausdruck nicht mehr den erwarteten Datentyp besitzt.
Der Entwickler kann also Soll und Ist direkt im Editor vergleichen, Fehler können markiert werden, Fehlerbeschreibungen können deutlicher erklärt werden, da der Editor Rückfragen an den Übersetzer weiterleiten kann.
Um die Darstellung zu optimieren benutzen gute Editoren und auch einige modernere Entwicklungsumgebungen Faltungen. Hierbei wird Quelltext, der einem zusammengehörenden Abschnitt in einem Algorithmus beschreibt, zu einer kurzen Erläuterung in einer einzigen Zeile gefaltet. Dies erhöht die Übersicht des Quelltextes bedeutend.
Faltungen werden in der Regel über verschlüsselte Symbole in Kommentaren begonnen bzw. beendet, zum Beispiel ist bei C++ möglich eine Faltung „/// Faltungsname“ beginnen zu lassen und durch „/*/*/“ wieder zu beenden. Dies ist kein Fähigkeit der Sprache C++, sondern C++ sieht nur Kommentare – der Editor interpretiert den Quelltext hier so, dass er Quelltext zwischen diesen beiden Marken falten darf.
Die einzige Sprache, die diese Fähigkeit in der Sprache aufgegriffen hat, ist C#. Dabei handelt es sich um die Präprozessoranweisungen „#region“, bzw. „#endregion“, die vom Compiler vermutlich wie Kommentare behandelt und ebenfalls ignoriert werden. Diese Anweisungen unterscheiden sich jedoch eindeutig von eventuell zufällig gleich formatierten Kommentaren bei C++.
Im Editor zeigt sich eine gefaltete Darstellung in etwa dieser Form:
if( databaseActive ) #region Datenbank abfragen { Quellcode(); } #endregion else Fehlermeldung und Benutzerdialog öffnen…
Einen derartigen Weg sollte die neue Sprache auch gehen, aber dabei darauf achten, dass der Entwickler die Darstellungsform frei wählen kann, da die Lösung, die in C# verwendet wird, wie alle Editoren ausschließlich zeilenweise arbeitet. Wünschenswert wäre die Möglichkeit Faltungen, auch zeichenweise formulieren zu können, um eine noch kleinere Darstellung zu ermöglichen. Hier mit einer möglichen Beschreibung:
if( databaseActive ) [*Datenbank abfragen: {
Quelltext();
} *] else Fehlermeldung und Benutzerdialog öffnen…
Wichtige Anlaufpunkte in der Sprache, wie Klassendeklarationen oder Funktionsdefinitionen sollten als suchbare Schlüsselwörter ausgedrückt werden.
Um Software zu dokumentieren, existieren Tools wie JavaDoc oder VisualStudio, die speziell markierte Kommentare aus dem Quelltext auslesen und zum Beispiel als HTML-Dokument speichern.
Alle mir bekannten Systeme lesen die Dokumentation aus Kommentaren heraus, es ergibt sich das gleiche Problem wie bei Faltungen, dass Kommentare eventuell falsch interpretiert werden. Daher sollten Dokumentationen ebenfalls durch eindeutige Befehle verwaltet werden, die der Compiler entweder ignorieren oder ggfs. verwerten kann. So kann der Compiler beispielsweise die Version oder den Autor einer Klasse aus der Dokumentation ausgegeben, so dass Informationen nicht redundant in Dokumentation und Quellcode geführt werden müssen. Beispiel:
doc Author „Sascha Atrops“; doc Version „1.0“; doc Id „main“ doc param argc „Anzahl der Argumente“ doc param argv „Array mit übergabeparametern“
Aufwendige Debugger erlauben das schrittweise Durchlaufen von laufenden Programmen. Debugger werden sicherlich beizeiten ebenfalls ein diplomtaugliches Thema, dass im Zusammenhang mit dieser Sprache zu klären ist. Bis dahin muss das bedingte Kompilieren ausreichen.
Das bedingte Einkompilierten von klaren Fragestellungen ist bei komplexen und rekursiven Programmen häufig einfacher, da man die Kontrolle, ob ein Fehler aufgetreten ist, programmieren kann und nicht bei jedem rekursiven Aufruf im Debugger selber prüfen muss. Ein einkompiliertes printf oder das Schreiben in eine Log-Datei gibt dann die Information an den Entwickler weiter.
Dies hat im Vergleich zu den aufwendigen Debuggern den Vorteil, dass die Kontrolle, ob die Debug-Information interessiert oder nicht, vom Computer übernommen wird. Die Fehlerposition wird in mehreren Tests nicht regelmäßig versehentlich übersprungen.
Solche Kontrollen werden häufig im Quelltext belassen und kann in C++ über den Präprozessor zugeschaltet oder abgeschaltet werden, so dass eine Release-Version frei von Debug-Ausgaben ist.
Da der Präprozessor als zweite Sprache über C++ im gleichen Quelltext mit C++-Anweisungen vermischt wird, enthält ein C++-Sourcecode eine Vermischung zweier unterschiedlich mächtigen Sprachen. Dies widerspricht jedoch dem Grundprinzip, das eine einheitliche Darstellung von ähnlichen Sachverhalten verlangt. Der Präprozessor ist damit in der Sprache nicht vertretbar.
Die bedingte Kompilierung sollte dennoch möglich bleiben. Da im Quelltext ein Abfrage gemacht werden muss, ob der jeweilige Part einkompiliert wird, sollte dies einer if-Verzweigung gleichen.
Die Verwendung von Makros wird seit C++ durch inline-Funktionen realisiert, die keine über keine unerwünschten Seiteneffekte verfügen:
#define MagicNumber 4711 #define myMacro( X ) \ printf( “Ausgabe: Dezimal %d: Hex: %x\n“, X, X ); long x = MagicNumber; myMacro( x++ );
wird durch den Präprozessor umgesetzt in:
long x = 4711; printf( “Ausgabe: Dezimal %d: Hex: %x\n“, x++, x++ );
Damit wird x zweimal inkrementiert, obwohl der Programmierer dies vermutlich nicht wünschte. MagicNumbers müssen ebenfalls nicht mehr über den Präprozessor realisiert werden und können durch konstante Variablen ersetzt werden:
int const MagicNumber = 4711 ; inline void myMacro( int x ) { printf( “Ausgabe: Dezimal %d: Hex: %x\n, x, x ); } long x = MagicNumber; myMacro( x++ );
Der Seiteneffekt, dass der vollständige Ausdruck x++ doppelt in den Quelltext eingefügt, entfällt bei dieser Lösung.
Das Einbinden von Dateien wird in Java durch die import-Anweisung realisiert, die direkt in Java realisiert wurde. Java verzichtet damit vollständig auf den Präprozessor, diesem Beispiel ist zu folgen.
Das ‚#’ Zeichen, das Präprozessorbefehle einleitet und als Operator für den Präprozessor dient (‚#’ und ‚##’), wird damit unbenutzt und kann als Operator in der normalen Sprache verwendet werden.
Es gibt keine Vermischung von unterschiedlichen Sprachen mehr innerhalb eines Sourcecodes.
Werden Schleifen verschachtelt, ergibt sich das Problem, dass man den äußeren Schleifen über break oder continue keine Anweisungen mehr geben kann.
while( true ) while( true ) break;
Aus diesem Grund sollten Schleifen und Verzweigungen benannt werden können.
Dieser Name sollte ebenfalls wie eine Variable als Wert der Bedingung verwendet werden können.
Eine besondere Form einer Komponente stellt das Streaming dar. Das Betriebsystem Linux baut in großen Teilen auf das Streamen von Daten. Beim Auslesen von Dateien, Webseiten oder Datenbanken handelt es sich um das Senden von Anfragen und Empfangen von Antworten, bzw. umgekehrt als Empfangen von Anfragen und Senden von Antworten.
Der Unterschied liegt im Protokoll: Dateioperation, http oder das Protokoll des jeweiligen Datenbank-Servers.
Dieser Austausch von Daten sollte über einen eindeutigen Datentyp geregelt werden, der in einem für die Programmausführung erreichbaren Verzeichnis Protokoll-Komponenten lagert. Damit braucht man nur noch Protokoll und Verbindungsdaten anzugeben, zum Beispiel eine URL, und der Compiler automatisiert diese Anfrage an das richtige Protokoll-Plugin, dass dann die Anfrage beantworten kann.
Eine häufige Fehlerquelle sind Algorithmen, die durch Copy’n’Paste programmiert wurden und Algorithmen die in gleicher oder ähnlicher Form mehrfach im Quelltext vorliegen. Übereinstimmende Teile sollten nach Möglichkeit so ausgedrückt werden, dass eine Wiederholung von Quelltexten nicht erforderlich ist.
Nicht alle Fälle werden können von gängigen Programmiersprachen aufgefangen werden. Diese müssen identifiziert werden und durch passende Anweisungen abgefangen werden.
Rechtevergabe bei Klassen Große Projekte verteilen sich auf viele Klassen, eventuell auf mehrere Frameworks. Dabei enthalten Klassen teils so viele Memberfunktionen, dass die Unterscheidungen über public, private und protected nicht mehr ausreicht. Eine Methode, die sich auf die Kommunikation mit einer bestimmten anderen Klasse A spezialisiert hat, kann sich nicht vor Kommunikationsversuchen mit einer dritten Klasse B schützen. Wird Klasse A als ‚friend class’ deklariert, darf die vollständige Klasse auf die Methode zugreifen. Hier fehlen Möglichkeiten, den Nachrichtenfluss zwischen den Klassen kontrollieren zu können.
Eine beliebte Forderung an Compiler sei jedoch egalisiert: Die Portierbarkeit des vollständigen Compilers soll keine Berücksichtigung bei der Implementierung finden.
Für die Portierung eines Compilers wird mit Compiler der Arbeitsplattform benötigt und ein Compiler, der Maschinensprache für die Zielplattform erstellt. Der Compiler mit dem Codegenerator für die Zielplattform kann mit dem Compiler für die Arbeitsplattform kompiliert werden. Anschließend kann der Compiler für die Zielplattform den eigenen Quelltext kompilieren. Das Ergebnis ist ein Compiler für die Zielplattform mit Codegenerator für die Zielplattform. Hierbei wurde lediglich der Codegenerator des Compilers neu geschrieben. Diese Vorgehensweise nennt man „BootStrapping“.
Es geht nicht darum, dass eine Sprache entsteht, die leicht durch Neuimplementierung portierbar ist. Wichtig ist eine Sprache, auf dem aktuellen System arbeitet. Dem Wunsch nach schneller Portierung ist durch die Erstellung von Codegeneratoren entsprochen, ohne dass der eigentliche Quelltext des Compilers angefasst werden müsste. Die Entwicklung eines neuen Codegenerators für das Zielsystem kann auf einem bereits bekannten System geschehen, der Compiler anschließend durch seine eigene Erweiterung kompiliert werden. Das Vorgehen entspricht dem üblichen Bootstrapping. Die Neuimplementierung einer Sprache wird von daher vermutlich nach der ersten Version nicht mehr erforderlich sein.