Wie Komplexere Projekte korrekt Programmieren

agredo

Cadet 2nd Year
Registriert
Juni 2015
Beiträge
24
Hallo liebe Community,

ich Programmiere schon das zum zweiten Mal das gleiche Projekt, da ich mit meinem Codeaufbau nicht zufrieden bin. Der Code ist nicht Total Banane´, jedoch entspricht er nicht meine Anforderungen in Lesbarkeit.

Nun habe ich mir vorgenommen, mir eine professionellere Strukturierung anzueignen, um auch Code zu Programmieren, der von anderen gut gelesen werden kann. Primär aber auch von mir, nachdem ich einige Zeit den Code nicht mehr angeschaut habe, wieder damit vernünftig arbeiten kann.

Nun habe ich mich versucht schlau zu machen und musste feststellen, dass es dazu sehr schwer ist vernünftige Quellen zu finden.

Was ich erwartet habe sind grundlegende "Programmierrichtlinien" an die man sich halten sollte, um einen professionelleren Code zu Programmieren. Z.B. Wie man Klassen richtig anwendet, Wie man das Projekt strukturiert usw.

Kennt Jemand da Bücher, Videos oder hat Tipps?

LG

Agredo
 
Ich kenne "Weniger schlecht programmieren" aus dem O'Reilly Verlag [1]. Ich finde es eher mittelgut; die Ratschläge darin sind sicherlich nicht verkehrt, so wirklich konkret und innovativ allerdings auch nicht.

Vielleicht hilft es ja trotzdem!
 
Kennst du dich mit den "Software Design Patterns" aus? Google mal danach, es gibt eine menge Stoff dazu im Netz und auch Bücher.

Und falls du mit C/C++ arbeitest könnte dir diese Seite vielleicht hilfreich sein More C++ Idioms
 
Zuletzt bearbeitet:
Das Buch "Clean Code" ist vermutlich die Referenz auf dem Gebiet. UML würde ich jedenfalls vollständig ignorieren. Ich hab noch keinen guten Programmiere gesehen, der UML Diagramme zeichnet.
 
  • Gefällt mir
Reaktionen: Sgt_H4rtman, Brixto, PEASANT KING und eine weitere Person
agredo schrieb:
Was ich erwartet habe sind grundlegende "Programmierrichtlinien" an die man sich halten sollte, um einen professionelleren Code zu Programmieren. Z.B. Wie man Klassen richtig anwendet, Wie man das Projekt strukturiert usw.

Der eine Tipp wäre "Clean Code", was hier bereits erwähnt wurde und das andere wäre DDD (Domain Driven Design).
 
- Lange Methoden/Funktionen nach Möglichkeit vermeiden (z.B. durch sinnvolle Auslagerung in extra Methoden).
- Code, der sich irgendwie mehrfach nutzen lässt in eigene Methoden, anstatt zwei mal ähnlichen Code zu schreiben.
- Klassen möglichst abstrakt halten und Vererbung nutzen.
- Bei Programmieren von grafischem Kram den Code für die UI soweit möglich von der Logik unabhängig machen, siehe MVVM.

Damit kam ich bisher bei Projekten, die aus mehr als einer Code-Datei bestehen recht gut zurecht.

Sollte mir noch was einfallen, schreibe ich es dazu.
 
  • Gefällt mir
Reaktionen: T_55 und psYcho-edgE
Vielen Dank. für eure Tipps.
UML bringt ja tatsächlich nur was, wenn man Klassen korrekt anwendet. UMLs Programmieren ja nicht für mich :)
"Clean Code" und "Code Complete" klingt soweit gut :)

@Bagbag
ich vermute mal, dass genau solche schritte in Clean Code beschrieben werden. Aber genau nach so etwas habe ich gesucht :)

Vielen Dank für eure Hilfe.

kennt ihr vielleicht ein Open Source Projekt, welches in C# Programmmiert ist, an dem ich mir so etwas in "natura" anschauen kann?

Danke :)
 
Ein besonders tolles Beispiel habe ich jetzt nicht, da ich es selbst oft aufgrund von Faulheit nicht ganz durchziehe, aber zumindest für die ViewModels habe ich was: PastySharpClient.
Schau dir dort mal ViewModels/MainWindowViewModel.cs, MainWindow.xaml und MainWindow.xaml.cs an.

Wobei du in letzerer nicht viel mehr sehen wirst außer:
Code:
MainWindowViewModel _mainWindowViewModel = new MainWindowViewModel();
DataContext = _mainWindowViewModel;
doch genau das ist ja der Sinn dahinter :)

Was programmierst du denn? Vielleicht können wir dir dann konkrete Vorschläge zur Abstrahierung geben.
 
Zuletzt bearbeitet:
Bagbag schrieb:
- Klassen möglichst abstrakt halten und Vererbung nutzen.

Genau diesen Punkt mag ich nicht (mehr). Man bekommt in der Schule / an der Uni gelehrt, wie toll doch Vererbung ist und dann wird es mit aller Gewalt eingesetzt. Nach vielen Jahren in der Praxis halte ich es für besser, wenn der erste Reflex NICHT Vererbung ist.

"Aus dem Liskov Substitution Principle ergibt sich ferner die Empfehlung, über Vererbung sehr genau nachzudenken. In den allermeisten Fällen ist die Komposition der Vererbung vorzuziehen (Favor Composition over Inheritance). Bei der Vererbung sollte man in jedem Fall über das Verhalten nachdenken, nicht nur über die Struktur. Statt Vererbung alsis-a Relation zu betrachten und dabei nur die (Daten-)Struktur zu bedenken, sollte man besser von einer behaves-as Relation ausgehen und das Verhalten der Klasse berücksichtigen."

(Quelle: Link)

Wie im Zitat erwähnt, immer in behaves-as denken, statt in is-a, wenn es um Vererbung und Komposition geht. Das führt mich gleich zum nächsten Punkt. Viele Leute meinen, dass sie objektorientiert programmieren. Schaut man sich die Klassen an, dann sind das meistens nur Datencontainer. Methoden? Fehlanzeige, außer set/get. Die Implementierung des Verhaltens ist konzentriert auf wenige Klassen, die denn Manager/Helper oder was auch immer heißen und eine hohe Kohäsion zu den umliegenden Klassen haben. Meistens ist das ein sehr, sehr schlechtes Design.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: psYcho-edgE
Software Engineering ist ein umfangreiches Thema.
Wenn es darum geht, "guten" Code zu schreiben, dann kann ich http://clean-code-developer.de/ empfehlen.
Dort geht es darum schrittweise, in Graden, die Clean Code Prinzipien anzuwenden.
Ich empfehle dir einfach mal die Seite komplett durchzulesen. Die Praxis beginnt dann mit dem roten Grad: http://clean-code-developer.de/die-grade/roter-grad/

Ich persönlich habe auf meinem Schreibtisch ein Cheat-Sheet mit den Prinzipien liegen, damit ich an diese immer wieder erinnert werde.

Zum Thema Komplexität bzw. warum die SW erneut schreibst: Um welche SW handelt es sich genau, wenn das kein Geheimnis ist? Hintergrund der Frage: Neben Clean Code spielt auch die Software-Architektur eine wichtige Rolle. Einfach drauf los programmieren ohne sich vorher grob Gedanken zu machen, wie man den Code strukturiert, führt meist zu Chaos. Für gängige Software wie z.B. Webanwendungen gibt es Referenzarchitekturen an denen man sich orientieren kann. Ansonsten benötigt das Erstellen einer guten SW-Architektur Erfahrung, die du gerade mit Learning by doing sammelst. Wie bereits meine Vorposter gesagt haben, solltest du auch mal einen Blick auf Design Patterns werfen.

Falls es sich um eine Desktop-Anwendung mit grafischer Oberfläche handelt, könnte folgender Artikel hilfreich sein: http://martinfowler.com/eaaDev/uiArchs.html
 
Faust2011 schrieb:
Genau diesen Punkt mag ich nicht (mehr). Man bekommt in der Schule / an der Uni gelehrt, wie toll doch Vererbung ist und dann wird es mit aller Gewalt eingesetzt. Nach vielen Jahren in der Praxis halte ich es für besser, wenn der erste Reflex NICHT Vererbung ist.

Zwanghafter Einsatz ist sicher verkehrt, doch oft ist es hilfreich, wie ich es z.B. mal hier gemacht habe: github.

Am beginn habe ich ein Bild, dass dann verkleinert, gefiltert und analysiert werden muss. Mit den Ergebnissen sollte dann weitergearbeitet werden. Da das ganze Periodisch geschieht und es deshalb auf mehreren Kernen/Threads ausgeführt werden soll, habe ich mir gedacht, ich mache das ganze im Prinzip wie Fließbandarbeit.

Als Grundgerüst gibt es WorkerBase, das ist der Eingang, hier entsteht der Rohstoff. Dann gibt es QueueWorkerBase, das erbt WorkerBase und implementiert zusätzlich noch eine Queue, in die die Produkte der Workers davor hinein kommen.

Dann gibt es noch jeweils die Worker, die QueueWorkerBase erben und in denen dann lediglich die Arbeit, die sie verrichten, implementiert wird, die dann die verarbeiteten Produkte an den nächsten Worker übergeben.

So habe ich ein sehr flexibles System, bei dem ich Worker beliebig austauschen, entfernen und hinzufügen kann - da alle, dank der Vererbung, kompatibel zueinander sind. Eine Änderung am Fließband (WorkerBase) (z.B. die Geschwindigkeit), wird somit automatisch an alle Arbeiter weitergegeben.

Am Ende kam dann sowas bei raus:
Code:
//Als Parameter wird jeweils der Arbeiter, an den das Produkt übergeben werden soll angegeben.
_aimingQueueWorker = new AimingQueueWorker();
_targetsFinderQueueWorker = new TargetsFinderQueueWorker(_aimingQueueWorker);
_filterQueueWorker = new FilterQueueWorker(_targetsFinderQueueWorker);
_resizeQueueWorker = new ResizeQueueWorker(_filterQueueWorker);
_screenshotWorker = new ScreenshotWorker(_resizeQueueWorker);

Ein weiteres Beispiel, was du dir anschauen kannst und das ich im selben Projekt geschrieben habe: github.
Die Filterung für das Bild soll nicht nur mittels RGB, sondern auch mit HSL erfolgen können.
Anstatt
Code:
if (selectedFilter == rgb)
    rgb.filter(pixel);
else if (selectedFilter == hsl)
    hsl.filter(pixel);
schreibt man dann einfach:
Code:
selectedFilter.filter(pixel);
was insbesondere dann, wenn es mehr als nur 2 Dinge sind, das ganze sehr vereinfacht.
 
agredo schrieb:
Nun habe ich mir vorgenommen, mir eine professionellere Strukturierung anzueignen, um auch Code zu Programmieren, der von anderen gut gelesen werden kann. Primär aber auch von mir, nachdem ich einige Zeit den Code nicht mehr angeschaut habe, wieder damit vernünftig arbeiten kann.
Was bislang noch nicht erwähnt wurde, aber speziell hierführ sehr nützlich ist: kommentiere deinen Code. Umschreibe, was er machen soll. Und schreibe bei jeder Funktion/Methode oder Klasse, wozu die gut ist. Selbst wenn du der Meinung bist, dass es doch offensichtlich sei. Jemand anderes ist es vielleicht nicht, und du selbst bist es vielleicht auch irgendwann nicht mehr.

Noch etwas zur Vererbung: wenn du in einer abgeleiteten Klasse eine Methode einer Basisklasse aufrufst, ist es zuweilen nützlich erkennbar zu machen, dass es sich um eine Methode der Basisklasse handelt. Sei z.B. Foo die Basisklasse und bar() die betreffende Methode, so geht das in C++ so:
Code:
int result = Foo::bar()
oder in C# so
Code:
int result = base.bar()

EDIT: Ach ja, noch etwas. Vermeide es, Code zu verfassen, der für tausend unterschiedliche Dinge zuständig ist. Die durch solchen Code erzielte Abstraktion mag intellektuell befriedigend sein, ist aber eine Katastrophe, wenn du mal einen Feher suchen und dazu durch den Code durchdebuggen musst. Ich habe mal ein Projekt gesehen, in dem es eine Klasse "Feature" gab, und alle möglichen Objekte in diesem Projekt waren Instanzen dieser Klasse, da war es verdammt schwierig, beim Debuggen genau das Objekt zu finden, das einen gerade interessierte.
 
Zuletzt bearbeitet:
Bagbag schrieb:
Zwanghafter Einsatz ist sicher verkehrt, doch oft ist es hilfreich, wie ich es z.B. mal hier gemacht habe: github.
....

​Was wär hier jetzt der konkrete Vorteil gegenüber ganz normal Dependency Injection ohne das ganze Vererbungszeug?


Was bislang noch nicht erwähnt wurde, aber speziell hierführ sehr nützlich ist: kommentiere deinen Code. Umschreibe, was er machen soll. Und schreibe bei jeder Funktion/Methode oder Klasse, wozu die gut ist. Selbst wenn du der Meinung bist, dass es doch offensichtlich sei. Jemand anderes ist es vielleicht nicht, und du selbst bist es vielleicht auch irgendwann nicht mehr.
​Was noch viel wichtiger ist: Selbstsprechender Code -> Variablen Namen und Funktionen sind kurz und eindeutig nach dem benannt, was sie machen. -> Code "ersetzt" den Kommentar
Wenn man den Code auf Anhieb versteht ist es schon besser als, wenn man anhand des Kommentars darüber rekonstruieren muss, was da genau gemacht wird.

​Statt
Code:
//Summanden
auto var1=5,var2=4; 
​//Addition der Summanden
​auto var3 = calc(var1,var2);
Lieber:
Code:
auto summand1=5, summand2=3;
​auto Summe=calcSum(summand1,summand2)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: psYcho-edgE und Miuwa
The Ripper schrieb:
​Was wär hier jetzt der konkrete Vorteil gegenüber ganz normal Dependency Injection ohne das ganze Vererbungszeug?

Der Ausdruck Dependency Injection ist mir neu, doch nach kurzem Googlen... Ist das einfach nur die Nutzung von Interfaces?

Falls ja: In einem Interface kann ich lediglich Methoden bestimmen, die eine Klasse, die das Interface nutzt, implementieren muss. Bei einer Basisklasse kann ich aber auch selbst schon Code implementieren (bei mir bspw. das erstellen des Thread), was ich dann in den vererbenden Klassen nicht mehr muss.
 
Bagbag schrieb:
Der Ausdruck Dependency Injection ist mir neu, doch nach kurzem Googlen... Ist das einfach nur die Nutzung von Interfaces?
.

Ähm nein. DI ist eine Möglichkeit die Kopplung zwischen den Klassen zu lösen. Interfaces sind daran beteiligt, jedoch nicht Kern der DI. Folgender Artikel ist zwar bereits 12 Jahr alt, erklärt aber die Grundlagen recht gut: http://www.martinfowler.com/articles/injection.html

In Java kann ich z.B. folgendes schreiben:
Code:
public class MeinService
{
      @Inject
      private Datenbank db;

      public void macheWas()
      { // Hier kann ich die Datenbank ganz normal verwenden, ohne "new" zu verwenden
      }
}

Die Abhängigkeit wird dann zur Laufzeit injeziert. (Hierzu muss das verwendete DI-Framework passend konfiguriert sein) Ob ich dann eine In-Memory oder irgend eine andere Art von Datenbank bekomme, sehe ich an meiner Klasse nicht. Zur Laufzeit interessiert mich das womöglich auch nicht. Vorteil: Ich bin nicht von einer konkreten Datenbank abhängig. Wohin mein Service die Daten schreibt, spielt für den Service keine Rolle.
 
- sprechende Variablen- und Methodenbezeichner verwenden! lieber mal länger über einen sinnvollen Namen nachdenken als schnell ein "num" oder "perform" hinzurotzen
- Keine Abkürzungen, Kram ausschreiben
- Kurze Methoden
- möglichst seiteneffektfrei programmieren, Boolean-Prädikate und Seiteneffekte trennen
- Belange trennen
- auch mal Zeug von anderen reviewen lassen
- bei C++ Const-Correctness von Anfang an
- keine Hacks, außer die Stelle ist performancekritisch und da liegt erwiesenermaßen (!) der Flaschenhals
- öfter mal die Sinnfrage stellen: wieso mache ich das so? Brauche ich das so? Overengineere ich gerade?!
- Blick auf die Domaene nicht verlieren; versuchen mehr als Domaenenmensch denn als Programmierer zu denken

Weiter oben genannt, aber immer wieder zu empfehlen:

- "Clean Code" und "Clean Coder" lesen
 
Domäne = die Fachsprache, in der Du Dein Problemfeld löst.

Baust Du z. B. eine Finanzindustrie-Anwendung, dann würde ich im Code Methodennamen und Bezeichner erwarten, die irgendwas von Risiko, Marge, Cashflow, .... im Namen tragen.
 
  • Gefällt mir
Reaktionen: psYcho-edgE
Zurück
Oben