Ich selber gebe/gab selten als erstes Projekt ein „Hello World“ Programm zur Aufgabe. Denn dies ist nur eine Zeile. Die Auszubildene Person erhält zwar ein kurzen Einblick zur Ausgabe aber, das empfinde ich selber als viel zu einfach für einen Auszubildenden. Ich habe immer, auch bei Praktikanten, mit einem Rechner angefangen. Bei einem Rechner muss die Auszubildene Person ein Eingabe annehmen, die Verarbeiten und ein Ergebnis Ausgeben. Also Eingabe, Verarbeitung, Ausgabe (EVA-Prinzip) lernen.
Erst alles in einer Funktion, dann die Grundrechenarten in einzelnen Funktionen. Alles nur Konsolenbasiert. Während er oder sie Programmierte führte ich einige Kontrollen durch und Prüfte ob er oder sie alles verstanden hat was die Auszubildende Person so schreibt. Sobald der Stoff drin war, kam immer die nächste Aufgabe, die Funktionen zum Rechnen in einer eigenen Klasse zu schreiben um ein Einblick in die Objektorientierten Programmierung zu erhalten. Alles allerdings erst in der Konsole, ohne GUI(Graphical User Interface). Wenn direkt z.B. mit Windows Form angefangen wird, ist die Verführung sehr groß den Code direkt in der Methode des Button klick zu schreiben. Da ich aber Wert lege dem Auszubildenden clean Code und den Vorteil der Wiederverwendbarkeit der Objektorientierten Programmierung bei zubringen.
Währen der oder die Auszubildene hier die erste Schritte im Programmieren lernt, erwarte ich zum großenteil selbstständiges lernen. Darunter verfolge ich folgende Ziele:
Teil 1:
- Wie kann eine Ausgabe in der Konsole gemacht werden?
- Wie kann eine Eingabe in der Konsole durchgeführt werden?
- Was sind Variablen und Datentypen.
- Welche Datentypen werden für was verwendet?
- Wie kann das Programm immer wiederholt werden, ohne es nach jeder Rechenoperation das sich das Programm beendet?
- if / switch (bei C#)
- Direkt vor Start des Projektes, zeige ich der Auszubildene Person Console.Reaadline() und Console.WriteLine().
Hier habe ich ein Beispielcode, eines Praktikanten den ich mal (vor Jahren) hatte. Wie gesagt beim ersten Teil achte ich nicht auf Fehlerbehandlung, Kommentationen und Notation. Auch muss der Code nicht perfekt sein. Code ist für C#.
Quellcode Teil 1
float a=0, b=0, ergebnis = 0;
bool weiter = true;
do {
Console.WriteLine(" ");
Console.WriteLine("Wähle eine Grundrechenart (+,-,*,/)");
switch (Console.ReadKey().KeyChar) {
case '+':
Console.WriteLine(" ");
Console.WriteLine("Addieren");
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
ergebnis = a + b;
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '*':
Console.WriteLine(" ");
Console.WriteLine("Multiplizieren");
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
ergebnis = a * b;
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '-':
Console.WriteLine(" ");
Console.WriteLine("Subtrahieren");
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
ergebnis = a - b;
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '/':
Console.WriteLine(" ");
Console.WriteLine("Dividieren");
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
if (b == 0)
{
Console.WriteLine("Dividieren durch 0 ist verboten!");
}
else {
ergebnis = a / b;
}
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '0':
Console.WriteLine(" ");
Console.WriteLine("Lösche vorheriges Ergebnis");
ergebnis = 0;
break;
default:
Console.WriteLine(" ");
Console.WriteLine("ungültige Eingabe");
break;
}
Console.WriteLine(" ");
Console.WriteLine("Nochmal rechnen? (n für Beenden)");
weiter = Console.ReadKey().KeyChar.ToString().ToLower() != "n";
} while (weiter);
Teil 2:
- Was sind Funktionen?
- Wie rufe ich Funktionen auf?
- Vor der Änderung des Projektes: Ich Zeige und erkläre wie Quellcode Kommentiert wird, sowie Funktionen geschrieben werden, mittels Kommentar beschrieben wird, was eine Funktion macht und warum es Sinnvoll ist sein Quellcode zu kommentieren. Oft kann man den Grund zu Kommentation gut anhand des Geschriebenen Codes aus Teil 1 schon erklären. Da die Auszubildenen schon einiges Vergessen haben.
Quellcode zu Teil 2
Der Code kann in einem Neuen Consolen Projekt eingefügt werden.
static void Main(string[] args)
{
float a = 0, b = 0, ergebnis = 0;
bool weiter = true;
do
{
try
{
Console.WriteLine(" ");
Console.WriteLine("Wähle eine Grundrechenart (+,-,*,/, 0 ? vorheriges Ergebnis löschen)");
switch (Console.ReadKey().KeyChar)
{
case '+':
Console.WriteLine(" ");
Console.WriteLine("Addieren");
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
//ergebnis = a + b; kommentiert, rechnung findet in Funktion statt
ergebnis = Addieren(a, b);
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '*':
Console.WriteLine(" ");
Console.WriteLine("Multiplizieren");
if (a == 0)
{
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
}
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
//ergebnis = a * b;kommentiert, rechnung findet in Funktion statt
ergebnis = Multiplizieren(a, b);
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '-':
Console.WriteLine(" ");
Console.WriteLine("Subtrahieren");
if (a == 0)
{
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
}
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
//ergebnis = a - b; kommentiert, rechnung findet in Funktion statt
ergebnis = Subtrahieren(a, b);
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '/':
Console.WriteLine(" ");
Console.WriteLine("Dividieren");
if (a == 0)
{
Console.WriteLine("Bitte Zahl 1 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out a);
}
Console.WriteLine("Bitte Zahl 2 eingeben.");
float.TryParse(Console.ReadLine().ToString(), out b);
ergebnis = Dividieren(a, b);
// kommentiert, rechnung findet in Funktion statt
//if (b == 0)
//{
// Console.WriteLine("Dividieren durch 0 ist verboten!");
//}
//else
//{
// ergebnis = a / b;
//}
Console.WriteLine("Ergebnis = " + ergebnis);
break;
case '0':
Console.WriteLine(" ");
Console.WriteLine("Lösche vorheriges Ergebnis");
ergebnis = 0;
break;
default:
Console.WriteLine(" ");
Console.WriteLine("ungültige Eingabe");
break;
}
Console.WriteLine(" ");
Console.WriteLine("Zahl 1 ist nun Ergebis ("+ergebnis +")");
Console.WriteLine("Nochmal rechnen? (n für Beenden)");
a = ergebnis;
weiter = Console.ReadKey().KeyChar.ToString().ToLower() != "n";
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // Fehlerfall Ausgabe in der Konsole
}
} while (weiter);
}
static float Addieren(float a, float b) {
float ergebnis = 0;
try // try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a + b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion Main() weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
static float Subtrahieren(float a, float b) {
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a - b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion Main() weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
static float Multiplizieren(float a, float b) {
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a * b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion Main() weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
static float Dividieren(float a, float b) {
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
if (b == 0)
{
Console.WriteLine("Dividieren durch 0 ist verboten!");
ergebnis = 0;
}
else
{
ergebnis = a / b;
}
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion Main() weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
Im Grunde genommen macht das Programm genau das, was es auch in Teil 1 macht: Es rechnet die Zahlen, die wir in die Variablen a
und b
eingegeben haben, zusammen. Diesmal übernehmen jedoch Funktionen die Rechenaufgaben.
Warum sollte sich ein Programmierer die Mühe machen und einzelne Funktionen schreiben? Zunächst einmal ist es etwas mehr zu tippen. In diesem Beispiel/Lehrprojekt sind es kleine und sehr einfache Funktionen, aber mit zunehmender Erfahrung folgen auch größere Projekte mit komplexeren Funktionen und Aufgaben. Wenn eine Funktion fehlerfrei funktioniert, kann sie immer wieder verwendet werden – Stichwort Nachhaltigkeit und Wiederverwendbarkeit.
Was der Praktikant oder Auszubildende neu gelernt hat, war der Umgang mit try
und catch
. Wenn innerhalb des try
-Blocks ein Fehler passiert, z.B. bei Fehleingabe des Benutzers, springt das Programm zum catch
-Block. Im catch
-Block wird der Code eingegeben, der im Fehlerfall ausgeführt werden soll. Hier wird der Fehler zur aufrufenden Funktion weitergeleitet, die den Fehler dann ausgibt.
Warum hat der Praktikant dies so gemacht und nicht direkt in der Funktion den Fehler ausgegeben? Dies war eine Vorbereitung meinerseits auf spätere Projekte und auf die Arbeit mit Klassen (Teil 3). Innerhalb einer Klasse weiß ich noch nicht, welche UI ich verwenden werde, z.B. Windows Form-App, WPF-App, Konsolen-App, Browser-App oder was es in Zukunft noch geben wird. Gebe ich den Fehler später “nur” in einer Log-Datei aus oder zeige ich ihn in einer MessageBox an? Gebe ich die Meldung auf der Konsole aus oder muss ich sie in einem Webbrowser darstellen? Daher reiche ich den Fehler weiter und die aufrufende Funktion soll den Fehler anzeigen.
Funktionsaufbau bei Teil 2
static float Dividieren(float a, float b){}
„static“ gehört zur Klasse selbst, also nicht zu einer Instanz der Klasse und können so direkt ohne ein Objekt der Klasse zu erstellen aufgerufen werden.
Auf Funktionen, die nicht statisch sind, kann erst nach der Instanziierung der Klasse zugegriffen werden. Statische Funktionen werden einmal im Speicher abgelegt und existieren während der gesamten Laufzeit des Programms. Statische Funktionen, Methoden, Variablen und Konstanten sind besonders geeignet für Utility-Methoden, die keinen Zustand benötigen.
Teil 3:
- Was ist Objektorientiertes Programmieren?
- Was sind Objekte
- Was sind die Vorteile zum Objektorientierten Programmieren?
- Eigene Matheklasse mit den Rechenoperationen schreiben.
- Die Klasse soll eine statische Funktion besitzen um die Zahl Pi mit der Genauigkeit von 7 Dezimalstellen ausgeben können.
- Alle Funktionen für Rechenoperationen sollen nicht Statisch sein
- Erstes Programm aus Teil 1 & Teil 2 umschreiben, das mit der eigene Klasse gearbeitet wird.
- Objekt Instanziierung
- Fehlerbehandlung
Da der Code in der Main() nahezu gleich bleibt, beschreibe ich in der Main() gleich nur die Änderungen.
Zuerst wird eine Klasse erstellt.
namespace KleinerRechner // Namespace der Klasse
{
public class MeineMatheKlasse // Name der Klasse
{
// Inhalt der Klasse
}
}
Aufbau einer Klasse:
Eine Klasse ist Teil eines „Namespace“, und der Klassenname wird hier als „public“ deklariert, sodass sie von allen Programmen verwendet werden kann, die auf ‚KleinerRechner‘ verweisen. Ich werde auf Teil 4 ausführlicher eingehen als auf die Teile 1 bis 3. Unter dem Abschnitt Programmierung sollten Sie auch die Algorithmen betrachten. Vielleicht möchten Sie eine Klasse für die Algorithmen programmieren (oder eine DLL?).
Nun zur Klasse MeineMatheKlasse:
namespace KleinerRechner
{
public class MeineMatheKlasse
{
public float Addieren(float a, float b)
{
float ergebnis = 0;
try // try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a + b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion (Main()) weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
public float Subtrahieren(float a, float b)
{
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a - b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion (Main()) weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
public float Multiplizieren(float a, float b)
{
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
ergebnis = a * b;
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion (Main()) weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
public float Dividieren(float a, float b)
{
float ergebnis = 0;
try// try Block, wenn ein Fehler passiert, springt das Programm zum catch Block
{// , bei Fehlerfreier ausführung wird der catch Block übersprungen.
if (b == 0)
{
Console.WriteLine("Dividieren durch 0 ist verboten!");
ergebnis = 0;
}
else
{
ergebnis = a / b;
}
}
catch (Exception ex)
{
throw ex; // Fehlerfall, der Fehler wird zur Aufrufenden Funktion (Main()) weitergereicht
}
return ergebnis; // Rückgabe des Ergebnisses
}
public static float getPi()
{
return 3.1415927f;
}
}
}
Hier wurden einfach die bereits Funktionierenden Funktionen per Copy & Paste in der neuen Klasse „MeineMatheKlasse“ eingefügt und aus dem Konsolenprogramm (unter der Main()) entfernt. Neu ist die statische Funktion getPi(), welche die Zahl Pi bis zur 7 Dezimalstelle zurückgibt. Meistens frage ich, welcher Datentyp genauer ist.
Auch sind die Funktionen der Matheoperationen anders, genau wie bei der Klassendeklaration steht dort nur public float funktionsname(){}. Hier sehen wir, das die Klasse sowohl statische, als auch nicht statische Funktionen besitzt.
Wie wir bei Statische Methoden in Teil 2 lernten, gehört eine Statische Methode zur Klasse selbst und kann direkt aufgerufen werden.
Console.WriteLine("Pi = " + MeineMatheKlasse.getPi().ToString());
Der Codeschnipsel zeigt wie die statische Funktion getPi() aufgerufen werden kann, also MeineMatheKlasse.getPi(). Wie wir in der Deklaration der Funktion sehen, gibt diese ein float zurück, daher setzte ich ein .ToString() hinzu, damit die float zahl in einem Stringtext gewandelt wird, um mehr über Datentypen zu erfahren, siehe Datentypen.
Um die Restlichen Funktionen verwenden zu können, muss diese Klasse Instanziiert werden. Dazu wird an geeigneter stelle (hier direkt bei Programmstart/Methodenstart), die Klasse Instanziiert:
MeineMatheKlasse mmk = new MeineMatheKlasse ();
Erläuterung des Codes:
„MeineMatheKlasse“ ist der Name der Klasse, die ich verwenden möchte. „mmk“ ist der Name der Variable, in die ich das Objekt „MeineMatheKlasse“ laden will. Das Objekt „mmk“ wird erstellt, indem ich den Konstruktor der Klasse „MeineMatheKlasse“ mit „= new MeineMatheKlasse()“ aufrufe. Den Konstruktor werde ich im Teil 4 näher erläutern. Nachdem „mmk“ instanziiert wurde, kann ich auf die mathematischen Methoden zugreifen.
ergebnis = mmk.Addieren(a, b);
Wie zu sehen ist, wird statt nur Addieren() nun mmk.Addieren() aufgerufen. Diese Klasse kann nun in allen Programmen verwendet werden. und muss im Fehlerfall nur 1x korrigiert werden. Danach ist die Änderung in allen Programmen enthalten, sofern nur auf der Klasse verwiesen wird und diese nicht 20x im Repository vorhanden ist. Schön geht es hier mit DLLs aber das kommt in Teil 4.
Vorschau auf Teil 4:
- Neues Projekt erstellen (Windows Forms)
- Rechner (Zahlen 0-9, Rechenoperatoren, 1 Textfeld) optisch (GUI) erstellen, hier beginnen Objekte.
- Die selbst erstellte Klasse verwenden um mit dem Rechner zu rechnen.
Lernzielkontrolle über gelerntes ausführen sowie Fragen und Unklarheiten/Verständnisprobleme klären (ggf. nach jedem oder auch während der einzelnen Teile).
Ich habe immer gerne ein Rechner als erstes Projekt ausgewählt, da ich zum einem nah am EVA dran bin, als auch noch mit Variablen arbeiten gelernt werden kann. später kann noch mit Arrays gearbeitet werden, oder Ergebnisse oder Logfiles auf einem Datenträger gesichert werden, etc. Je nach Verständnis oder Lerngeschwindigkeit des Auszubildenden dauern die jeweiligen Teile bis zu einer Woche.
Mir ist dabei wichtig das Auszubildene lernen u.a. das Internet als Informationsquelle nutzen zu können, das richtige suchen in Suchmaschinen sowie gestellte Probleme zu lösen. Bei größeren Probleme gebe ich der Auszubildene Person Tipps und Ratschläge oder Stichworte. Ist danach das Problem nicht gelöst, helfe ich. Ein Programmierer steht oft vor neuen Herausforderungen oder Problemstellungen und muss lernen Probleme zu Lösen.
Bei allen 4 Teile wiederholen sich einige Dinge, wodurch diese dann richtig „einbrennen“ neben dem neu erlernten.
Es gibt die Meinung, das durch Fehlern gelernt wird. Ich sehe diese Aussage zwiespältig. Ich würde eher sagen, das wenn zu viele Fehler gemacht werden oder zuviel korrigiert wird, dann wird der Spaß am lernen gedämpft. Beim Lernen sind Erfolge die treibende Kraft. Natürlich müssen Fehler aufgezeigt werden, aber nur über Fehler stolpern ist meines Erachtens nicht der richtige Ansatz. Es müssen auch Positive Dinge hervorgehoben werden.