Sollte sich einmal die Gelegenheit ergeben, würde ich gern einen Programmierkurs geben – vorrangig für Anfänger. Allerdings muss ich selbst dann erst einmal herausfinden, was die Probleme eines Anfängers sind und wie man etwas Anfängern erklärt, da ich nun fast 30 Jahre programmiere und weit, weit von den Anfängen entfernt bin.
Da man Programmieren nicht direkt erklären, sondern es nur anhand von vielen Beispielen erlernen kann, würde ich den Kurs anders als üblich gestalten – irgendwo habe ich einmal den Vergleich gelesen: dass ein Dichter nicht mit Schreiben, sondern mit Lesen beginnt. In diesem Sinne würde ich auch damit beginnen, bestehende Programme zu modifizieren und dabei Stück für Stück Code analysieren und debuggen. Damit sollten auch schneller Erfolgserlebnisse eintreten, anstatt bei primitiven Beispielen wie Hello World oder Türme von Hanoi.
Da Programmieren auch mehr als reines Quelltextschreiben ist und innerhalb von einer oder zwei Wochen keine vollständigen Programmierkenntnisse vermittelt werden können, würde ich viel Wert auf das Drumherum legen, also die Programmierphilosophie und das Verständnis, anstatt viel Quelltext zu schreiben.
Hier einmal die Skizze, wie ich einen solchen Kurs gestalten würde:
Aufruf
TODO
Einführung
Was ist Programmierung und was tut ein Programmierer?
+---------+ +---------------+ +----------+
| | | | | |
| Aufgabe | -----> | Programmierer | ------> | Computer |
| | | | | |
+---------+ +---------------+ +----------+
| | | |
+----------+---------+ +-----------+-------+
| |
v v
Problem-/Projektanalyse Programmierung
- einfach gesprochen: Programmierer löst Aufgaben mithilfe eines Computers; aber Programmieren ist mehr
- Aufgaben des Programmierers:
- die Aufgabe verstehen: Problemanalyse (entwickeln eines Problemmodells im Kopf)
- dem Computer das Problem erklären: Programmierung (übertragen des Modells in eine Programmiersprache)
- Programmieren ist mehr als Coden: Vor allem Analysieren, Modellieren, Optimieren; immer und ständig, Pragmatic programmer, früher (Lochkarten) war Programmierer und Coder getrennt
- meist kommuniziert man mit einem Kunden (Auftraggeber) über die Aufgabe in menschlicher/natürlicher Sprache
- mit dem Computer kommuniziert man mit einer Programmiersprache
- Programmiersprachen sind strenger strukturiert als natürliche Sprachen und erlauben weniger Ungenauigkeiten/Mehrdeutigkeiten als natürliche Sprachen, daher ist es nicht so leicht, Aufgaben in Programme umzusetzen
- Fähigkeiten eines Programmierers:
- Analytisches Denken, d. h. (1) Reduktion der Aufgabe auf die wesentlichen Teile, (2) Erkennen und (3) Strukturierung der Elemente und Arbeitsschritte
- Kenntnisse über die Programmiersprache (Syntax, Funktionsweise) und der Funktionen in den Bibliotheken
- Kenntnisse über Aufbau und Arbeitsweise des Computers
- Arbeitswerkzeug: Editor oder Entwicklungsumgebung
- Welche Programme nutze ich? Welche Programmiersprache nutze ich? ⇒ Wahl des passenden Werkzeugs zum Problem und nicht das Problem auf das Werkzeug anpassen (Hammer+Nagel) ⇒ Lernen, experimentieren, Fähigkeiten schulen und ausbauen
- Neugier, Frage »Warum?«
- Wie koche ich Kaffee? Welche Temperatur ist relevant, welche Körnung? Frage (Wille zur Analyse) ist der Einstieg, der Rest folgt automatisch/Übung.
- Aufgabe: Bilder Pragmatic-Programmer-X.jpg lesen + übersetzen
- Entscheidung für Rust
- Aufgabe: VSCodium oder Emacs installieren, Rust einrichten, Hello-World
Programmiersprachen
- Programmiersprachen wie Sand am Meer
- oft für spezielle Einsatzzwecke oder mit besonderen Konzepten
- Bunte Mischung:
- Assembler: maschinennah, wie Binäranweisungen, prozessorspezifisch
- C: prozessorunabhängig(er)
- C++, Java, C#: Objektorientierte Programmierung
- Shell, Ruby, Python, Javascript: Skriptsprachen
- Haskell, Lisp: Funktionale Programmierung
- LabView: Grafische Programmierung von Steuerungen für Fabrikanlagen
- Brainfuck: Zustandsautomat, theoretische Informatik
- Prolog: Logische Programmierung
- Sinumerik: CNC-Maschinen, SPS, Makerspace
- Octave, Matlab, Mathematica, Maxima: Mathematik
- S/R, SPSS/PSPP: Statische Berechnungen
- Intercal, Java2k, Whitespace, HQ9+:
Spaßprogrammiersprachen,
Esolang, the esoteric programming languages
wiki,
This last keyword [PLEASE] provides two reasons for the program's rejection by the compiler: if "PLEASE" does not appear often enough, the program is considered insufficiently polite, and the error message says this; if too often, the program could be rejected as excessively polite.
- PostScript, TeX: Drucksachen
- PHP: Webseiten
- Formale Definition einer Programmiersprache: Turing-Vollständig (Turing-Award)
- Auszeichnungs- und Abfragesprachen:
- Zeittafel der Programmiersprachen
- Compiler, Interpreter
Zeitlicher Ablauf von Programmierung – Projektmanagement
- Projektmanagement: Strukturiere Umsetzung von Aufgabenstellungen
- Ablauf: PDCA-Zyklus
- Plan: Analyse der Aufgabe und Planung der Arbeit
- Do: Umsetzung des Plans
- Check: Überprüfung des Ergebnisses
- Act: Bewertung der Überprüfung und Abgleich mit dem ursprünglichen Plan
- Modularisierung des Projekts, große Aufgaben werden in kleine, in sich abgeschlossene Teilaufgaben zerlegt und für diese wird der PDCA-Zyklus durchlaufen: agiles Projektmanagement
- Beispiel Hausbau: Welches Dach muss entschieden sein, wenn die Mauern gesetzt werden. Software leichterer Umbau
- top-down, bottom-up
- klassisches Projektmanagement, bei dem die komplette Aufgabe (Projekt) in einem Durchlauf erledigt wird, hat sich als fehleranfällig und zu behäbig erwiesen; Software ist besser anpassbar und erweiterbar als Hardware, weshalb inkrementelles Vorgehen in überschaubaren Schritten besser geeignet
- TODO Test-driven-development
Softwaretests
- wesentlicher Bestandteil des Vorgehens ist die Prüfung, daher Prüfbarkeit von Anfang an einplanen und bei der Umsetzung bedenken
- Prüfungen sollten klein und leicht sein, damit man besser sehen kann, ob die Prüfung selbst korrekt ist – Prüfung der Prüfung (der Prüfung …) wäre zu kompliziert
- Prüfungen sollten immer wieder durchgeführt werden ⇒ langweilige, stupide Aufgaben ständig wiederholen ⇒ Computer damit beauftragen, d. h. Tests der Software mit Software (= Testprogramme) schreiben
- Unit-Tests, Integration-Tests
- Testabdeckung/Code-Coverage
Aufgabe: TODO Erstellen von Softwaretests für ein existierendes Programm
Versionsverwaltung
- git
- add, commit
- push, pull
- clone
- bisect
- Verteilen von Blättern
- Logarithmus
- Beispiel
git bisect run cargo test
- Firefox-Fehlersuche
- schöne an Programmierung: Zusammensetzen zu einem größeren Ganzen
Grundlagen der Computertechnik
Rechnerarchitektur
- Von-Neumann-Architektur, 1. Teil des Konzepts: Ein Computer ist in einzelne Komponenten geteilt, die jeweils spezifische Aufgaben erfüllen. Kein »einer macht alles« oder »jeder kann alles«.
- CPU (engl. central processing unit): Steuer- und Recheneinheit des Computers
- hier passiert die eigentliche Arbeit
- untergliedert sich in mehrere Komponenten:
- Befehlsdekodierung und Steuerung an Einzelkomponenten
- Register + Cache (L1/L2/L3) für die Befehle und die Daten zur Bearbeitung
- ALU (engl. arithmetic logic unit) für die Berechnungen,
- FPU (engl. floating point unit) für Gleitkommaberechnungen,
- MMU (engl. memory management unit) zur Speicherorganisation
- MMX/SSE2 für Multimediaberechnungen
- Kryptoeinheit für Verschlüsselungsalgorithmen
- Virtualisierungseinheit für die Arbeit mit virtuellen Maschinen
- Zufallszahlengenerator
- IPMI, Intel ME
- …
- andere Geräte:
- die Außenwelt um die CPU herum
- früher wurde mal in Ein- und Ausgabegeräte unterschieden, aber viele Geräte sind beides
- Speicher:
- Arbeitsspeicher, RAM (engl. random access memory): nächste Ebene Datenablage; What every programmer should know about memory
- Festplatte: SSD, HDD, NVMe
- CD-ROM, DVD, Blueray
- Bandspeicher, MO-Speicher
- SAN (engl. Storage Area Network): über Netzwerk mit speziellen Protokollen angebundener Speicher
- Netzwerkkarte
- Grafikkarte
- USB, Tastatur, Maus, Touchpad
- Sensoren für Spannung, Temperatur u. s. w.
- BIOS/UEFI zum Starten des Rechners
- TPM (engl. trusted platform management) zur Aufbewahrung von Schlüsseln
- …
- mehrere Bus-Systeme zur Verbindung der (1) CPU mit Geräten oder (2) von
Geräten untereinander
- PCI, PCI-Express
- North-Bridge, South-Bridge
- Bus zur Verbindungen mehrerer CPUs (Multiprozessorsystem, SMP, engl. symmetric multi-processing)
- I2C
- Speicherhierarchie:
- in Wirklichkeit gibt es eine noch größere Vielfalt in den Ebenen
- Primärspeicher:
- Register: in der CPU, direkt an den Berechnungsstellen, sehr schnell, starr im Aufbau, nur für ganz bestimmte Operationen, weniger als 1 kB in Summe
- Cache: in der CPU, Level-1, Level-2, Level-3, sehr schnell, starr im Aufbau, recht teuer, für beliebige Daten, weniger als 10 MB in Summe
- RAM: außerhalb der CPU, für alle möglichen Daten, grob strukturiert (in Speicherseiten/Pages), mittelmäßig schnell, preiswert, meist austauschbar, z. Z. üblich im mittleren zweistelligen GB-Bereich, in Großcomputern auch schon im einstelligen TB-Bereich
- Sekundärspeicher:
- Festplatten und Festplattenverbünde (RAID-Systeme) (Sekundärsp.): langsam, (fast) frei einteilbar, z. Z. üblich im unteren zweistelligen TB-Bereich
- SAN (Sekundärsp.): üblich in Rechenzentren, fast so schnell wie
Festplatten, Kapazität im Peta-Bereich
- Teritärspeicher:
- Bandlaufwerke und Verbünde von Bandlaufwerken mit Robotern (Teritärsp.): im Profibereich für spezielle Anwendungen, sehr langsam, Kapazitäten im Zeta-Bereich
- USB-Sticks, USB-Festplatten, SD-Karten, Blueray (Teritärsp.): für Endanwender, sehr langsam, preiswert
- Teritärspeicher:
- in Wirklichkeit gibt es eine noch größere Vielfalt in den Ebenen
Aufgabe: lscpu
angucken, Benchmarking von Speicherzugriffen
Alternativen
- Menschliche Gehirn
Was ist eigentlich aus DNA-Computern geworden? | heise online
Im Jahr 2017 haben Wissenschaftler, die Fortschritte mit Kohlenstoffnanoröhrchen-Transistoren gemacht hatten, diese mit Schichten aus nichtflüchtigen Memristoren und Silizium-Bauelementen integriert – ein Prototyp für einen Ansatz, um die Geschwindigkeit und den Energieverbrauch bei der Datenverarbeitung zu verbessern, indem man von der herkömmlichen Architektur abwendet.
Ganzzahlen und Gleitkommazahlen
- uX, u128, usize
- iX, i128, isize
- Überlauf, Sättigung, Geprüfte
- f32, f64, Exponentenschreibweise
- Aufgabe: NST-Berechnung nach Newton + Tests, Differenzenquotienten, algebraisch oder numerisch
- Aufgabe: Berechnung von f(x) = c
Arbeitsweise des Rechners und Organisation des Speichers
- Ansprechen aller Komponenten ist kompliziert, maximal im Embedded-Bereich macht ein Programm noch alles selbst
- Betriebssystemkern und Userspace
- 2. Teil des von-Neumann-Konzepts: Speicher linear (nicht low-mem, High-mem), sequenzielle Befehlsfolge mit Sprüngen, Befehle sind einfach Daten (nur Trennung im L1i und L1d)
- virtuelle Speicheradressierung, jeder Prozess hat seine eigenen Speicherwelt, MMU, Absicherung
- Stack (starr, schnell, CPU-Unterstützung), Heap (flexibel, aufwendig in der Verwaltung)
- Programmablauf: OS → main
Theoretische Grundlagen
Zahlensysteme und Grundoperationen der CPU
- Implementation der Multiplikation in der CPU
- Algorithmus zum Multiplizieren vorstellen
- Schüler sollen diesen programmieren; mit
/ 2
und% 2
- Schüler sollen Tests dafür erstellen
- Zahlensysteme erklären binär, dezimal, hexadezimal
- kompakte Schreibweise abc des Stellenwertsystem entspricht a·10² + b·10¹ + c·10⁰
- Austausch der Basis ergibt 2 → binär, 8 → octal, 16 → hexadezimal
- Literale
und Ausgaben in Rust:
0b…, 0o…, 0x…
,println!("{:04b} {:X}", …)
- Bedeutung des Binärsystems für Schaltungen: Strom an/aus
- Binärsystem mit Arithmetik (Rechenoperationen) von Leibniz 1705
- binäre/boolesche Operationen: AND, OR, XOR, NOT ⇒ boolesche Algebra
- Funktion/Abbildung, Wahrheitswerttabelle (von Wittgenstein 1905)
- technischer Implementierung mit Relais/Transistor als Logikgatter, technische Entwickelung
- Addierer, Volladdierer ⇒ Rechnerarchitektur
- Schiebeoperationen als Ersatz für Multiplikation und Division,
&
als Ersatz für%
⇒ Rechnerarithmetik - abändern des Programms
- mathematische Grundlage des Algorithmus zum Multiplizieren
- Benchmarking zeigen und jeder setzt es um
- Assemblercode mit Godbolt
- römische Zahlen:
- Zahlensystem erklären
- gemeinsam einen Algorithmus entwickeln
- auf die Wiederholung im Code eingehen (DRY)
- Schleifeninvarianz
- trinäre Logik mit JS undefined/Perl undef/SQL, C, C# NULL; Quanten; Teritium non datur
(Effizienz von) Algorithmen und Datenstrukturen
- Standardbibliothek, externe Bibliotheken
- std::collections - Rust
- Vec, VecDeque
- Listen
- Bäume
- HashMap
- HashSet
Weiteres
Computergeschichte
- Charles Babbage
- mechanische Rechensysteme (Registrierkassen)
- Zuse
- Relais, Röhren, Transistoren, integrierte Schaltkreise, integrierte Systeme (GraKa auf der CPU, SoC)
- Transistor count
- Manchester Mark 1 Photo Gallery
- ARM, Intel, PowerPC, Mips, m68k (Apple)
- Simula 67: OOP
Fehler
Definition
- Definition Fehler (Bug, engl. eigentlich Käfer): unerwünschtes Verhalten eines Programms, dessen Auswirkungen nachteilige Folgen hat (Datenverlust); was ein Fehler ist, hängt sehr vom Betrachter (und den Folgen) ab
- Korrektheit/Fehlerfreiheit kann man nur für sehr, sehr begrenzte Systeme nachweisen; in über 99,9 % der Fälle kann man nicht von fehlerfrei sprechen, nur von »kein Fehler bekannt«; Anwendungen können nur auf Fehlerbehandlung setzen ⇒ jeder der Fehler abstreitet und nicht bereit ist, darüber nachzudenken, begeht einen Fehler ⇒ Demut; Fehler passieren und das muss man anerkennen
- 100 %-ige Sicherheit gibt es nicht, Demut statt Selbstüberschätzung/Überheblichkeit, »Das Programm könnte einen Fehler enthalten. Ich habe ihn nur noch nicht erkannt.«, »nach bestem Wissen und Gewissen sicher«
- Fehler verteilen sich auf einem Kontinuum zwischen »alles super« und
»megaschlecht«:
- super: Spezifikation ist abgeschlossen und vollständig und die Implementation entspricht exakt der Spezifikation; das Programm tut das und nur das, was in der Spezifikation steht, alles ist spezifiziert und genauso umgesetzt
- schlecht: Datenverlust; verletzte Schutzkonzepte: Angreifer können Daten einsehen oder verändern (bemerkt und unbemerkt); negative Folgen auf externe Systeme, z. B. Hardware-Beschädigung, Identitätsdiebstahl, Gelddiebstahl
- drei Bereiche:
- kreative Nutzung: nicht von der Spezifikation vorgesehen oder gedeckt; Hacking; ungefährlich, aber missfällt oft den Autoren des Systems; Hammer mit einer Schraube, Musizieren mit dem Telefon, Hopperticker
- üblichen Fehler; Klick auf einen Knopf und es passiert nichts, hellgraue Schrift auf weißem Untergrund, Sortierung der Tabelle in falscher Reihenfolge, Nichtbehandlung von
- kritische Fehler inklusive Sicherheitslücken; Programmabsturz, Datenverlust
Fehlerbehandlung
- Bedeutung eines Fehlers hängt von den Folgen ab, teilweise nicht abschätzbar
- Fehler können sich auch erst aus dem Zusammenspiel bestimmter Komponenten ergeben, z. B. Unix-Software auf Case-insensitive Dateisystemen (git)
- Fehlerbehandlung (Risikomanagement): gleitender Bereich von bis Unendlich; Kosten-Nutzen-Abwegung; Risiko = Kosten des Fehlers * Wahrscheinlichkeit des Fehlers
- Fehlerbehandlung ist eine Mischung aus a priori und a posteriori Maßnahmen; wichtig vorher darüber nachdenken und auf gewisse Fehlerklassen vorbereiten, um sie bei wirklichen Eintreten behandeln zu können; d. h. im Quelltext im Fehlerfall relevante Daten ausgeben, Logs schreiben; Fehlerbehandlung ist vielschichtig und erstreckt sich meist über mehrere Komponenten (Datenzugriffsfehler durch Prozessseparierung im Kernel und virtual memory in der CPU)
- Ergebnisse erfüllen die Spezifikation, Abweichung von der Spezifikation, nicht spezifiziert
- Fehlerquellen: Design/Spezifikation, Implementation/Umsetzung; Programme, Bibliotheken, Betriebssystem, Hardware (Spectre, Produktionsfehler) ⇒ Tabelle mit Beispielen
- Sicherheitslücke: Zugriff (lesen, verändern) auf Daten durch Dritte, obwohl die Meta-Spezifikation (»Daten sollen sicher sein«) dies ausschließt
Beispiele für Fehler
- Brute-Force
- Überläufe, Speicherzugriffsfehler
- Stack-Smashing (“Smashing the stack for fun and profit”), Heap-Überläufe
- SQL-Injection, Code-Injection (XSS)
- Timing, Side-Channel-Attacs
- DOS: Ausfall des Systems, Programmabsturz, Dead-Locks
- Hardware-Bugs: Rowhammer, Spectre (Beispiel), Meltdown
- ASLR, Fuzzying
Maßnahmen zur Fehlerbehandlung
- Software-Tests prüfen die Software gegen die Spezifikation
- ⇒ Software-Tests sollen nicht nur den Postiv-Fall (»Programm tut, was es soll«), sondern auch den Negativ-Fall (»Programm tut nicht, was es nicht soll«), Menge der Negativ-Fälle ist unendlich groß
- Problem: Die Welt ist wesentlich komplexer, als wir sie uns machen; Nebenläufigkeit (zwei Threads haben das Recht auf Daten zuzugreifen, nur in der falschen Reihenfolge, erzeugen sie Schrott), Superscalare CPUs, Out-Of-Order-Execution, Nicht-Atomare-Operationen
- »Fehler lernt man erst, indem man sie macht«, etwas muss schief gehen, damit man lernt, welche Probleme es gibt. Der Einfachheit halber werden viele Probleme im ersten Ansatz mit Vertrauen und Gutgläubigkeit gelöst, Prozesstrennung (DOS), kooperatives Multitasking (Win 3.11), Zugriffsrechte (Win NT)
- Ausnutzen von Sicherheitsfehler in der Regel eine Kombination mehrerer Programmzustände, die von der Spezifikation abweichen
- Angreifer und Verteidiger spielen ein ungleiches Spiel: Der Angreifer muss nur einmal Glück (Erfolg) haben – nur einen Weg finden –, aber der Verteidiger muss immer Glück haben – alle Wege verteidigen.
- Sichere Software ist Software, die nach aktuellen Wissensstand alle Wege verteidigt.
- ökonomische Definition von Sicherheit: Aufwand des Angreifers ist größer als der Gewinn
- Sicherheitsbewertung basiert immer auf einem Angriffsszenario
- Kreativität
- Fehlersuche/Debuging ist ein eigener Themenkomplex,
dbg!()
, Minimalbeispiel