Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

BurningWave

Alter Hase

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

71

03.09.2010, 21:36

@MasterK
Was ist das für ein Code-Design, wenn man davon ausgeht, dass man einen Compiler (bzw. Editor), der Instellisense hat, benutzen muss, um einen Zeiger von einer normalen Variable zu unterscheiden, ohne dauernd in Klassendefinitionen nachzusehen? Meiner Meinung nach sind gerade Bezeichnungen wie a für Array, r für Referenz und p für Pointer die Wichtigsten, m_ und g_ sind vor allem für Personen wichtig, die den Code später verstehen sollen und die restlichen Präfixe bringen tatsächlich nicht viel.

Es gibt viele Fälle, wo man Pointer nicht leicht von Referenzen und normalen Variablen unterscheiden kann, insbesonders dann, wenn man sehr lange Funktionen hat.

MasterK

Frischling

Beiträge: 92

Wohnort: Koblenz

Beruf: Teamleiter Softwareentwicklung

  • Private Nachricht senden

72

03.09.2010, 22:22

Was ist das für ein Code-Design, wenn man davon ausgeht, dass man einen Compiler (bzw. Editor), der Instellisense hat, benutzen muss, um einen Zeiger von einer normalen Variable zu unterscheiden, ohne dauernd in Klassendefinitionen nachzusehen?
Wenn du so viel code hast, dass du das nicht erkennst, sind deine klassen eh zu gross.

Meiner Meinung nach sind gerade Bezeichnungen wie a für Array, r für Referenz und p für Pointer die Wichtigsten, m_ und g_ sind vor allem für Personen wichtig, die den Code später verstehen sollen und die restlichen Präfixe bringen tatsächlich nicht viel.

Wie gesagt, "m_" lass ich ja noch durchgehen. Globale variablen sollte man eh nicht haben. Und warum ein "a" für "array", wenn du zB für vector keine besondere notation verwendest?

Es gibt viele Fälle, wo man Pointer nicht leicht von Referenzen und normalen Variablen unterscheiden kann, insbesonders dann, wenn man sehr lange Funktionen hat.

Da schreibst du doch schon den fehler, "sehr lange funktionen". Stell lieber das ab statt irgendeine kryptografische notation zu nutzen.

BurningWave

Alter Hase

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

73

03.09.2010, 22:29

Und warum ein "a" für "array", wenn du zB für vector keine besondere notation verwendest?

Weil ein Vector eben eine einfache Struktur ist und ein Array einen Speicherblock bezeichnet, den man anders handhabt, als Strukturen oder einfache Variablen.

Und zur Länge von Klassen und Funktionen: Es gibt sehr wohl Fälle, wo eine Funktion 100-200 Zeilen lang ist und wo es keinen Sinn macht, die Funktion in 10 Teilfunktionen auszulagern.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

74

03.09.2010, 23:12

Da kannst du sagen, was du willst, manche Klassen sind eben groß und abstrakt, genau wie manche Methoden eben auch, egal wie weit man sie auf andere Methoden verteilen mag (was damit die Größe der Klasse wiederum beeinflusst).

Ich sehe nicht inwiefern zwischen Größe und Abstraktheit irgendein allgemeiner Zusammenhang besteht. Ist es nicht gerade ein wichtiges Grundprinzip wiederholt auftretende Teilprobleme zu identifizieren, zu abstrahieren und damit vom Rest zu isolieren? Man könnte argumentieren dass man mit Abstraktion sehr oft eigentlich versucht Dinge klein und überschaubar zu halten.
Die mit Abstand längste Methode in meinem letzten Projekt hat ca. 120 Zeilen Brutto (und das auch nur weil ich das Ding da einfach nurmehr fertig haben wollte und deswegen ein wenig Pasta in Kauf genommen habe), praktisch alle anderen Methoden signifikant weniger.

Die Aussage, dass die Klasse oder Methode dann wohl zu groß ist, die weist direkt darauf hin, dass du die Variable also doch nicht aus dem Kontext erkennst, sondern es mit einem zweiten Blick auf die Klasse/Methode nachschaust - egal wie groß oder klein die Klasse/Methode auch sein mag, du brauchst also den zweiten Blick. Das ist aber genau das, was mit z.B. "m_" oder "_" oder wasauchimmer verhindert werden soll.

Nein ich brauche keinen zweiten Blick. Wenn ich eine Klasse Player habe und weiß dass der Player einen Namen hat, dann ist mir klar dass name ein Member ist der ein String ist der eben diesen Namen beinhaltet. Ein m_name würde hier imo nur den Lesefluss stören, genauso wie strName oder was auch immer sonst.

Meine Frage scheint damit geklärt. Anstatt ein schönes Beispiel zu bringen, hast Du Dich nur darüber ausgelassen, was wohl an meinen Sachen falsch wäre.
Sehr schade, ich hatte auf ein konstruktives Beispiel gehofft.

Eigentlich war es nicht meine Intention mich "darüber auszulassen was an deinen Sachen falsch wäre". Fakt ist dass ich deinen Code nicht kenne und darüber weder urteilen kann noch vorhabe dies zu tun. Ich finde die Diskussion hier läuft soweit eigentlich erfreulich sachlich ab und habe auf keinen Fall vor dich in irgendeiner Form persönlich anzugreifen, falls das so rübergekommen ist möchte ich mich dafür entschuldigen. Ich habe lediglich versucht aufzuzeigen wo ich in einem Fall wie du ihn beschrieben hast das Problem sehe und wie meiner Meinung nach die eigentliche Lösung aussehen würde sowie angemerkt welchen Teil meiner früher geäußerten These ich hier angesprochen sehe.
Ich habe kein Beispiel gepostet weil ich nicht so ganz wusste was genau ich mit diesem Beispiel zeigen sollte. Beispiele eignen sich etwa wenn man ein Negativbeispiel anführen will um auf ein Problem hinzuweisen. Meine Argumentation war aber dass Ungarische Notation in gutem Code nicht notwendig ist. Da man aber von einem Beispiel nicht auf die Allgemeinheit schließen kann sehe ich in einem Beispiel kein geeignetes Mittel um meine Aussage zu untermauern. Wir können die Sache aber umdrehen. Ich zeige dir ein Beispiel und du sagst mir wo du Probleme mit der Leserlichkeit siehst und inwiefern eine Notation deiner Wahl hier Abhilfe schaffen würde. Der Fairness halber habe ich eine nicht ganz saubere und relativ unübersichtliche Methode gewählt die ich mir unlängst mal schnell aus den Fingern gesaugt hab:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
void AnimatedModel::load(const char* name)
{
  Resource::File file(name, "MODEL");

  const TowerModelFileFormat::Header& header = file.get<TowerModelFileFormat::Header>();

  if (header.magic != TowerModelFileFormat::magic_id)
    throw std::runtime_error("Invalid File Format");

  size_t vertex_count = file.get<TowerModelFileFormat::size_t>();
  vertices.resize(vertex_count);

  for (size_t i = 0; i < vertex_count; ++i)
  {
    const TowerModelFileFormat::Vertex& v = file.get<TowerModelFileFormat::Vertex>();
    vertices[i].pos[0] = v.pos[0];
    vertices[i].pos[1] = v.pos[1];
    vertices[i].pos[2] = v.pos[2];
    vertices[i].n[0] = v.n[0];
    vertices[i].n[1] = v.n[1];
    vertices[i].n[2] = v.n[2];
    vertices[i].t[0] = v.t[0];
    vertices[i].t[1] = v.t[1];
    vertices[i].t[2] = v.t[2];
    vertices[i].b[0] = v.b[0];
    vertices[i].b[1] = v.b[1];
    vertices[i].b[2] = v.b[2];
    vertices[i].texcoord[0] = v.texcoord[0];
    vertices[i].texcoord[1] = v.texcoord[1];
    vertices[i].bone = v.bone;
  }

  size_t face_count = file.get<TowerModelFileFormat::size_t>();
  size_t index_count = 3 * face_count;
  indices.resize(index_count);

  for (size_t i = 0; i < face_count; ++i)
  {
    const TowerModelFileFormat::Face& t = file.get<TowerModelFileFormat::Face>();
    for (int j = 0; j < 3; ++j)
      indices[3 * i + j] = t.v[j];
  }

  size_t material_count = file.get<TowerModelFileFormat::size_t>();
  groups.resize(material_count);

  for (size_t i = 0; i < material_count; ++i)
  {
    const TowerModelFileFormat::Material& material = file.get<TowerModelFileFormat::Material>();
    if (material.texture_name[0] != '\0')
    {
      std::string tex_name = material.texture_name;
      try
      {
        groups[i].diffuse_tex = GL::Texture2D((tex_name + ".diffuse").c_str());
        groups[i].normal_map = GL::Texture2D((tex_name + ".normal").c_str());
      }
      catch (...)
      {
      }
      groups[i].start_index = material.start * 3;
      groups[i].index_count = material.face_count * 3;
    }
  }

  size_t joint_count = file.get<TowerModelFileFormat::size_t>();

  for (size_t i = 0; i < joint_count; ++i)
  {
    const TowerModelFileFormat::Joint& j = file.get<TowerModelFileFormat::Joint>();

    SbRotation rot  = SbRotation(SbVec3f(1.0f, 0.0f, 0.0f), j.rotation[0]);
               rot *= SbRotation(SbVec3f(0.0f, 1.0f, 0.0f), j.rotation[1]);
               rot *= SbRotation(SbVec3f(0.0f, 0.0f, 1.0f), j.rotation[2]);

    SbMatrix mat;
    mat.setTransform(SbVec3f(j.position[0], j.position[1], j.position[2]),
                     rot,
                     SbVec3f(1.0f, 1.0f, 1.0f));

    joints.push_back(Joint(mat, i, j.parent == -1 ? 0 : &joints[j.parent], j.parent));
  }

  size_t anim_count = file.get<TowerModelFileFormat::size_t>();

  for (size_t i = 0; i < anim_count; ++i)
  {
    const TowerModelFileFormat::Animation& anim = file.get<TowerModelFileFormat::Animation>();

    Animation::channel_vector channels(joint_count);

    for (size_t j = 0; j < joint_count; ++j)
    {
      channels[j] = Animation::Channel(file);
    }

    animations.insert(std::make_pair(anim.name, new Animation(*this, channels)));
  }


  for (size_t i = 0; i < vertex_count; ++i)
  {
    if (vertices[i].bone != -1)
    {
      SbVec3f pos;
      const SbMatrix& absolute = joints[vertices[i].bone].bind_absolute();
      SbMatrix inv_absolute = absolute.inverse();
      inv_absolute.multVecMatrix(vertices[i].pos, pos);
      vertices[i].pos = pos;
    }
  }

  vb = GL::VertexBuffer<Vertex>(&vertices[0], vertex_count);
  ib = GL::IndexBuffer<index_t>(&indices[0], index_count);
}

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »dot« (04.09.2010, 00:06)


Fred

Supermoderator

Beiträge: 2 121

Beruf: Softwareentwickler

  • Private Nachricht senden

75

03.09.2010, 23:38

Früher habe ich die ungarische Notation auch mal verwendet. Bessere Lesbarkeit und so einen Quatsch redet man sich da ein. Verwendet man sie dann schließlich nicht mehr, stellt man fest, dass die Lesbarkeit sogar eher noch verbessert wird(wobei ich mir angwöhnt habe, Code mit ungarischer Notation zu lesen als sei diese nicht vorhanden - ich bemerke die Präfixe meist gar nicht mehr).
m_ und g_ halte ich ehrlich gesagt für die unwichtigsten Bestandteile. C++ ist eine Objektorientierte Programmiersprache, was man sich wo immer möglich zu Nutze machen sollte und somit möglichst keine g_-Variablen besitzen sollte. Folglich existieren nur noch lokale Variablen und Membervariablen, die ich nicht unbedingt durch 2 Zeichen gesondert kennzeichnen muss. Ich handhabe es jedoch so, dass ich private Membervariablen soweit kennzeichne, dass ich einen Unterstrich als Suffix anfüge. Das beeinträchtigt die Lesbarkeit nur geringfügig und hilft mir auch mit IntelliSense noch, da ich so sofort weiß, ob ich direkt auf die Variable zugreifen darf oder ob ich evtl. eine Setter bzw Getter-Funktion nutzen muss. Das ist aber auch wirklich das einzige was ich verwende.
Arrays müssen meiner Meinung nach schon deshalb nicht gesondert gekennzeichnet werden, weil ich entweder genau weiß, was für ein Array das ist oder ohnehin nachschauen muss, wie das Array definiert ist, so dass ich nicht versehentlich auf irgendwelche Elemente zugreife, die im Array gar nicht existieren.
Pointer und Referenzen können ohne Notation zwar manchmal verwechselt werden, aber letztlich passiert mir das so selten, dass es eine zusätzliche Kennzeichnung nicht rechtfertigen würde, gerade da es leicht mal vorkommen könnte, dass ich aus einem Pointer schließlich doch eine Referenz mache oder andersherum. Dann müsste ich wieder alle Variablennamen ändern und das ist mal wirklich nervig.
Das wichtigste ist meiner Meinung nach verständliche, eindeutig definierte Variablen und Funktionsnamen zu verwenden, dann brauche ich auch keine besoondere Notation und wenn der Umstand nicht gegeben ist, dann verbessert auch ein m_pX die Lesbarkeit meines Codes nicht im Mindesten.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

76

03.09.2010, 23:49

Pointer und Referenzen sind in C++ zwei grundsätzlich verschiedene Dinge. Wenn man Probleme hat die auseinanderzuhalten dann hilft einem ein gutes Grundlagenbuch mehr als irgendeine Notation :evil:

MasterK

Frischling

Beiträge: 92

Wohnort: Koblenz

Beruf: Teamleiter Softwareentwicklung

  • Private Nachricht senden

77

04.09.2010, 10:37

Und warum ein "a" für "array", wenn du zB für vector keine besondere notation verwendest?

Weil ein Vector eben eine einfache Struktur ist und ein Array einen Speicherblock bezeichnet, den man anders handhabt, als Strukturen oder einfache Variablen.

Ein argument hat fred ja schon gebracht. Hier noch andere:
Einen vector _nutze_ ich im allgemeinen genauso wie ein array, nämlich über den index-operator. Warum sollte ich das bei einem array wissen müssen, bei einem vector aber nicht? Im übrigen handhabt man einen vector auch anders als zB eine map. Warum da keine unterscheidung? Und was, wenn du dich entschliesst dein array gegen einen vector auszutauschen? Dann geht die umbenennerei wieder los. Benenn die variable nach dem was drinsteckt, nicht nach dem typ.

Und zur Länge von Klassen und Funktionen: Es gibt sehr wohl Fälle, wo eine Funktion 100-200 Zeilen lang ist und wo es keinen Sinn macht, die Funktion in 10 Teilfunktionen auszulagern.

Wenn die funktion nicht mehr lesbar ist, dann IST sie zu lang und beinhaltet zuviel verschiedene logik. Wenn eine funktion 200 zeilen lang ist und nur "Blockcode" enthält (also code, den man nicht wirklich aufteilen kann), dann ist dieser code (sollte es zumindest) so strukturiert, dass man ihn eh leicht überblicken kann. Ein paar sinnvolle kommentare (zum beispiel in dots code wären da einige wirklich sinnvoll) helfen da tausendmal mehr als irgendwelche prefixe.
Also: eine 200 zeilen lange funktion, die nicht gut lesbar ist, die ist zu LANG. Und das kann mir dann keiner erzählen, dass man die nicht in sinnvolle und gut zu wartende kleinere funktionen aufteilen kann.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

78

04.09.2010, 10:54

@dot:
Die Methode ist nur in sofern von mir kritisierbar, als dass da mehrere Case-Varianten verwendet werden, was aber generell ein Problem in C++ ist. Die Bezeichnungen der Variablen sind nicht groß kritisierbar, da fast nur lokale Variablen innerhalb der Spaghetti benutzt werden. Die Bezeichnung vb und ib finde ich allerdings drastisch kurz und da musste ich erst nochmal die gesamte Methode durchsehen um herauszufinden, ob du so wirklich Member nennen würdest. Das bestätigt mich in so fern, als dass man bei diesem Code entweder erst die gesamte Methode durchscrollen oder die Klassen-Definition lesen müsste. Dass für den Code keine Ungarische Notation notwendig oder sinnvoll ist, da stimmen wir ja eh beide seit Anfang an überein. Eine (irgendeine) Notation der Member hätte meiner Meinung nach in diesem Beispiel dennoch geholfen.

@MasterK:
Kommentare sind Code-Smell und würden in diesem Fall darauf hindeuten, dass der Code zu lang und unübersichtlich ist. ;)
Was mich an solchem "Lange Methoden aufsplitten" nur immer stört, das ist der Overhead im Code. Da entstehen dann oft 3-4 Methoden, die nur zur Lesbarkeit einer einzelnen Methode beitragen, aber sonst nur die Lesbarkeit der gesamten Klasse verschlimmern und diese scheinbar aufblähen. Lösungsvorschläge um beides 'sauber' zu bekommen?
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

MasterK

Frischling

Beiträge: 92

Wohnort: Koblenz

Beruf: Teamleiter Softwareentwicklung

  • Private Nachricht senden

79

04.09.2010, 11:29

Kommentare sind Code-Smell und würden in diesem Fall darauf hindeuten, dass der Code zu lang und unübersichtlich ist. ;)

Kommentare sind kein "code smell", sondern ermöglichen es, direkt zu erkennen was ein bestimmer codeblock macht. Dots beispiel ist da eigentlich super... da sind mehrere blöcke von schleifen, wo ich mir erst die schleifen genau anschauen muss um zu wissen, was da eigentlich warum passiert. Ein kurzer kommentar sagt mir das sofort. Und irgendeine prefix-notation bringt mir in so einem fall genau gar nix. Dort hab ich absolut nichts davon zu wissen, ob eine variable nun ein lokales array ist oder nicht. In der regel will man wissen, WAS passiert, nicht das WIE. Das "wie" bekommt man eh durch lesen des quellcodes raus. Aber darauf habe ich bei einem projekt mit 100k zeilen und mehr wirklich keine lust.


Was mich an solchem "Lange Methoden aufsplitten" nur immer stört, das ist der Overhead im Code. Da entstehen dann oft 3-4 Methoden, die nur zur Lesbarkeit einer einzelnen Methode beitragen, aber sonst nur die Lesbarkeit der gesamten Klasse verschlimmern und diese scheinbar aufblähen. Lösungsvorschläge um beides 'sauber' zu bekommen?

Da man natürlich nur zu grosse funktionen aufsplittet, reden wir hier von einem aufblähen von vielleicht 5%. Wenn die klasse dadurch zu gross wird, ist sie doch eh zu gross. Dafür wird die eigentlich methode um so lesbarer:

Quellcode

1
2
3
4
5
6
7
8
9
function blablubb(a, b)
{
  c = machirgendwas(a)
  d = tuwasanderes(b)
  e = berechneergebnis(c, d)
  debug(e)

  return e
}

Auch nach monaten kann man schnell überblicken, was die funktion macht. Und wenn ich einen einzelnen teil ändern will, kann ich schnell dorthin springen und kann ebenfalls wieder schnell überblicken, was die funktion macht. Ganz abgesehen davon, dass der code natürlich auch wesentlich besser testbar ist zb in einem unit-test. Und auch beim debuggen ist das wesentlich angenehmer.

edit:
Und bei solch kurzen funktionen welche wiederrum andere sinnvoll benannte funktionen aufrufen spar ich mir dann sogar die kommentare ;)

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

80

04.09.2010, 12:23

Also bezüglich Code Smell von Kommentaren gibt es ziemlich "offizielle" Meinungen, die ich nur wiedergegeben habe. Ich selber würde auch eventuell Kommentar hinschreiben. Dennoch ist dieser unnötig bei besserer Aufsplittung.
Aber mal angenommen die Klasse von dot hat noch 3 weitere solcher Methoden. Gerade bei einer solchen Klasse wie "AnimatedModel" kommt sehr schnell sehr viel Code zusammen, ich sehe nicht, wie man den sinnvoll umgehen könnte. Aber mal zurück... angenommen er hat noch 3 weitere solch großer Methoden und fängt nun an diese sinnvoller aufzusplitten, dann hat er aus 4 Spaghetti-Methoden 16 Methoden gemacht, die alle gut lesbar und kurz sind. Aber was hilft ihm das, denn nun hat 12 Overhead-Methoden, die eigentlich keinen echten Klassen-Zweck erfüllen, sondern nur irgendwelchen privaten Code ausführen? Ist das nicht künstliches Aufblähen der Klasse? Wie verhindert ihr denn sowas, das ist mir irgendwie unklar. Alle sagen, man soll Klassen kurz halten und Methoden auch. Methoden haben das schon geschilderte Problem.
Bei Klassen sehe ich bei meinen konkreten Umsetzungen immer wieder, dass das irgendwie auch nicht geht. Keine Ahnung, wie das andere immer umsetzen.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige