Zum Inhalt springen

Whiz-zarD

Mitglieder
  • Gesamte Inhalte

    2.020
  • Benutzer seit

  • Letzter Besuch

  • Tagessiege

    48

Reputationsaktivitäten

  1. Like
    Whiz-zarD hat eine Reaktion von MarcoDrost erhalten in Wie spricht man Bewerber/innen an?   
    Sehe ich genauso. Deswegen schaue ich mir solche Stellenangebote auch nicht mehr weiter an. Allein wenn ich nur die Stellenanzeige anschaue:
    Da muss ich nur mit dem Kopf schütteln, dann das ist eine allgemeine Beschreibung, was ein Entwickler überhaupt macht. Da steckt überhaupt keine Information, was eigentlich gewünscht wird. Genauso gut könnte man diesen Satz auch weglassen. Nicht einmal der Technologie-Stack wird hier klar. Handelt es sich hier nun um eine Desktop Applikation und man arbeitet vielleicht mit Swing oder ist es eine Smartphone-App und arbeitet mit Android oder ist es sogar eine Web-App und arbeitet mit React/Angular/Was-auch-immer? Sucht man überhaupt einen Frontend-Entwickler oder eher jemanden im Backend-Bereich oder sucht man einen Allrounder? Auf was lässt man sich da also ein?
    Weiterhin steht dort u.a.
    Ja, welche denn? MSSQL, OracleDB, MySQL, MariaDB, ... ? Oder geht es hier um DB2, weil Erfahrung im IBM-Umfeld vom Vorteil wäre? Bin ich, der hauptsächlich im OracleDB-Umfeld arbeitet, der richtige? Und nein, relationale Datenbank ist nicht gleich relationale Datenbank.
    Es gibt da einen schönen Spruch: "Der Knochen kommt nicht zum Hund" oder "Seit wann kommt der Knochen zum Hund?" Wieso sollte ich mich also auf so eine Stelle bewerben? Eine Firma sucht Mitarbeiter. Also sollte doch die Firma erstmal in Vorleistung gehen und sich präsentieren.
  2. Like
    Whiz-zarD hat eine Reaktion von Britanny erhalten in Festfahren in Details beim programmieren   
    Wenn man zu sehr in den Details feststeckt, dann sollte man sich überlegen, ob die Lösung überhaupt die richtige ist?
    Zu viele Details bedeutet oftmals auch zu viel Code, der auch instabil ist. Man will wohl zu viel auf einmal erledigen. Daher wäre es wohl ratsam, hier ein Schlussstrich runterzusetzen und noch mal die Aufgabe überdenken. Evtl. müssen die Teilschritte noch mal unterteilt werden. Ratsam wäre auch mit einem Kollegen über die Aufgabe zu sprechen. Oftmals kommen da Ideen, an die man nicht gedacht hat. Pair-Programming kann in solchen Situationen auch hilfreich sein.
     
  3. Like
    Whiz-zarD reagierte auf Rienne in Projektangtrag: Entwicklung von Automatisierten CSV Datenübertragung und CSV Datenbankabgleichung   
    Ich finde es immer wieder interessant, was die IHKs so alles im Vorfeld schon im Antrag wissen wollen, was definitiv eigentlich zur Projektdurchführung zählen sollte.
    Klar kann man, wenn man es vorher schon weiß, angeben womit man programmiert. Aber eigentlich gehören solche Entscheidungen in die Definitionsphase und so etwas wird ja erst mit dem Pflichtenheft festgelegt.
    Genauso diese genauen Zeitangaben - man weiß, man hat 70h und grob sollte man so etwas auch vorher wissen, z.B. als Dauer der einzelnen Phasen; aber doch nicht die genaue Dauer jedes einzelnen Arbeitspaketes - denn auch die sollten erst NACH Projektstart definiert werden...
    Du hast doch noch gar nicht mit dem Projekt begonnen, oder?
    Bin immernoch skeptisch, bei einer festen Zeitvorgabe und ALLEINIGER Projektdurchführung, zu behaupten man würde ein Projekt agil durchführen.
    Ich kann jedoch verstehen, wenn die IHK den Antrag aufgrund des fehlenden Detailgrades der Beschreibung ablehnt.
    DU solltest dir immer die Frage stellen: Versteht jemand, der weder die Firma noch mein Tätigkeitsgebiet kennt, was ich da genau mache?
    Dazu ist es oft hilfreich z.B. etwas genauer zu erläutern um was für Prozesse es sich handelt, was der Kunde macht und was er genau geändert haben möchte.
    Du schreibst quasi lediglich: Eine CSV soll eingelesen und geprüft werden. Nichts zur Architektur, dem detaillierten Prozess, etc pp
  4. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in Asyl-berechtiger , Flüchtlinge   
    Darf ein Mensch jetzt kein Hobby mehr haben und dies im Internet zeigen, weil ein potenzieller, zukünftiger Arbeitgeber nach dieser Person auf Facebook suchen könnte und herausfindet, dass die Person Hobbies hat, die er nicht versteht? ... Sorry aber Privat ist Privat und Arbeit ist Arbeit. Wenn ein Arbeitgeber meint, er hat Vorurteile gegenüber anderen Menschen, weil ihm ein Profilbild nicht passt, weil er es nicht kapiert und auch keine Lust hat hat, zu recherchieren, dann ist er wohl auch nicht der richtige Arbeitgeber und von solchen Firmen sollte man dann auch einen großen Bogen machen.
    Sorry, aber jeder der mal was von American Football und NFL gehört hat, kann was mit dem Begriff "Raiders" anfangen. Gemeint ist die Mannschaft Oakland Raiders. Ein Blick unter Google verrät auch, dass "Raiders Nation" die Fans dieser Mannschaft betitelt. Dazu gibt es sogar einen Wikipedia-Eintrag. Also bitte einmal das Gehirn einschalten ...
  5. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in Im Beruf ankommen   
    Das Wort "Codeäffchen" lese ich öfters und frage mich jedes Mal, was das bittesehr sein soll?
    Offenbar entstammt das Wort vom Infinite-Monkey-Theorem. Dabei frage ich mich immer, was das nun mit Softwareentwicklung zu tun hat? Vielleicht gibt es Buden, die solange auf der Tastatur rumhacken, bis irgendwann mal eine Software dabei rauskommt aber wenn eine Firma ernsthaft Geld mit ihrer Software verdienen möchte, dann ist es schnell vorbei mit dem "Codeäffchen". Softwareentwicklung ist nun mal mehr, als nur Code zu schreiben. Heutzutage haben wir es mit einem gigantischen Technologiestack zu tun, der irgendwie orchestriert werden muss. Mit ein paar "Codeäffchen" kommt man da nicht weit und wenn eine Firma auch auf wartbaren Code achten möchte, dann müssen auch Entwickler ran, wie was von Clean Code verstehen. Darüber hinaus ist auch sehr viel Kommunikation von Nöten. Also die sog. "Soft Skills" sind heutzutage auch sehr wichtig geworden. 
    Glaube mir mal, so weit weg von "state of the art" ist das nicht.
    Mag sein, dass Startup-Hipster andere Technologien verwenden, aber ist würde davon mal ausgehen, dass viele Firmen sowas wie Hibernate, Spring MVC oder Bootstrap gar nicht kennen. Man darf sich auch nicht von den Stellenangeboten verunsichern lassen. Oftmals spielen sie da Bullshit-Bingo und feuern alles ab, was denen so einfällt. Die Realität sieht dann meist anders aus. Bewirb dich einfach auf Junior-Entwickler-Stellen. Du wirst sehen, dass andere auch nur mit Wasser kochen und auch nicht wirklich viel schlauer sind, als du selbst. Die Erfahrung kommt erst mit den Jahren. Also mache dir da mal keine Sorgen.
  6. Like
    Whiz-zarD hat eine Reaktion von Striewe erhalten in Fragen mündliche Prüfung   
    Dazu fällt mir noch ein:
    Welche Alternative gibt es zu POP3 und ist die Alternative nicht sinnvoller?
  7. Like
    Whiz-zarD hat eine Reaktion von Britanny erhalten in Tabellen in Html bzw. css   
    Du erstellt mit HTML ein Tabelle und mit CSS passt du die mit deinem Designstil an. CSS ist eine Stylesheet-Sprache, mit der man das Erscheinungsbild von Elementen anpasst. Mehr ist CSS nicht. Kannst ja z.B. hier mal reinschauen, wie sie die Tabellen gestaltet haben
  8. Like
    Whiz-zarD hat eine Reaktion von Britanny erhalten in Projektantrag: Entwickeln einer Web-Anwendung „Zeitmanagement“ mit einer Datenbankanbindung   
    Mag sein, dass die Prüfer da nicht ganz so streng draufschauen, aber ich finde den Antrag sehr fragwürdig. Auf der einen Seite wird die Software als riesiges, komplexes Konstrukt dargestellt aber auf der anderen Seite wird gar nicht auf die komplexität eingegangen.
    Als erstes die Projektbeschreibung:
    Sie wird dargestellt als würde die Software komplexe Strukturen der Projekt-Phasen in unterschiedlichen Firmen abbilden, kontrollieren und steuern können. Die Aufgabe des Azubis ist die Implementierung der Basisfunktionalität. Gut, lassen wir es einmal so wirken.
    Bis jetzt liest sich wie ein PR-Text eines Vertrieblers, der den Wunschzustand erwähnt aber wünschen kann man sich ja vieles.  Zur Nutzen/Kostenberechnung sage ich mal nichts. Das würde jetzt den Rahmen sprengen. 
    Punkt 2:
    Hier wird es mal etwas konkreter, was die Software können soll:
    Projekte anlegen Projekte verwalten (auf welche Weise auch immer?) Projekte tabellarisch oder als Ganttdiagramm darstellen Das wars? Wo ist die erwähnte Steuerung und Kontrollierung der Projekte?
    Und das soll dann verkauft werden? Okay, vielleicht ist das auch nur die Basisfunktionalität. Wer weiß? 
    Punkt 3.1:
    Du erwähnst, dass das Projekt mittels dem Entwicklungsmodel des Wasserfallmodell und der agilen Entwicklung entwickelt werden soll. Beides schließt sich aber gegenseitig aus. Das Wasserfallmodel beruht auf sehr starren Phasen, während die agile Entwicklung bestmöglich gar nicht aus Phasen besteht. Agile Entwicklung beruht auf vier Grundsätze:
    Menschen und Interaktionen stehen über Prozessen und Werkzeugen Funktionierende Software steht über einer umfassenden Dokumentation Zusammenarbeit mit dem Kunden steht über der Vertragsverhandlung Reagieren auf Veränderung steht über dem Befolgen eines Plans Das Wasserfallmodell verstößt schon gegen drei der vier Grundsätze. Ich nehme mal an, du meinst eher das V-Modell.
    Punkt 3.2:
    Wieso wird so oft MySQL erwähnt? Mag ja sein, dass ihr MySQL verwendet aber ihr wollt die Software verkaufen, also muss sie auch mit anderen Datenbanken zurecht kommen. Angenommen, ein Kunde hat MSSQL oder OracleDB im Einsatz. Was dann?
    Punkt 3.3:
    In der Implementierungsphase wird mit vier Sätzen ein generisches Login-System beschrieben. Das Gantt-Diagramm muss mit einem Satz auskommen. Wie wird das Diagramm überhaupt generiert? Ist das eine HTML-Tabelle? Eine Grafik? Schreibst du die Bibliothek dafür selber? Wird da was fertiges verwendet? Die eigentliche Aufgabe der Software wird hier gar nicht beschrieben. Ist das generieren des Gantt-Diagramms überhaupt deine Aufgabe oder implementierst du nur das Login-System?
    Punkt 4:
    In der "Entwurf"-Phase designst du 9 Stunden lang Datenbank-Tabellen. Wozu? In der Phase "Implementierung" lässt du diese Tabellen mit dem Entity Framework generieren. Auch designst du in der "Entwurf"-Phase noch Views. Wozu? Weshalb? Welchen Grund haben die Views? Willst du die komplette Business-Logik als Views abbilden? Das halte ich für fatal. Ihr habt doch das mächtige Entity Framework und bitte sag mir jetzt nicht, dass ihr das Framework nicht richtig einsetzt und nur stumpf SQL-Queries mit der ExecuteQuery()-Methode ausführt. 
    Fazit:
    In der gesamten Projektbeschreibung wird aus meiner Sicht überhaupt nicht klar, was du machen möchtest. Es wird eine angeblich riesige Software vorgestellt, die aber in Wirklichkeit nur sehr klein ist und auf deine Aufgabe gehst du nicht mal ein. Der Hauptaugenmerk liegt irgendwie beim Login-System aber das kann doch nicht deine einzige Aufgabe sein. 
    Ansonsten das noch, was @stefan.macke schrieb.
  9. Like
    Whiz-zarD hat eine Reaktion von thereisnospace erhalten in C# OOP - Eure Meinung   
    Dein Problem mit dem '\r' ist folgendes:
    Du hast ein String, der aus mehreren Zeilen besteht. Das Zeilenende unter Windows wird standardgemäß mit "\r\n" dargestellt. Das sind nicht-druckbare Steuerzeichen. Das \r steht für "cariage return" und das \n für "new line". Reguläre Ausdrücke werden aber standardgemäß pro Zeile ausgewertet und das eine Zeile wirklich beendet ist, leitet nur das \n ein. Somit bleibt \r stehen.
    Wenn du es unbedingt mit einen regulären Ausdruck ermitteln möchtest, dann musst du es so schreiben:
    Regex rsource = new Regex("(?<=Source\\=)[^\r]+"); Aber reguläre Ausdrücke würde ich persönlich vermeiden. Die versteht doch kein Schwein. Ich würde durch jede Zeile der Ini-Datei iterieren und jede Zeile per Split() mit dem '='-Zeichen teilen. So habe ich den Schlüssel und den Wert getrennt und kann damit arbeiten. 
    Versuche aber mal die Settings und das Laden der Settings zu trennen. Also dass du eine Klasse Settings hast:
    public class Settings { public string source { get; set; } public string Destination { get; set; } } Und eine Klasse z.B. mit dem Namen SettingsLoader, die eine Methode bereitstellt, die die Settings aus der Ini-Datei lädt und dir eine Instanz vom Typ Settings zurückliefert. Also z.B:
    public class SettingsLoader { public Settings Load(string fileName) { ... } } So trennst du die Datenstruktur von der Logik. Angenommen, du hast neben der Ini-Datei als Quelle für die Einstellungen jetzt noch eine Datenbank. Dann müsstest du ja die Settings-Klasse um weitere Methoden aufblähen, die die Daten aus der Datenbank lesen und dann kommt vielleicht die dritte (z.B. aus Textboxen einer WinForms-Anwendung), vierte und fünfte Quelle. Dann wird die Klasse sehr riesig und unübersichtlich. Das möchte man aber nicht. Man sollte das Ziel verfolgen, eine Klasse so klein wie möglich zu halten. Einige sagen, eine Klasse darf nicht mehr als 200 Zeilen besitzen aber ich finde so dogmatisch sollte man es nicht halten, aber man sollte sich daran orientieren. Um also mehrere Quellen abzugrasen sollte man pro Quelle eine Klasse schreiben, die die Einstellungen einliest und zurückgibt.
  10. Like
    Whiz-zarD hat eine Reaktion von StefanE erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  11. Like
    Whiz-zarD hat eine Reaktion von thereisnospace erhalten in Wirtschaftsinformatik Ausbildung und dann FiAE?   
    Erst mal habe ich zuvor eine betriebliche Ausbildung als Mechatroniker hinter mir. Ich kenne also das Leben als Azubi. Zweitens hatte ich im Laufe der Zeit schon einige Gesprächen mit so manchen FIAE-Azubis gehalten und irgendwie berichtet jeder von den selben Problemen. Drittens hatte ich mich mal in diesem Forum angemeldet, weil ich Informationen über die Ausbildung sammeln wollte, da ich vor habe, die Ausbildereignungsprüfung abzulegen aber in diesem Forum werden immer wieder die Dinge bestätigt, die auch zuvor die Azubis berichtet haben, mit denen ich gesprochen habe und das erschüttert mich doch sehr. 
    Das mag vielleicht sein. Das kann ich so nicht beurteilen. Ich selber war auf einer Schule in Schleswig-Holstein und diese Schule genießt in Norddeutschland einen sehr guten Ruf und die Leute, die auf dieser Schule einen Abschluss gemacht haben, werden eher genommen, als jemand mit einer betrieblichen Ausbildung.
  12. Like
    Whiz-zarD hat eine Reaktion von sas86ks erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  13. Like
    Whiz-zarD hat eine Reaktion von mapr erhalten in Synchronisation einer Oracle DB mit einer MySQL DB   
    Ich nehme mal an, es handelt sich um eine FIAE-Ausbildung. Wieso bildet die Firma FIAEler aus, wenn keiner programmieren kann? Was lernst du in der Firma? Vor allem von wem?
  14. Like
    Whiz-zarD hat eine Reaktion von Chief Wiggum erhalten in Synchronisation einer Oracle DB mit einer MySQL DB   
    Ich nehme mal an, es handelt sich um eine FIAE-Ausbildung. Wieso bildet die Firma FIAEler aus, wenn keiner programmieren kann? Was lernst du in der Firma? Vor allem von wem?
  15. Like
    Whiz-zarD reagierte auf stefan.macke in Präsentation   
    Mh... und wer sagt, dass man Sachlichkeit nicht mit Humor kombinieren darf. Warum müssen Prüfungen langweilig sein? Weil "man" das so macht? Oder weil das Gehirn langweilige Inhalte besser aufnimmt? Wenn man sein Thema beherrscht und das auch durch den Inhalt der Präsentation (ich wiederhole mich) deutlich wird, warum soll man dann nicht auch mal eine witzige Einlage bringen? Wie gesagt, es geht nicht darum, mit lustigen Folien von völliger technischer Ahnungslosigkeit abzulenken.
    Ich sage auch nicht, dass jede Folie aus animierten GIFs bestehen soll (was ich durchaus schon einmal in einem - übrigens richtig coolen - Fachvortrag gesehen habe). Man sollte vielleicht einfach das richtige Maß finden. Weder nur Text und die geforderte "Sachlichkeit", noch ausschließlich lustige Bildchen. Die gute Mischung macht's!
    Du schaust dir also ernsthaft gerne "sachliche" (ich übersetze das jetzt für mich einfach mit "langweilige") Präsentationen an, die dich nicht involvieren oder visuell stimulieren? Wenn das so ist, kann ich wenig dazu sagen. Über Geschmack kann man nicht streiten. Nur wenn ich mich auf den großen (technischen) Konferenzen da draußen umschaue, scheint die Mehrheit der Zuschauer stimuliert und inspiriert werden zu wollen. Und sollen wir unsere Azubis nicht auf das wahre Leben vorbereiten? Besteht der Alltag da draußen wirklich aus Textwüsten und humorlosen Präsentationen? Ich hoffe nicht! Und wenn doch, ist es - meiner Meinung nach - Zeit das zu ändern.
  16. Like
    Whiz-zarD hat eine Reaktion von Gooose erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  17. Like
    Whiz-zarD hat eine Reaktion von el_pollo_diablo erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  18. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wie lernt man effektiv eine Programmier-Sprache?   
    Einfacher geht es kaum noch. Ich versuche es aber mal.
    Du hast eine For-Schleife. Im Kopf stehen folgende Informationen:
    Die Zählervariable heißt i  => int i = 0 i wird mit 0 initialisiert => int i = 0 Die Schleife läuft so lange, wie i kleiner 5 ist => i < 5 Pro Schleifendurchlauf wird i um 1 erhöht => i++ Das bedeutet, du hast 5 Schleifendurchläufe bei jedem Schleifendurchgang wird folgende Logik angewendet:
    if (i == 3) { result += 10; } else { result += i; } D.h. wenn i gleich 3 ist, dann wird auf result 10 addiert. Ansonaten i.
    Jetzt gehen wir die Durchläufe Schritt-für-Schritt durch:
    Durchlauf:
    i = 0, result = 0
    i ist nicht 3, also wird auf result 0 addiert. result ist 0; also: result = result + i = 0 + 0 = 0 Durchlauf:
    i = 1, result = 0
    i ist nicht 3, also wird auf result 1 addiert. result ist 0; also: result = result + i = 0 + 1 = 1 Durchlauf:
    i = 2, result = 1
    i ist nicht 3, also wird auf result 2 addiert. result ist 1; also: result = result + i = 1 + 2 = 3 Durchlauf:
    i = 3, result = 3
    i ist 3, also wird auf result 10 addiert. result ist 3; also: result = result + 10 = 3 + 10 = 13 Durchlauf:
    i = 4, result = 13
    i ist nicht 3, also wird auf result 4 addiert. result ist 13; also: result = result + i = 13 +4 = 17 Beim 6. Durchlauf hätte i den Wert 5 und somit entspricht i nicht mehr der Bedingung, dass es kleiner als 5 ist und bricht die Schleife ab. result ist dann 17 und wird in der Konsole ausgegeben.
  19. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wie lernt man effektiv eine Programmier-Sprache?   
    Und wo genau liegt das Problem? Gehen wir mal die Schleife durch:
    i    result ------------------------------------ 0    result += 0 =>    result = result + 0 => result = 0 + 0 = 0 1    result += 1 =>    result = result + 1 => result = 0 + 1 = 1 2    result += 2 =>    result = result + 2 => result = 1 + 2 = 3 3    result += 10 =>    result = result + 10 => result = 3 + 10 = 13 4    result += 4 =>    result = result + 4 => result = 13 + 4 = 17 Auf result wird pro Schleifendurchlauf immer i dazu addiert. Die Ausnahme ist, wenn der Zähler i bei 3 ist. Dann wird 10 draufaddiert. Der += operator ist nur eine Abkürzung
    result += i ist das selbe wie
    result = result + i
  20. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wie lernt man effektiv eine Programmier-Sprache?   
    Suche unter Google nach "coding katas" und versuche sie zu bearbeiten. Eine Liste findest du z.B. hier
  21. Like
    Whiz-zarD hat eine Reaktion von tegberts erhalten in Berufsschule? Sinnlos!   
    Dein Text klingt aber nicht so wirklich danach, als sei alles super.
    Sorry aber das, was ich von dir in diesem Forum gesehen habe, war keine objektorientierte Programmierung. Nur weil man eine objektorientierte Sprache verwendet, heißt es noch lange nicht, dass man auch Objektorientierung anwendet und Struktogramme braucht man im Alltag auch gar nicht. Struktogramme sind auch nicht einfacher zu lesen, als Code. Von daher kann man es auch gleich weglassen.
    Nicht nur das. Auch die Ausbildung gehört für mich komplett überarbeitet.
    Das ist echt schon abenteuerlich, was ich hier so über die Abschlussarbeiten- und Prüfungen lese.
    Die Azubis lernen veraltete und überholte Methoden, nur um die Prüfung bestehen zu können. Vieles, was sie in der Berufsschule lernen, können sie auch gleich nach der Abschlussprüfung wieder vergessen, weil es entweder in der Praxis gar nicht mehr angewendet wird oder so selten, sodass man sowieso erst mal einen Blick auf Google oder Wikipedia werfen muss. Meiner Meinung nach, setzt man auch die Prioritäten komplett falsch. Anstatt den Azubis drei Jahre lang Pseudo-Code und UML-Diagramme einzutrichtern, sollte man - vor allem den Anwendungsentwicklern - mehr sauberen Code beibringen aber offenbar ist selbst das in der Abschlussprüfung zu viel verlangt. Die offiziellen Lösungen der Programmieraufgaben in der Abschlussarbeit ist so ein schlechtgeschriebener Code, dass man davon Pickel bekommt, wenn man nur ihn ansieht. Von gravierenden Fehlern ganz zu schweigen.
    Was das Abschlussprojekt angeht, bin ich auch kein Freund vom Pflichten- und Lastenheft, was aber offenbar vielfach von der IHK gefordert wird. Das sind auch Fragmente aus einer alten Zeit. Ich habe inzwischen schon viele Projekte gesehen, wo ein Pflichten- und Lastenheft geschrieben wurde (ich habe auch schon sowas das eine oder andere Mal geschrieben) und ständig sind solche Projekte in die Hose gegangen, weil sich die Anforderungen ändern können oder gewisse Dinge sowohl vom Kunden als auch vom Entwickler nicht vollständig durchleuchtet worden waren. Beliebt sind auch Fehlinterpretationen. Sowohl Kunde als auch Entwickler schreiben den selben Wortlaut und dennoch reden sie einander vorbei. Viel wichtiger ist es, mit den Kunden ständig im Kontakt zu bleiben, damit man schnell auf Änderungen reagieren kann. Auch sollte man den Kunden immer mit dem Status informieren und ihm auch den derzeitigen Zwischenstand ungeschönt präsentieren und ihn nicht vor vollendeten Tatsachen stellen, wie es mit dem Pflichten- und Lastenheft der Fall wäre. Wenn man feste Termine mit den Kunden arrangiert, dann braucht man auch kein Lasten- und Pflichtenheft. Änderungswünsche können dann formlos festgehalten und im nächsten Meeting präsentiert werden.
    Ob und wann sich eine Entwicklung amortisiert hat, ist auch gar nicht die Aufgabe eines Entwicklers. Der Entwickler soll nur analysieren, wie aufwendig eine Implementierung ist. Mehr nicht. Auch lässt sich das als Entwickler oft gar nicht so einfach herausfinden, wie es um die Amortisierung steht. Dies ist nur bei In-House-Lösungen möglich. Wenn man eine Software für einen externen Kunden einführt, muss der Kunde diese Analyse vornehmen, weil der Entwickler kein Einblick in die Interna des Kundens hat. Auch eine Ist-Analyse ist hier gar nicht möglich, wenn der Kunde davon nicht redet.
    Wer in einem echten SCRUM-Team arbeitet, wird von solchen Analyse-Kram sogar ferngehalten. Nicht weil man den Entwickler dumm halten möchte, sondern weil jeder seine Kernkompetenzen besitzt und die Kernkompetenz eines Entwicklers ist die Softwareentwicklung und nicht das Ausloten der Wirtschaftlichkeit. Als Beispiel nehme ich mal diese Abschlussarbeit, die mit 100% bewertet wurde. Die Abschnitte "Projektkosten" und "Amortisationsdauer" lesen sich wie ein Standardtext, den man in jeder Abschlussarbeit reinknallen könnte. Diese Texte haben überhaupt keine Aussagekraft. Viel wichtiger wären doch technische Aspekte. In einem Nebensatz wird erwähnt, dass die Daten historisiert abgelegt werden sollen. Da werde ich als Entwickler doch sehr hellhörig, weil die Historisierung gar nicht trivial ist. Schon gar nicht in einer relationalen Datenbank. Da hätte ich schon ein Kapitel erwartet, dass sich mit diesem Thema auseinandersetzt. Was passiert z.B. wenn eine Spalte in einer Tabelle hinzukommt? Ich will ja nicht, dass die Azubis z.B. die Data Vault-Modellierung bis ins Detail verstehen und auch anwenden können aber wenn man schon so ein Thema anreißt, dann erwarte ich auch, dass man sich damit beschäftigt. Sei es auch nur, dass man die Risiken aufzählt. 
    Meiner Meinung nach müsste das gesamte System reformiert werden. Wer nach drei Jahren immer noch nicht verstanden hat, dass es keine if-Schleifen gibt, wird das nie verstehen. Ich finde, eine Berufsschule sollte dafür sein, um das in Firmen angelernte Wissen zu vertiefen und zu erweitern. z.B. mal ein Blick auf Datenstrukturen werfen, um ein Gespür zu bekommen, welche wann geeigneter ist. Der Unterschied zwischen einer verketteten Liste und einer Arrayliste ist wohl offenbar nur sehr wenigen bekannt oder dass in Java und C# Arrays als assoziative Arrays missbraucht werden, indem man den gesuchten Index als Konstante ablegt. Anstatt sinnlose Amortisierungsrechnungen aufzustellen, sollte man vielleicht mit den Azubis DDD üben (z.B. die Schule als Kunde) oder mit Hilfe von Coding Dojos sauberen Code oder TDD in Verbindung mit Continuous Testing üben. Auch wäre ein Blick auf funktionale Programmiersprachen nicht verkehrt, weil diese Sprachen immer mehr in kommen sind. Nicht weil sie trendige Hipster verwenden, sondern weil einfach gewisse Probleme, die man aus der Objektorientierung kennt, dort nicht existieren und daher für viele Aufgaben besser geeignet sind, als objektorientierte Sprachen.
    TL;DR:
    Gerade die IT-Welt ist so extrem wandelbar. Da ist es einfach nicht klug, einen Ausbildungsrahmenplan bis in alle Ewigkeit in Stein zumeißeln. Auch die Berufsschule und die IHK müssen hier wandelbar sein und Trends erkennen. Kein Azubi hat etwas davon, drei Jahre irgendeinen Quatsch zu lernen, was Prüfungsrelevant ist aber später keine Anwendung mehr findet. Eine Berufsschule sollte dafür da sein, ein Azubi auf den Berufsalltag vorzubereiten nicht nur für die Prüfung. Sie ist dafür da, Defizite, die in den Ausbildungsstätten vorkommen können, aufzuarbeiten und den Azubis ein Blick über den Tellerrand zu ermöglichen.
  22. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  23. Like
    Whiz-zarD reagierte auf pr0gg3r in Warum solche Unterschiede zu Studenten?   
    Natürlich ist es nicht so, dass Studenten 10 Semester Spaß haben und dafür den Bachelor bekommen. Student sein ist genau so ein Fulltimejob: Hier ein Projekt, dort eine Prüfungsvorleistung, dort noch irgendwas. Vor allem gegen Prüfungsphase wird es schnell sehr viel mehr, als eine 40-Stunden-Woche. Ein großer Unterschied ist die Eigenverantwortung: man bekommt nicht gesagt, dass man hier oder da das zu lernen hat (wie in der Berufsschule), sondern man muss sich selbst disziplinieren und organisieren. Das Leben als Student ist aber generell ein ganz anderes, als als Schüler oder Azubi. Und wenn man das mag, ist ein Studium gar nicht so verkehrt (aus diesem Grund habe ich mich auch für ein Vollzeitstudium entschieden).
    Der nächste Unterschied ist, wie bereits gesagt, das fachliche. Ich (studiere Medieninformatik) hatte zB in Softwareentwicklung1 mehr Stoff innerhalb des ersten halben Semsetsers, als in der gesamten Berufsschule. Das selbige gilt für Netzwerktechnik, Datenbanken, BWL usw. Dann gibt es Module, die in der Berufsschule gar nicht behandelt wurden, wie zB IT-Recht, Mensch-Computer-Interaktion, Medienpsychologie, usw. Zusätzlich kann man sich je nach Wahl(pflicht)-Modulen noch weiter in bestimmte Richtungen spezialisieren.
    Ich war anfangs, in den ersten zwei Semestern, auch viel am zweifeln, ob ein Studium überhaupt das Richtige für mich ist, vor allem da ich bisher eigentlich immer eher der Praktiker als der Theoretiker war und zwar meine Datenbankabfragen zusammenbasteln konnte und anfangs nicht verstanden hatte, warum man das mathematisch oder wissenschaftlich hier und da ausdrücken können muss. Mittlerweile merke ich aber, wie ich an Problemlösungen (und nichts anderes ist IT im allgemeinen) bewusster herangehe. Habe ich früher zB einen Algorithmus einfach so programmiert, weil ich es mir im Kopf so ausgemalt habe, sehe ich nun die (mathematischen) Strukturen dahinter und kann das ganz anders ausdrücken/kommunzieren/implementieren.
    Ich möchte eigentlich gar nicht über das Geld reden, denn das war für mich nie eine Motivation.
  24. Like
    Whiz-zarD hat eine Reaktion von RipperFox erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  25. Like
    Whiz-zarD hat eine Reaktion von pl0nd erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...