Programmier"stil" C#

steppi

Commander
Registriert
Apr. 2012
Beiträge
2.158
Ich habe mal eine Frage zum Stil einer Programmierung.
Ich muss im Fernstudium zur Zeit wieder Programmieren (C#) und der Tutor legt auch Wert auf "Cleancode" bzw. einen guten Stil. Bisher habe ich im beruflichen Umfeld eher rudimentär "Spaghetti-Code" für kleine Sachen in den Abteilungen geschrieben. (bin kein Software-Entwickler)

Ist es besser/ sauberer Attribute in eigene Klassen auszulagern und diese dann mit Get und Set-Methoden zu versehen, wo ich diese brauche und die Instanz dann in der anderen Klasse recht global zu instanzieren? Oder kann man gern auf zusätzliche Klassen verzichten und legt die Attribute einfach "global" an und verwendet Sie durch den ganzen Code?

Also z.B.
Variante 1,
könnte ich Ball, Spielfeld, Spieler als Klassen darstellen, die jeweil dann z.b. eine Breite und eine Höhe haben.
Den Ball müsste ich mir dann in einer anderen Klasse, " in der das Spiel abläuft" instanzieren und dort wo ich es brauche via Get- und Set-Methoden die Attribute holen oder diese verändern.

Variante 2,
ich erstelle "global" in der Klasse, "in der das Spiel abläuft" Variablen und greife dann entsprechend, wo ich es brauche immer wieder auf diese globalen Variablen zu.

Sind beide Varianten gleichzusetzen oder bieten beide die gleiche Performance? Bei großen Projekten könnte ich mir vorstellen, dass die Lesbarkeit und Übersicht in Variante 1 höher ist, besonders wenn man je Klasse auch ein File erstellt. Gibt es Nachteile bei einer der Variante?


Beispiel V1:
C#:
public class Playground
    {
        int columns, rows;
        public void set_PG_Dimensions(int x, int y)
        {
            this.columns = x;
            this.rows = y;
        }
        public int get_PG_XDimensions()
        {
            return columns;
        }
        public int get_PG_YDimensions()
        {
            return rows;
        }
    }

public class Spiel : Form
    {
        static string filename;
        Playground playground = new Playground();
    
        public Spiel( )
        {
            ReadData();
            this.ClientSize = new Size(playground.get_PG_XDimensions() * 28, playground.get_PG_YDimensions() * 28);
        ...
        }   
        public void ReadData(){
            StreamReader sr = new StreamReader(".\\" + filename);
            playground.set_PG_Dimensions(int.Parse(sr.ReadLine()), int.Parse(sr.ReadLine()));
            ...

Beispiel V2:
C#:
public class Spiel : Form
    {
        static string filename;
        int columns, rows;

        public Spiel( )
        {
            ReadData();
            this.ClientSize = new Size(columns * 28, rows * 28);
            ...
        }    
        public void ReadData(){
            StreamReader sr = new StreamReader(".\\" + filename);
            columns = int.Parse(sr.ReadLine());
            rows = int.Parse(sr.ReadLine());
            ...


Was wäre denn der bessere Stil bzw. der saubere Code?
Übersichtlicher für die kleine Dinge, die ich da machen muss finde ich Variante 2.
 
Zuletzt bearbeitet:
Variante 2 hat eigentlich nur Nachteile, außer es geht wirklich nur mal schnell drum was einmaliges auszuführen. Und dann ist man mit der Variante auch nur dann schneller wenn man nicht großartig Fehler suchen muss, denn das kann in Spaghetti-Code auch schnell mühselig werden.

Aber schon bei deinem Beispiel ist es ein Vorteil den Spieler als Klasse anzulegen weil das Code spart und an einer Stelle zusammenführt. Üblicherweise gibts ja 22 Spieler bei einem Spiel ;-)
 
  • Gefällt mir
Reaktionen: steppi
Clean Code ist sehr breit gefächert.

Im eigentlichen Sinne würde ich auf folgendes achten:
  • Für alle eigenständigen "Dinge" wie "Ball, Spielfeld, etc" sollten eigene Klassen angelegt werden.
  • Um auf Variablen in anderen Klassen zuzugreifen sind immer Getter/Setter zu benutzen.
  • Wenn du in einer Methode mehrere Dinge tust, z.B. einen Spieler in das Spielfeld zu setzen und gleichzeitig den Ball anzustoßen, solltest du, gemäß Clean Code, diese Parts in eigene Methoden auslagern. Eine Methode sollte möglichst nur eine Sache tun.
  • Namen von: Klassen, Methoden, Variablen, etc sollten im Namen das beinhalten, was es tut. Z.B. die Methode stosseBallAn().
  • Wenn deine Methoden fertig sind, überdenke erneut dessen Namen. Erwähne ALLES, was es tut. Wenn die Methode nachher heißt fuehreSpielerInDasFeldUndStosseBallAnUnd... solltest du die entsprechenden Inhalte auslagern.
  • Kommentare sollten nur dann gesetzt werden, wenn sie zwingend notwendig sind um Entscheidungen zu erklären. Sie sind unnütz, wenn du nur wiederholst, was der Methodenname bereits aussagt.
  • Lieber den Code einfach halten und damit leicht lesbar, als mehrfach zu verschachteln.

Die Liste ist natürlich unvollständig, aber ein erster Hinweis.

Zu deinen Beispielen:
Variante 1 ist eher Clean Code, einfacher zu erweitern und zu warten.
Performanceprobleme gibt es gar keine. Auch sind viele lange Variablennamen gar kein Problem mehr, der Speicherplatz ist heute nahezu unbegrenzt (aus Sicht der einfachen Anwendung).

edit:
Methodennamen bitte vorne klein Schreiben (aber weiterhin im CamelCase) - ups, komme aus der Java-Welt^^
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: KitKat::new(), Kalsarikännit, steppi und 2 andere
steppi schrieb:
Beispiel V1:
C#:
public class Playground
    {
        int columns, rows;
        public void set_PG_Dimensions(int x, int y)
        {
            this.columns = x;
            this.rows = y;
        }
besser so:

C#:
public class Playground
    {
        public int Columns {get; set;}
        public int Rows {get; set;}
    }
 
  • Gefällt mir
Reaktionen: KitKat::new(), Autokiller677, SomeDifferent und eine weitere Person
  • Gefällt mir
Reaktionen: steppi
Für dein Fernstudium ist die 1. Variante ggf. besser (siehe auch was @Jesterfox kommentiert hat).
In der Realität der Spieleprogrammierung, wären keine deiner Typen Klassen sondern Strukturen und ggf. ref structs (wenn du denn ein aktuell genuges .NET nutzen würdest).
Endless Storm schrieb:
Methodennamen bitte vorne klein Schreiben (aber weiterhin im CamelCase)
Methodennamen vorne NICHT klein Schreiben, das ist nicht Java. siehe auch was @pcv-junkie gelinkt hat.
 
  • Gefällt mir
Reaktionen: 7r1c3, KitKat::new() und SomeDifferent
@Endless Storm
Wow, danke für deine Ausführung das hilft mir schon sehr weiter. Auch der Hinweis zu den Kommentaren, da es sich um eine Studienarbeit handelt, habe ich nämlich fast mehr Kommentare als Code, um den Tutor nochmal zu erklären warum ich Sachen so implementiert habe.

@Jesterfox
cooler Tip, dass macht Variante 1 sehr viel angenehmer!
Nur eine Frage, wie rufe ich die später wieder auf? Hab das mal auf die schnelle versucht zu implementieren und kann die get und set nicht aufrufen.

@All
Danke für den vielen und schnellen Input.
 
Tornhoof schrieb:
Für dein Fernstudium ist die 1. Variante ggf. besser (siehe auch was @Jesterfox kommentiert hat).
In der Realität der Spieleprogrammierung, wären keine deiner Typen Klassen sondern Strukturen und ggf. ref structs (wenn du denn ein aktuell genuges .NET nutzen würdest).

Methodennamen vorne NICHT klein Schreiben, das ist nicht Java. siehe auch was @pcv-junkie gelinkt hat.
Da hast du wohl recht. Ich hatte erstmal mit .Net 5.0 angefangen bzw. mit Core, musste aber zurück auf .Net 4.7.2 für die Aufgaben, da der Tutor das so fordert.

Im weiteren Studium werde ich aber wie Studienkollegen die nach neuer Prüfungsordnung schreiben auf Java oder Phython wechseln.
Ergänzung ()

Jesterfox schrieb:
Der Zugriff funktioniert dann über

C#:
playground.Columns = 20;
playground.Rows = 10;

Also als direkte Zuweisung statt eines Methodenaufrufs.
Das hatte ich versucht, da schreibt er mir "Zugriff ist aufgrund des Schutzgrads nicht möglich.
 
Ich Depp hab das public übersehen. Hatte es jetzt vor dem lesen deiner Antwort aber schon gefunden und korrigiert.

Vielen Dank nochmal!

So gefällt mir Variant 1 nochmal deutlich besser und macht auch die Vorteile für mich nochmal nachvollziehbarer.
 
  • Gefällt mir
Reaktionen: SomeDifferent
Tornhoof schrieb:
Methodennamen vorne NICHT klein Schreiben, das ist nicht Java. siehe auch was @pcv-junkie gelinkt hat.

Oh, danke für den Hinweis. Komme tatsächlich von der Insel Java^^
Habe meinen Post mal angepasst, damit es nicht andere noch verwirrt.
 
  • Gefällt mir
Reaktionen: breedmaster und steppi
steppi schrieb:
So gefällt mir Variant 1 nochmal deutlich besser und macht auch die Vorteile für mich nochmal nachvollziehbarer.
Jepp. Und wenn du doch noch eigenen Code in den Getter oder Setter einbauen musst kannst du das auch bei der Variante machen, ohne das sich der Zugriff von außen verändert (genau deshalb auch der Grundsatz alles über Getter/Setter zu machen, man kann viel einfacher erweitern)
 
gibt es da nicht sogar "Normen" für was ein guter Programmierstil ist ...

bei Spyder Python meckert er sogar wenn man gewisse Dinge 5 Felder statt 4 Felder einrückt.. beim Codecheck.

Obwohl das Ergebnis das gleiche ist.

Speziell das Groß und klein Schreiben wir ja auch da drinnen behandelt ..
 
Jesterfox schrieb:
Jepp. Und wenn du doch noch eigenen Code in den Getter oder Setter einbauen musst kannst du das auch bei der Variante machen, ohne das sich der Zugriff von außen verändert (genau deshalb auch der Grundsatz alles über Getter/Setter zu machen, man kann viel einfacher erweitern)
Also dann einfach den bestehenden Get mit einer neuen Methode Get überschreiben?
 
Getter und Setter sollte man nur sehr sparsam einsetzen.

Anstatt in der Klasse Spiel die Koordinaten des Spielers und die Größe des Spielfeldes abzufragen und dann zu berechnen, wie sich der Spieler bewegen darf, warum fragst du nicht einfach das Spielfeld, ob ein Spieler einen Schritt nach rechts gehen darf? Dann müsste zwar immer noch das Spielfeld die Koordinaten des Spielers kennen, aber die Abhängigkeit zum Spiel hin ist gelockert. Dann könnte man vielleicht später sogar einmal ein Spielfeld verwenden, das nicht rechteckig ist …

Der Architekturentwurf eines Programmes ist wie ich finde immer der schwierigste Schritt. Es zahlt sich aus, nicht einfach nur nach Schema vorzugehen, sondern wirklich zu überlegen, was sinnvoll ist und wie man die Strukturen, die man sich schafft, verwenden will und vorauszuschauen, wie man die Architektur so gestalten kann, dass bestimmte Programmierfehler von vornherein ausgeschlossen sind. Gleichzeitig ist es auch gut, einfach anzufangen und immer wieder Geschafftes über den Haufen zu hauen und nicht aufzugeben. Das ist mühsam, zahlt sich am Ende aber mit einem robusten Programm aus.
 
  • Gefällt mir
Reaktionen: BeBur, breedmaster und steppi
@steppi : Microsoft hat eine recht ausführliche Doku für sowas. Schau mal hier welche Möglichkeiten Properties bzw. getter und setter bieten.

In Visual Studio kann man das sogar mit Snippets abkürzen. Gib mal "prop" ein und drück doppelt Tab.
 
  • Gefällt mir
Reaktionen: steppi
plami schrieb:
Getter und Setter sollte man nur sehr sparsam einsetzen.

Anstatt in der Klasse Spiel die Koordinaten des Spielers und die Größe des Spielfeldes abzufragen und dann zu berechnen, wie sich der Spieler bewegen darf, warum fragst du nicht einfach das Spielfeld, ob ein Spieler einen Schritt nach rechts gehen darf? Dann müsste zwar immer noch das Spielfeld die Koordinaten des Spielers kennen, aber die Abhängigkeit zum Spiel hin ist gelockert. Dann könnte man vielleicht später sogar einmal ein Spielfeld verwenden, das nicht rechteckig ist …
Danke für den Hinweis. Wenn ich die Klassen mit den Getter und Setter weglasse, wäre ich im Code aber wieder bei globalen Variablen die ich dann in der/ über die Klasse Spiel in mindestens 4 Methoden verwende.

Deine Anmerkung werde ich mal durchdenken, wobei das hier im Thread nur ein Beispiel war.
In einer der eigentlichen Aufgaben, ist aber auch ein Spielfeld nötig, welches über eine externe Textdatei eingelesen werden soll, wobei Zeile 1 die Spalten des Feldes und Zeile 2 die Zeilen des Spielfeld angibt. Das Spielfeld folgt dann in den Zeilen darunter. Ein Zeichen im Spielfeld ist "frei" bzw. mit Leerzeichen und dort wird die Spielfigur platziert.
 
steppi schrieb:
Wenn ich die Klassen mit den Getter und Setter weglasse, wäre ich im Code aber wieder bei globalen Variablen
Nein, es geht nur darum Code direkt in die Getter und Setter zu packen, das sollte man nur sehr sparsam einsetzen.
 
  • Gefällt mir
Reaktionen: steppi
Zurück
Oben