051.1 Lektion 1
Zertifikat: |
Open Source Essentials |
---|---|
Version: |
1.0 |
Thema: |
051 Software-Grundlagen |
Lernziel: |
051.1 Softwarekomponenten |
Lektion: |
1 von 1 |
Einführung
Freie und Open Source Software, oft als FOSS abgekürzt, sind zu einem integralen Bestandteil unseres Alltags geworden, meist ohne dass wir uns dessen bewusst sind. FOSS steckt zum Beispiel in irgendeiner Form hinter all unseren Aktivitäten im Internet: auf dem Computer, auf dem wir Webseiten im Browser betrachten, oder auf Servern, die diese Webseiten speichern und ausliefern, sobald wir sie aufrufen.
Was ist Software?
Bevor wir auf die Besonderheiten von freier und quelloffener Software eingehen, müssen wir klären, was Software eigentlich ist. Beginnen wir mit einer sehr allgemeinen Beschreibung: Software ist der nicht-physische, immaterielle Teil von Computern in jeder Form. Software sorgt dafür, dass die physischen Teile (die Hardware) des Computers zusammenwirken und dass der Computer Befehle annehmen und Aufgaben ausführen kann.
Ein Smartphone, ein Laptop oder ein Server im Rechenzentrum ist im ausgeschalteten Zustand nur eine Maschine aus Metall und Kunststoff. Sobald sie eingeschaltet wird, startet Software in Form von kodierten Befehlssequenzen, die die einzelnen Komponenten dieser Maschine steuern. Das macht es dem Benutzer möglich, mit der Maschine zu interagieren und durch den Aufruf einzelner Anwendungen bestimmte Aufgaben erfüllen.
Es ist die Arbeit der Softwareentwickler, die Aufgaben zu analysieren, die der Computer erfüllen soll, und sie so zu spezifizieren, dass der Computer sie ausführen kann. Die von Entwicklern verwendeten Werkzeuge sind so zahlreich und vielfältig wie die Aufgaben, die die Software erfüllt.
Einige Aufgaben von Software sind sehr eng mit der Hardware und der Architektur des Rechners verbunden, z.B. die Adressierung und Verwaltung des Speichers oder die Handhabung verschiedener Prozesse. Systemprogrammierer arbeiten daher nahe an der Hardware.
Anwendungsentwickler hingegen haben mehr die Benutzer im Blick und programmieren Anwendungen, mit denen diese ihre Aufgaben sowohl effizient als auch intuitiv erledigen. Ein Beispiel für eine komplexe Anwendung ist ein Textverarbeitungsprogramm, das alle Funktionen zur Textformatierung in Menüs oder Schaltflächen bereitstellt und den Text auch so anzeigt, wie er zuletzt gedruckt werden könnte.
Allgemein ist ein Algorithmus ein definierter Weg, ein Problem zu lösen. Um beispielsweise einen Durchschnitt zu berechnen, besteht der übliche Algorithmus darin, mehrere Werte zu addieren und die Summe durch die Gesamtzahl der Werte zu dividieren. Obwohl Algorithmen traditionell von Programmierern entworfen und ausgeführt werden, werden Algorithmen heute auch von künstlicher Intelligenz erzeugt.
Die Konzepte in dieser Lektion helfen Ihnen, Stärken und Risiken von FOSS zu verstehen, fundierte Entscheidungen zu treffen und sogar einzuschätzen, ob Sie Software entwickeln wollen.
Programmiersprachen
Programmiersprachen sind hochgradig strukturierte, künstliche Sprachen, die dem Computer sagen, was er zu tun hat. Programme werden in der Regel in Textform geschrieben, aber einige Sprachen auch in grafischer Form. Softwareentwickler schreiben Anweisungen (den Code) für den Computer in dieser künstlichen Sprache. Die Computerhardware kann diesen Code jedoch nicht unmittelbar, sondern nur eine Reihe von Bitmustern ausführen, die sich im Speicher befinden und Maschinencode oder Maschinensprache genannt werden. Alle Programmiersprachen werden entweder von einem Compiler in Maschinencode umgewandelt oder von einem anderen Maschinencodeprogramm, dem Interpreter, ausgewertet, damit die Hardware die Anweisungen ausführen kann.
Zu den derzeit am weitesten verbreiteten Programmiersprachen gehören Python, JavaScript, C, C++, Java, C#, Swift und PHP. Jede dieser Programmiersprachen hat ihre eigenen Stärken und Schwächen, und die Wahl der Sprache hängt vom Projekt und den Bedürfnissen der Entwickler ab. So ist Java beispielsweise eine beliebte Wahl für die Entwicklung großer Unternehmensanwendungen, während Python häufig für wissenschaftliche Berechnungen und Datenanalysen verwendet wird.
Entwickler haben bei der Entwicklung von Programmiersprachen eine beeindruckende Kreativität an den Tag gelegt. Ursprünglich handelte es sich um low-level (hardwarenahe) Sprachen, die den Anweisungen im Computer ähnelten. Sie wurden immer mehr zu höheren Programmiersprachen, d.h. sie versuchen, mächtige Kombinationen von Anweisungen in kurzen Begriffen darzustellen. Einige Sprachen spiegeln die Art und Weise wider, wie Menschen natürlicherweise denken, und bewahren gleichzeitig die für eine korrekte Ausführung erforderliche Stringenz.
Gegenwärtig sind etwa 400 Programmiersprachen bekannt, von denen viele jedoch nur in Nischenanwendungen oder Legacy-Umgebungen zum Einsatz kommen. Jede wurde entwickelt, um bestimmte Aufgaben zu lösen.
Merkmale, Syntax und Struktur von Programmiersprachen
Die Wahl der Programmiersprache hat erheblichen Einfluss auf Leistung, Skalierbarkeit und Einfachheit der Entwicklung eines Softwareprojekts. In diesen Abschnitten werden wichtige Sprachelemente erläutert.
Merkmale von Programmiersprachen
Zu den allgemeinen Merkmalen und Eigenschaften von Programmiersprachen gehören:
- Gleichzeitigkeit (Concurrency)
-
Gleichzeitigkeit bezeichnet die zeitgleiche Bearbeitung mehrerer Aufgaben, entweder durch die Ausführung auf verschiedenen Hardware-Prozessoren oder durch die abwechselnde Bearbeitung verschiedener Aufgaben durch einen einzelnen Prozessor. Der Grad der Gleichzeitigkeit, der von einer Programmiersprache unterstützt wird, kann sich stark auf Leistung und Skalierbarkeit auswirken, insbesondere bei Anwendungen, die eine Echtzeitverarbeitung oder große Datenmengen erfordern. Ein einzelner Arbeitsschritt wird als Prozess, Aufgabe oder Thread bezeichnet.
- Speicherverwaltung (Memory Management)
-
Unter Speicherverwaltung versteht man die Zuweisung und Freigabe von Speicher in einem Programm. Je nach Sprache oder Laufzeitumgebung kann die Speicherverwaltung manuell durch den Programmierer oder automatisch erfolgen. Eine ordnungsgemäße Speicherverwaltung entscheidet darüber, ob ein Programm den Speicher effizient nutzt oder ihm der Speicher ausgeht oder andere Probleme auftreten. Wenn ein Programm nicht in der Lage ist, ungenutzten Speicher freizugeben, verursacht es ein Speicherleck, durch das der Speicherverbrauch allmählich ansteigt, bis negative Auswirkungen auf die Leistung festzustellen sind oder das Programm abstürzt.
- Gemeinsam genutzter Speicher (Shared Memory)
-
Gemeinsam genutzter Speicher ist eine Art Kommunikationsmechanismus zwischen mehreren Prozessen, der es diesen ermöglicht, einen gemeinsamen Speicherbereich zu lesen und zu bearbeiten. Gemeinsam genutzter Speicher ist in Hardware z.B. in Form von Festplattenlaufwerken üblich und kann auch eine effiziente Möglichkeit sein, Daten zwischen Prozessen gemeinsam zu nutzen. Der Mechanismus erfordert jedoch eine sorgfältige Synchronisierung und Verwaltung, um eine Beschädigung der Daten zu verhindern. Ein Fehler, der als Race Condition (Wettlaufsituation) bekannt ist, tritt auf, wenn ein Prozess eine unvorhergesehene Änderung an Daten vornimmt, während ein anderer Prozess diese nutzt.
- Nachrichtenaustausch (Message Passing)
-
Nachrichtenaustausch ist ein Kommunikationsmechanismus zwischen Prozessen, über den diese Daten austauschen und Aktivitäten koordinieren. Er wird häufig in der nebenläufigen (concurrent) Programmierung für die Kommunikation zwischen Prozessen genutzt und kann durch verschiedene Mechanismen wie Sockets, Pipes oder Warteschlangen (Message Queues) implementiert werden.
- Garbage Collection
-
Die Garbage Collection ist eine automatische Speicherverwaltungstechnik, die einige Programmiersprachen nutzen, um Speicher zurückzugewinnen, der nicht mehr verwendet wird, während ein Prozess läuft. Sie kann dazu beitragen, Speicherlecks zu verhindern, und es Entwicklern erleichtern, korrekten und effizienten Code zu schreiben. Sie kann aber auch Overhead in der Performance verursachen und die Kontrolle über das genaue Verhalten des Programms erschweren.
- Datentypen
-
Datentypen bestimmen, welche Art von Informationen im Programm darstellbar sind. Datentypen können in der Sprache vordefiniert oder benutzerdefiniert sein und umfassen Ganzzahlen, Gleitkommazahlen (d.h. Näherungswerte für reelle Zahlen), Zeichenketten, Arrays und andere.
- Eingabe/Ausgabe, E/A (Input/Output, I/O)
-
Eingabe und Ausgabe sind Mechanismen zum Lesen und Schreiben von Daten in ein und aus einem Programm. Die Eingabe kann aus einer Vielzahl von Quellen stammen, wie z.B. Benutzerklicks und Tastatureingaben, einer Datei oder einer Netzwerkverbindung, während die Ausgabe an eine Vielzahl von Zielen gesendet werden kann, wie z.B. eine Anzeige, eine Datei oder eine Netzwerkverbindung. E/A ermöglicht Programmen die Interaktion mit der Außenwelt und den Austausch von Informationen mit anderen Systemen.
- Fehlerbehandlung (Error Handling)
-
Die Fehlerbehandlung erkennt Fehler, die während der Ausführung eines Programms auftreten, und reagiert darauf. Dazu gehören Fehler wie die Division durch Null oder eine angeforderte Datei, die nicht gefunden wird. Die Fehlerbehandlung ermöglicht es, dass Programme auch bei Auftreten von Fehlern weiterlaufen, was ihre Zuverlässigkeit und Robustheit erhöht.
Die aufgeführten Konzepte sind von grundlegender Bedeutung, um zu verstehen, wie Programmiersprachen funktionieren und wie man effizienten und wartbaren Code schreibt.
Syntax von Programmiersprachen
Die Syntax einer Programmiersprache beschreibt die Regeln für das Schreiben von Programmanweisungen und -ausdrücken. Es ist wichtig, dass die Syntax wohldefiniert und konsistent ist, damit Programmierer ihren Code überhaupt schreiben und verstehen können. Die folgenden sind Bausteine der meisten Programmiersprachen:
- Prozeduren und Funktionen
-
Prozeduren und Funktionen dienen dazu, wiederverwendbare Codeblöcke zu definieren, die mehrfach aufgerufen werden.
- Variablen
-
Variablen stellen Teile des Speichers dar und speichern Daten, die verändert und zwischen Prozeduren und Funktionen weitergegeben werden können.
- Operatoren
-
Operatoren sind Schlüsselwörter oder Symbole (wie
+
und-
), die Variablen Werte zuweisen und arithmetische Operationen durchführen. - Kontrollstrukturen
-
Im Allgemeinen wird der Programmcode in der Reihenfolge ausgeführt, in der er geschrieben wurde, aber bedingte Anweisungen ändern den Ablauf der Ausführung. Welcher Code als nächstes ausgeführt wird, hängt von verschiedenen Bedingungen ab, wie z.B. dem Inhalt des Speichers, dem Zustand der Tastatur, den vom Netzwerk ankommenden Paketen usw. Die Schleifenanweisung, eine spezielle Form der bedingten Anweisung, ist nützlich, um dieselben Operationen an einer Reihe von Datensätzen auszuführen. Eine Ausnahme, die bei Auftreten eines Fehlers speziellen Code aufruft, ist eine weitere Kontrollstruktur.
Syntax und Verhalten dieser Konstrukte können von Programmiersprache zu Programmiersprache unterschiedlich sein, und die Wahl der Sprache hat großen Einfluss auf Lesbarkeit und Wartbarkeit des Codes.
Bibliotheken
Eine gute Programmiersprache sollte die Entwicklung von Programmen und die Wiederverwendung von bestehendem Code erleichtern. Viele Programmiersprachen verfügen über einen Mechanismus, mit dem sich Prozeduren und Funktionen in Teile gliedern lassen, die in anderen Programmen wiederverwendet werden können.
Eine Bibliothek (Library) ist eine Sammlung von Prozeduren und Funktionen zur Unterstützung eines bestimmten Features oder Ziels, die in einer einzigen Datei zusammengefasst sind. Die Verfügbarkeit vieler einfach zu benutzender Bibliotheken ist eine wichtige Anforderung an eine gute Programmiersprache. Python zum Beispiel gilt weithin als eine gute Sprache für die Entwicklung KI-bezogener Programme, weil sie eine Reihe von Bibliotheken hat, die für die KI-Verarbeitung geeignet sind.
Mit der zunehmenden Größe und Komplexität von Programmen werden Bibliotheken als fertige Bausteine immer wichtiger. Dies gilt insbesondere für die Open-Source-Welt, wo Code, den andere erstellt haben, gerne übernommen und wiederverwendet wird. Infolgedessen hat sich für jede Programmiersprache ein Ökosystem von Bibliotheken entwickelt, und Paketmanager wie composer
für PHP, pip
für Python und gems
für Ruby machen die Installation von Bibliotheken einfach.
Bibliotheken sind auch wichtige kompilierte Sprachen. Das Kombinieren mehrerer Binärdateien und vorkompilierter Bibliotheken, um eine einzige ausführbare Datei zu erhalten, wird Linken genannt, und das Werkzeug, das diesen Vorgang durchführt, ist ein Linker. Es gibt zwei Arten des Linkens: statisches Linken, bei dem nur der notwendige Bibliothekscode in die endgültige ausführbare Datei der Anwendung aufgenommen wird, und dynamisches Linken, bei dem eine im System installierte Bibliothek von allen Anwendungen, die diese Bibliothek verwenden, gemeinsam genutzt wird. Heute ist dynamisches Linken der bevorzugte Ansatz und zeichnet sich durch kleinere ausführbare Anwendungsdateien und weniger Speicherverbrauch zur Laufzeit aus.
Da dieselben Bibliotheken von mehreren Programmen verwendet werden, können Unterschiede zwischen den Versionen einer Bibliothek ein noch größeres Problem darstellen als bei Anwendungen. Schauen wir kurz, wie Versionsnummern aufgebaut sind: Üblich ist die semantische Versionierung, die Versionen durch drei durch Punkte getrennte Zahlen angibt. Eine typische Version wäre beispielsweise 2.39.16
mit der Hauptversion 2 (die sich wahrscheinlich nur alle paar Jahre ändert), der Nebenversion 39 innerhalb der Hauptversion (die alle paar Monate aktualisiert werden kann, um wichtige Funktionsänderungen anzuzeigen) und der schnelllebigen Revision 16 (die sich schon aufgrund einer einzigen Fehlerbehebung ändern kann). Spätere Versionen und Revisionen haben höhere Zahlen.
Ein sehr einfaches Beispiel
Schauen wir uns ein sehr einfaches Beispiel für ein Computerprogramm in der Sprache Python an, um eine ungefähre Vorstellung von einigen der genannten Elemente zu bekommen.
In natürlicher Sprache ausgedrückt, soll das Programm Folgendes tun: “Fordere den Benutzer auf, eine Zahl einzugeben, und prüfe, ob diese Zahl gerade oder ungerade ist. Gib zuletzt das Ergebnis aus.”
Und hier ist der Code, den wir in der Datei simpleprogram.py
speichern können:
num = int(input("Enter a number: "))
if (num % 2) == 0:
print("The given number is EVEN.")
else:
print("The given number is ODD.")
Selbst in diesen wenigen Codezeilen finden wir viele der oben genannten Merkmale und Syntaxelemente:
-
In Zeile 1 setzen wir die Variable
num
und weisen ihr einen Wert mit dem Operator=
zu. -
Der zugewiesene Wert entspricht der Eingabe des Benutzers (über die Funktion
input()
). Außerdem sorgt die Funktionint()
dafür, dass diese Eingabe in den Datentyp Ganzzahl konvertiert wird, sofern dies möglich ist. Der Ausdruck, der innerhalb von Klammern an eine Funktion übergeben wird, heißt Parameter oder Argument. -
Wenn der Benutzer eine Zeichenkette aus Buchstaben eingibt, würde Python einen Fehler als Teil seiner Fehlerbehandlung ausgeben. Wird eine Dezimalzahl eingegeben, konvertiert die Funktion
int()
sie in die Basiszahl, z.B.5.73
in5
. -
In den folgenden Zeilen steuert die Kontrollstruktur, die eine Bedingung angibt, mit den Schlüsselwörtern
if
undelse
, was in jedem der beiden möglichen Fälle geschieht (die Zahl ist entweder gerade oder ungerade). -
Zunächst testet der Modulo-Operator, ob (
if
) die Division der eingegebenen Zahl durch 2 den Wert 0 ergibt (d.h. kein Rest) — in diesem Fall ist die Zahl gerade. Das doppelte==
ist der Vergleichsoperator “ist gleich”, der sich vom Zuweisungsoperator=
in Zeile 1 unterscheidet. -
Im anderen Fall (
else
), d.h. wenn die Division durch 2 ein Ergebnis ungleich 0 ergibt, muss die eingegebene Zahl ungerade sein. -
In beiden Fällen gibt die Funktion
print()
das Ergebnis als Ausgabe in Textform zurück.
Und so sieht es aus, wenn wir das Programm in der Befehlszeile ausführen:
$ python simpleprogram.py Enter a number: 5 The given number is ODD.
Wenn man bedenkt, wie viel Sprachlogik bereits in diesem kleinen Beispiel steckt, bekommt man eine Vorstellung davon, wozu komplexe Software, über Tausende von Dateien verteilt, in der Lage ist — zum Beispiel Betriebssysteme wie Microsoft Windows, macOS oder Linux, die die gesamte Hardware eines Computers verfügbar machen und gleichzeitig dafür sorgen, dass die Nutzer alle gewünschten Anwendungen installieren und für die Arbeit oder zum Spaß nutzen können.
Maschinencode, Assemblersprache und Assembler
Wie bereits erwähnt, kann Hardware nur eine Reihe von Bitmustern, den so genannten Maschinencode, direkt ausführen. Die CPU liest ein Bitmuster in Einheiten von einem Wort (8 bis 64 Bits) aus dem Speicher und führt die entsprechende Anweisung aus. Einzelne Anweisungen sind recht einfach, z.B. “Kopiere den Inhalt von Speicherplatz A nach Speicherplatz B”, “Multipliziere den Inhalt von Speicherplatz C mit dem Inhalt von Speicherplatz D” oder “Lies die Daten, die an Adresse X im Gerät angekommen sind”. In der Ära der 8-Bit-CPUs konnten sich einige Leute noch alle im Maschinencode verwendeten Bitmuster merken und direkt Programme schreiben. Heutzutage hat sich die Zahl der Anweisungsmuster um eine Größenordnung erhöht, und sich alle diese Muster zu merken, wäre unpraktikabel.
Maschinencode ist eine Abfolge von Bitmustern (0 und 1), was für den Menschen nicht intuitiv ist. Um das Programmieren intuitiver zu machen, wurde die Assemblersprache entwickelt, in der die Anweisungen Namen erhalten und durch Zeichenketten spezifiziert werden. In der Assemblersprache werden Anweisungen, die exakt dem Maschinencode entsprechen, nacheinander geschrieben und könnten so aussehen:
move [B], [A] Kopiere den Inhalt von Speicher A nach Speicher B multi R1, [C], [D] Multipliziere den Inhalt von Speicher C mit dem Inhalt von Speicher D input R1, [X] Lies die Daten, die im Gerät an der Adresse X angekommen sind
Eine Anweisung in Assemblersprache entspricht einer Anweisung im Maschinencode, der genau der Anweisung entspricht, die die Hardware verstehen und ausführen kann. Zu den Vorteilen der Assemblersprache gegenüber Maschinensprache gehören:
- Bessere Lesbarkeit und Wartbarkeit
-
Assemblersprache ist viel einfacher zu lesen und zu schreiben als Maschinencode, was es Programmierern erleichtert, Code zu verstehen, zu debuggen und zu warten.
- Automatisierung der Adressberechnung
-
Die Programmierung von Maschinencode kann auch das Konzept der Variablen und Funktionen nutzen, aber alles muss in Form von Speicheradressen ausgedrückt werden. Assemblersprache ordnet den Speicheradressen auch Namen zu, was es einfacher macht, die Logik eines Programms auszudrücken.
Da Assemblersprache Zugriff auf alle Funktionen der Hardware hat, wird sie häufig in den folgenden Situationen verwendet:
- Architekturabhängiger Teil des Betriebssystems
-
Spezielle Anweisungen, die bei einer CPU-Architektur für den Zugriff auf Initialisierungs- und Sicherheitsfunktionen spezifisch sind, können nur in Assemblersprache erfolgen.
- Entwicklung von Low-Level-Systemkomponenten
-
Assemblersprache wird für die Entwicklung von Systemkomponenten verwendet, die direkt mit der Computerhardware interagieren müssen, wie z.B. Gerätetreiber, Firmware und das Basic Input/Output System (BIOS). Insbesondere Hochgeschwindigkeitsgeräte, die die Hardwareleistung bis an ihre Grenzen ausreizen, benötigen oft in Assemblersprache programmierte Treiber und Firmware.
- Programmierung von Mikrocontrollern
-
Assemblersprache wird auch zur Programmierung von Mikrocontrollern verwendet, kleinen, leistungsschwachen Computern, die in einer Vielzahl von eingebetteten Systemen von Spielzeug bis hin zu industriellen Steuerungen eingesetzt werden. Einige Mikrocontroller haben Speicherkapazitäten von nur einigen hundert Bytes und werden üblicherweise in Assemblersprache programmiert.
Assemblersprache ist das älteste Programmierwerkzeug und hat eine Reihe von Vorteilen, die bei der Programmierung in Maschinencode undenkbar waren. Manchmal wird übrigens die Assemblersprache kurz als Assembler bezeichnet.
Maschinencode und Assemblersprache unterscheiden sich von einem Hardware-Prozessor zum anderen. Man nennt sie “Low-Level-Sprachen”, weil sie direkt auf der Hardware arbeiten. Die Konzepte der Berechnung und der Ein-/Ausgabe sind jedoch bei allen Prozessoren gleich. Wenn die gemeinsamen Konzepte auf eine Weise ausgedrückt werden können, die für den Menschen leichter verständlich ist, kann das die Effizienz der Programmierung drastisch verbessern — und hier kommen die “High-Level-Sprachen” ins Spiel.
Kompilierte Sprachen
Kompilierte Sprachen sind Programmiersprachen, die entweder in Maschinencode oder in ein Zwischenformat namens Bytecode übersetzt werden. Bytecode wird auf dem Zielcomputer von einer virtuellen Maschine zur Laufzeit ausgeführt. Die VM übersetzt den Bytecode in den richtigen Maschinencode für jeden Computer. Bytecode ermöglicht es, dass Programme plattformunabhängig sind und auf jedem System mit einer kompatiblen virtuellen Maschine laufen.
Die Übersetzung des in einer höheren Programmiersprache geschriebenen Sourcecodes in Maschinencode oder Bytecode erfolgt durch einen Compiler. Beispiele für kompilierte Sprachen, die direkt Maschinencode erzeugen, sind C und C++. Sprachen, die Bytecode erzeugen, sind Java und C#.
Die Wahl zwischen Maschinencode und Bytecode hängt von den Anforderungen des Projekts ab, z.B. Leistung, Plattformunabhängigkeit und Einfachheit der Entwicklung.
Interpretierte Sprachen
Interpretierte Sprachen sind Programmiersprachen, die von einem Interpreter ausgeführt werden, anstatt in Maschinencode kompiliert zu werden. Der Interpreter liest den Quellcode und führt die darin enthaltenen Anweisungen aus. Ein Interpreter ist in der Lage, den Quellcode direkt zu verarbeiten, ohne ihn in ein anderes Dateiformat umzuwandeln. Im Gegensatz zu einem Compiler, der das gesamte Programm in Maschinencode übersetzt, bevor er es ausführt, liest ein Interpreter jede Codezeile und führt sie sofort aus, so dass der Programmierer die Ergebnisse jeder Zeile sehen kann, während sie ausgeführt wird.
Interpretierte Sprachen werden häufig für das Scripting verwendet, d.h. für das Schreiben kurzer Programme zur Automatisierung von Aufgaben, für Befehlszeilenschnittstellen und für die Batch- und Job-Steuerung. In interpretierten Sprachen geschriebene Skripte können leicht geändert und ausgeführt werden, ohne dass eine Neukompilierung erforderlich ist, wodurch sie sich gut für Aufgaben eignen, die ein schnelles Prototyping oder eine flexible und schnelle Iteration erfordern. Mit diesen Vorteilen gehen einige potenzielle Nachteile einher, darunter die Tatsache, dass ein interpretiertes Programm langsamer läuft als ein entsprechendes kompiliertes Programm und dass der Quellcode für jeden zugänglich ist, der im Besitz des Skripts ist.
Beispiele für interpretierte Sprachen sind Python, Ruby und JavaScript. Python wird häufig für wissenschaftliche Berechnungen, Datenanalysen und maschinelles Lernen verwendet, während Ruby häufig für die Webentwicklung und die Erstellung von Automatisierungsskripten eingesetzt wird. JavaScript ist eine clientseitige Skriptsprache, die in Webbrowser eingebettet ist, um dynamische und interaktive Webseiten zu erstellen.
Datenorientierte Sprachen
Datenorientierte Sprache sind für den effizienten Umgang mit großen Mengen strukturierter oder unstrukturierter Daten konzipiert und bieten eine Reihe von Werkzeugen für die Arbeit mit Datenbanken, Datenstrukturen und Algorithmen für die Datenverarbeitung und -analyse.
Datenorientierte Sprachen werden in einer Vielzahl von Anwendungen eingesetzt, darunter Data Science, Big Data Analytics, maschinelles Lernen und Datenbankprogrammierung. Sie eignen sich gut für Aufgaben, die die Verarbeitung und Analyse großer Datenmengen beinhalten, wie Datenbereinigung und -umwandlung, Datenvisualisierung und statistische Modellierung.
Beispiele für datenorientierte Sprachen sind SQL (kurz für Structured Query Language), R und MATLAB. SQL ist eine Standardsprache für die Verwaltung relationaler Datenbanken und in Wirtschaft und Industrie weit verbreitet. R ist eine Programmiersprache und -umgebung für statistische Berechnungen und Grafiken und wird häufig in den Bereichen Datenwissenschaft und maschinelles Lernen eingesetzt. MATLAB ist eine numerische Rechenumgebung und Programmiersprache, die in einer Vielzahl von Anwendungen eingesetzt wird, darunter Signalverarbeitung, Bildverarbeitung und Finanzberechnungen.
Programmierparadigmen
Neben den Besonderheiten von Programmiersprachen bestimmen Programmierparadigmen den jeweiligen Lösungsansatz. Ein Paradigma kann man sich als grundlegende Strategie vorstellen, mit der wir abhängig von den spezifischen Anforderungen und Bedingungen an eine Aufgabe herangehen.
Ein vergleichbares Beispiel ist der Bau eines Hauses: Ob Maurer die Wände Stein für Stein errichten oder Fertigbetonteile auf der Baustelle zusammengesetzt werden, ist eine grundsätzliche Entscheidung, die von den Anforderungen und Umständen abhängt: Welche Eigenschaften soll das Haus haben? Wo steht es? Ist es mit anderen Häusern verbunden?
In ähnlicher Weise geben Paradigmen die Richtung der Programmierung vor: ob und auf welche Weise beispielsweise ein Softwareprojekt in kleinere, separate Teile zerlegt wird. Jede Programmiersprache eignet sich am besten für ein bestimmtes Paradigma. Daher ist die Wahl des Paradigmas eng mit der Wahl der Programmiersprache verbunden.
Die folgenden Paradigmen sind in der Programmierung üblich:
- Objektorientierte Programmierung (OOP)
-
OOP basiert auf dem Konzept der Objekte, die Instanzen von Klassen sind, die Daten und Verhalten kapseln. Eine Sprache könnte beispielsweise ein Rechteck als Klasse anbieten, um dem Programmierer zu helfen, einen Kasten auf dem Bildschirm darzustellen.
OOP konzentriert sich auf die Manipulation von Daten auf Objektebene. OOP macht es einfacher, Code zu schreiben, der wartbar, wiederverwendbar und erweiterbar ist, und wird häufig in Desktop-Software, Videospielen und Webanwendungen verwendet. Beispiele für objektorientierte Programmiersprachen sind Java, C# und Python.
- Prozedurale Programmierung
-
Die prozedurale Programmierung führt Aufgaben durch Prozeduren oder Codeblöcke aus, die in einer bestimmten Reihenfolge ausgeführt werden können. Dies macht es einfach, strukturierten Code zu schreiben, der leicht zu erfassen ist, kann aber zu Code führen, der weniger flexibel und schwieriger zu warten ist, wenn Größe und Komplexität des Projekts zunehmen. Beispiele für prozedurale Programmiersprachen sind C, Pascal und Fortran.
Es gibt auch andere Ansätze für die Softwareentwicklung, und manche Sprachen sind dafür besser geeignet als andere. Darüber hinaus ermöglichen es Drag-and-Drop-Schnittstellen auch Nicht-Programmierern, Programme zu schreiben, und viele Online-Dienste generieren neuerdings Code durch künstliche Intelligenz, wenn sie Anweisungen in natürlicher Sprache erhalten.
Zusammenfassend lässt sich sagen, dass jedes Programmierparadigma seine Stärken und Schwächen hat und dass die Wahl des Paradigmas von den Anforderungen des Projekts, der Erfahrung und den Vorlieben der Entwickler sowie den Einschränkungen der Plattform und der Entwicklungsumgebung abhängt. Das Verständnis der verschiedenen Paradigmentypen kann helfen, das richtige Paradigma zu wählen sowie besseren und effizienteren Code zu schreiben.
Geführte Übungen
-
Was ist der Zweck von Funktionen?
-
Was ist der Vorteil von Bytecode gegenüber einer Datei mit Maschinencode?
-
Was ist der Vorteil einer Datei mit Maschinencode gegenüber Bytecode?
Offene Übungen
-
Was sind Nachteile der Aufteilung eines Programms in eine große Anzahl von Prozessen oder Tasks?
-
Sie haben mehrere Open-Source-Pakete gefunden, die in verschiedenen Versionen angeboten werden und die Funktionen bieten, die Sie für Ihr Programm benötigen. Was sind einige Kriterien für die Auswahl eines Pakets?
-
Welche anderen Paradigmen der Softwareentwicklung gibt es neben OOP und der prozeduralen Entwicklung und welche Programmiersprache unterstützt den jeweiligen Ansatz am besten?
Zusammenfassung
In dieser Lektion haben Sie gelernt, was Software ist und wie sie mit Hilfe von Programmiersprachen entwickelt wird. Die zahlreichen Programmiersprachen unterscheiden sich nicht nur in ihrer Syntax, sondern zum Beispiel auch in der Verwaltung von Hardwareressourcen oder der Handhabung von Datenstrukturen.
Programmiersprachen unterscheiden sich auch darin, wie der für Menschen lesbare Quellcode von einem Interpreter oder Compiler in den endgültigen Maschinencode umgewandelt wird, der vom Computer verarbeitet wird.
Programmierparadigmen bestimmen die Strategie von Softwareprojekten und damit auch die Wahl geeigneter Programmiersprachen, abhängig von den Anforderungen und der Größe des jeweiligen Projekts.
Antworten zu den geführten Übungen
-
Was ist der Zweck von Funktionen?
Funktionen kapseln allgemeine Aktivitäten, wie z.B. die Ausgabe einer Zeichenkette. Durch die Erstellung einer Funktion können Sie Ihrem Programm und anderen Programmen erlauben, die Funktion bequem und wiederholt auszuführen, ohne dafür eigenen Code schreiben zu müssen.
-
Was ist der Vorteil von Bytecode gegenüber einer Datei mit Maschinencode?
Die Datei mit Bytecode kann auf vielen verschiedenen Computern ausgeführt werden, wo eine virtuelle Maschine den Code in Maschinencode umwandelt. JavaScript läuft zum Beispiel in vielen Browsern auf vielen verschiedenen Computern.
-
Was ist der Vorteil einer Datei mit Maschinencode gegenüber Bytecode?
Maschinencode läuft so schnell wie möglich. Bytecode läuft langsamer, weil die virtuelle Maschine ihn in Maschinencode umwandeln muss, während sie den Bytecode ausführt.
Antworten zu den offenen Übungen
-
Was sind Nachteile der Aufteilung eines Programms in eine große Anzahl von Prozessen oder Tasks?
Wenn ein Programm in Prozesse aufgeteilt ist, müssen diese miteinander kommunizieren. Wenn sie mit vielen gemeinsamen Daten arbeiten, können die Prozesse viel Overhead beim Datenaustausch und beim Schutz der Daten vor mehrfachen, gleichzeitigen Änderungen (Race Conditions) verbrauchen. Prozesse verursachen auch Overhead, wenn sie gestartet und beendet werden. Je mehr Prozesse Sie haben, desto komplexer werden das Programm und seine Interaktionen, so dass es schwieriger sein kann, Fehler zu finden.
Das funktionale Paradigma macht es tendenziell einfacher, Programme in viele Prozesse aufzuteilen, da unveränderliche Daten nicht unter Race Conditions leiden.
-
Sie haben mehrere Open-Source-Pakete gefunden, die in verschiedenen Versionen angeboten werden und die Funktionen bieten, die Sie für Ihr Programm benötigen. Was sind einige Kriterien für die Auswahl eines Pakets?
Überprüfen Sie Fehlerberichte und Sicherheitshinweise für die Pakete, da einige sehr fehlerhaft und sogar unsicher sein können. Manchmal ist die neueste Version nicht die beste, da eine Sicherheitslücke hinzugekommen sein könnte.
Sehen Sie sich das Forum an, in dem die Entwickler über das Paket sprechen, um zu sehen, dass es aktiv gewartet wird. Ihr Programm wird wahrscheinlich für eine lange Zeit verwendet werden, und Sie möchten, dass das Paket auch im Laufe der Zeit verfügbar und robust ist.
Probieren Sie verschiedene Pakete aus, um ihre Leistung sowie ihre Korrektheit zu überprüfen.
Die meisten Pakete hängen von Funktionen ab, die in anderen Paketen gefunden werden (Abhängigkeiten), so dass eine Schwäche in einer der Abhängigkeiten Ihr Programm beeinflussen kann.
-
Welche anderen Paradigmen der Softwareentwicklung gibt es neben OOP und der prozeduralen Entwicklung und welche Programmiersprache unterstützt den jeweiligen Ansatz am besten?
Neben OOP und prozeduralen Entwicklungsparadigmen gibt es weitere Arten:
Die funktionale Programmierung legt den Schwerpunkt auf die Verwendung von Funktionen und mathematischen Konzepten wie Lambdas und Closures, um Code zu schreiben, der auf der Auswertung von Ausdrücken und nicht auf der Ausführung von Anweisungen basiert. Funktionale Programmierung behandelt Funktionen vorrangig — so dass sie vom Programm manipuliert werden können — und betont die Unveränderlichkeit oder die Arbeit mit Variablen, die sich nicht mehr ändern können, nachdem sie einmal gesetzt wurden. Das macht es einfacher, den Code zu analysieren und ihn zu testen sowie nebenläufige und parallele Anwendungen zu schreiben. Beispiele für funktionale Programmiersprachen sind Erlang, Haskell, Lisp und Scheme.
Imperative Sprachen konzentrieren sich auf die Anweisungen, die erforderlich sind, um den Fluss der Übergänge des Programms von und zu verschiedenen Zuständen zu steuern.
Deklarative Sprachen beschreiben die zu ergreifenden Maßnahmen und die Logik hinter den Anweisungen. Die Reihenfolge, in der die Anweisungen ausgeführt werden, ist nicht festgelegt. Diese Anweisungen, Funktionsaufrufe und anderen Anweisungen können von Compilern neu geordnet und optimiert werden, sofern die zugrundeliegende Logik erhalten bleibt.
Natürliche Programmierung ist ein Programmierparadigma, das natürliche Sprache oder andere menschenfreundliche Darstellungen verwendet, um das gewünschte Verhalten eines Programms zu beschreiben. Die Idee ist, das Programmieren für Menschen zugänglich zu machen, die keine formale Ausbildung in Informatik haben. Beispiele für natürliche Programmiersprachen sind Scratch und Alice.