Guter Code vs schlechter Code

Wichtiger Hinweis: Bitte ändert nicht manuell die Schriftfarbe auf schwarz sondern belasst es bei der Standardeinstellung. Somit tragt ihr dazu bei dass euer Text auch bei Verwendung unseren dunklen Forenstils noch lesbar ist!

Tipp: Ihr wollt längere Codeausschnitte oder Logfiles bereitstellen? Benutzt unseren eigenen PasteBin-Dienst Link
  • Moin ihr Brotfische da draußen,
    dieses Tutorial richtet sich an Neulinge, Fortgeschrittene und "alte Hasen" :D. Mein Ziel ist es, Euch für gutgeschriebenen Code zu sensibilisieren. Was ich damit meine? Nein, ich meine nicht den Inhalt, es geht in diesem Tutorial ausschließlich um Äußerlichkeiten. Ich hoffe die Beispiele sind anschaulich gewählt und erläutern die Sachverhalte angemessen. Für Kritik bin jederzeit zu haben. :thumbup: Auf geht's!



    Formatierung / Einrückung:

    Spoiler anzeigen

    Jeder kennt das Problem, man beginnt zu Scripten und schreibt viele Zeilen an Code, vergisst aber diese korrekt einzurücken. Als kleines worst-Case Beispiel:
    dcmd_setlevel(playerid,params[])
    {
    new pID, Level;
    if(sscanf(params, "ud",pID,Level))return SendClientMessage(playerid,0xFF0000FF,"Benutze: /setlevel [ID][Level]!"); //Falls nur /setlevel eingegeben wurde
    if (pID == INVALID_PLAYER_ID)return SendClientMessage(playerid,0xFF0000FF,"Spielerid ist nicht vorhanden!");
    // Falls eine falsche ID angegeben wurde, wird 0 returnt ;)
    if(Spieler[playerid][AdminLevel] > 3) // Hier wird abgefragt ob das Level des Spielers der den Befehl eingegeben hat größer als 3 ist.
    {
    new adminstring[128],levelsetter[MAX_PLAYER_NAME], playername[MAX_PLAYER_NAME];
    GetPlayerName(pID,playername,sizeof playername);
    format(adminstring,sizeof(adminstring),"Admins/%s.sav",playername);
    GetPlayerName(playerid,levelsetter,sizeof(levelsetter));
    Spieler[pID][AdminLevel] = Level; //Hier wird die Levelvariable mit dem neu eingegebenen Level überschrieben
    dini_IntSet(adminstring,"AdminLevel",Level); // Und hier das neue Level in der Datei gespeichert.
    format(adminstring,sizeof(adminstring),"%s hat dein Level auf %d gesetzt!",levelsetter, Level);
    SendClientMessage(pID,0x33FF33FF, adminstring);
    }
    else return SendClientMessage(playerid, 0xFF0000FF, "Dein Level reicht nicht aus!");
    // Falls das Level nicht ausreicht wird dieser Text gesendet.
    return 1;
    }

    Spoiler anzeigen
    Bei dieser Einrückung ist es fast schon vorprogrammiert, dass Übersicht sehr schlecht ist. Klammerfehler sind schwer zu entdecken und sonst ist auch nicht sofort offensichtlich, wo welcher Codeblock endet. Eindeutiger wäre es folgendermaßen:

    dcmd_setlevel(playerid,params[])
    {
    new pID, Level;
    if(sscanf(params, "ud",pID,Level))return SendClientMessage(playerid,0xFF0000FF,"Benutze: /setlevel [ID][Level]!"); //Falls nur /setlevel eingegeben wurde
    if (pID == INVALID_PLAYER_ID)return SendClientMessage(playerid,0xFF0000FF,"Spielerid ist nicht vorhanden!");
    // Falls eine falsche ID angegeben wurde, wird 0 returnt ;)
    if(Spieler[playerid][AdminLevel] > 3) // Hier wird abgefragt ob das Level des Spielers der den Befehl eingegeben hat größer als 3 ist.
    {
    new adminstring[128],levelsetter[MAX_PLAYER_NAME], playername[MAX_PLAYER_NAME];
    GetPlayerName(pID,playername,sizeof playername);
    format(adminstring,sizeof(adminstring),"Admins/%s.sav",playername);
    GetPlayerName(playerid,levelsetter,sizeof(levelsetter));
    Spieler[pID][AdminLevel] = Level; //Hier wird die Levelvariable mit dem neu eingegebenen Level überschrieben
    dini_IntSet(adminstring,"AdminLevel",Level); // Und hier das neue Level in der Datei gespeichert.
    format(adminstring,sizeof(adminstring),"%s hat dein Level auf %d gesetzt!",levelsetter, Level);
    SendClientMessage(pID,0x33FF33FF, adminstring);
    }
    else return SendClientMessage(playerid, 0xFF0000FF, "Dein Level reicht nicht aus!");
    // Falls das Level nicht ausreicht wird dieser Text gesendet.
    return 1;
    }

    Viel übersichtlicher oder? Das Einrücken des Codes ist entweder über die Leertaste oder die Tab-Taste möglich. Eine Einrückungstiefe von 2-3 Zeichen verhindert zudem, dass Euer Code außerhalb des Schirms landet. Gute Editoren wie z.B. Notepad++ oder ähnliche ermöglichen dies einzustellen und evtl. auch den Code automatisch einzurücken.

    Klammern:
    Spoiler anzeigen

    Wie kommen Klammerfehler zustande? Ein kleines Beispiel:

    CMD:test(playerid, params[])
    {
    if(IsPlayerAdmin(playerid)
    {
    if(IsPlayerInAnyVehicle(playerid)
    {
    if(IsPlayerNPC(playerid)
    {
    }}
    return 0;
    }

    Bei nur unzureichender Einrückung und schlechter Platzierung der Klammern vergisst man schnell eine Klammer. Es wäre allerdings auch so möglich:

    CMD:test(playerid, params[])
    {
    if(IsPlayerAdmin(playerid){
    if(IsPlayerInAnyVehicle(playerid){
    if(IsPlayerNPC(playerid){
    }
    }
    }
    return 0;
    }

    Letztlich ist es eine Geschmackssache, ob man die Klammern in eine neue Zeile schreibt oder noch in dieselbe.

    Redundanz im Code vermeiden:
    Spoiler anzeigen

    Was meine ich mit Redundanz im Code? Ganz einfach: Wiederholungen! ;) Gewisse Codesegmente wiederholen sich und können z.B. in Funktionen ausgelagert werden. Anstatt jedes Mal folgendes zu schreiben, wenn man den Spielernamen benötigt:

    new name[MAX_PLAYER_NAME]
    GetPlayerName(playerid, name, sizeof(name));

    folgendes Redundanzen zu vermeiden:
    GetPlayerName(playerid){
    new pName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, pName, sizeof(pName));
    return pName;
    }
    Gleichwohl kann man nun an zentraler Stelle Änderungen an dieser Abfrage vornehmen, ohne, dass man alle im Code vorkommenden Segmente per Hand verändern muss.

    Kommentare / Dokumentation:
    Spoiler anzeigen

    Am wichtigsten sind jedoch Kommentare! Wieso? Code wird sehr viel häufiger nur gelesen als editiert. Auch wenn ihr nur alleine scriptet, wäre es doch möglich, dass Ihr es verkaufen oder veröffentlichen möchtet. Stellt euch vor, Ihr veröffentlicht Euer Script auf Breadfish, allerdings ohne Kommentare. Vermutlich wäre euer Thread nach kurzer Zeit voll mit Supportanfragen. Nochmals ein "kleines" Beispiel aus einem meiner Skripte:

    CMD:verkaufen(playerid,params[])

    Spoiler anzeigen
    #pragma unused params

    Spoiler anzeigen
    if(IsPlayerInRangeOfPoint(playerid, 3, hInfo[Spieler[playerid][Hausid]][iconx], hInfo[Spieler[playerid][Hausid]][icony], hInfo[Spieler[playerid][Hausid]][iconz]))//return SendClientMessage(playerid, COLOR_RED, "Du bist nicht in der Nähe eines Hauses!");

    Spoiler anzeigen
    new pname[20];

    Spoiler anzeigen
    GetPlayerName(playerid, pname, 20);

    Spoiler anzeigen
    if (strcmp(hInfo[GetPVarInt(playerid, "DynID")][Name],pname,false))return SendClientMessage(playerid, COLOR_RED, "Dies ist nicht dein Haus!");

    Spoiler anzeigen
    new string1[128];

    Spoiler anzeigen
    format(hInfo[GetPVarInt(playerid, "DynID")][Name],255,"Keiner");

    Spoiler anzeigen
    DestroyPickup(HousePickup[GetPVarInt(playerid, "DynID")]);

    Spoiler anzeigen
    HousePickup[GetPVarInt(playerid, "DynID")] = CreatePickup(1273, 23, hInfo[GetPVarInt(playerid, "DynID")][iconx], hInfo[GetPVarInt(playerid, "DynID")][icony], hInfo[GetPVarInt(playerid, "DynID")][iconz]);

    Spoiler anzeigen
    DestroyDynamicMapIcon(GetPVarInt(playerid, "DynID"));

    Spoiler anzeigen
    HouseMapIcon[GetPVarInt(playerid, "DynID")] = CreateDynamicMapIcon(hInfo[GetPVarInt(playerid, "DynID")][iconx], hInfo[GetPVarInt(playerid, "DynID")][icony], hInfo[GetPVarInt(playerid, "DynID")][iconz], 31, 0, 0, 0, -1, 100.0);

    Spoiler anzeigen
    format(string1, sizeof(string1),"Besitzer: %s\nKosten: %d\nStunden: %d\nHausid: %d", hInfo[GetPVarInt(playerid, "DynID")][Name], hInfo[GetPVarInt(playerid, "DynID")][Kosten], hInfo[GetPVarInt(playerid, "DynID")][hStunden],GetPVarInt(playerid, "DynID"));

    Spoiler anzeigen
    UpdateDynamic3DTextLabelText(hInfo[GetPVarInt(playerid, "DynID")][hText],GREEN,string1);

    Spoiler anzeigen
    SendClientMessage(playerid, GREEN, "Du hast dein Haus verkauft!");

    Spoiler anzeigen
    SetPVarInt(playerid, "Geld", GetPlayerMoney(playerid)+hInfo[GetPVarInt(playerid, "DynID")][Kosten]);

    Spoiler anzeigen
    GivePlayerMoney(playerid, hInfo[GetPVarInt(playerid, "DynID")][Kosten]);

    Spoiler anzeigen
    new ID[5];

    Spoiler anzeigen
    valstr(ID,GetPVarInt(playerid, "DynID"));

    Spoiler anzeigen
    mysql_SetString("houses", "Name", "Keiner", "Hausid", ID);

    Spoiler anzeigen
    Spieler[playerid][Hausid] = -255;

    Spoiler anzeigen
    else if(IsPlayerInRangeOfPoint(playerid, 3, bInfo[Spieler[playerid][Bizid]][iconx], bInfo[Spieler[playerid][Bizid]][icony], bInfo[Spieler[playerid][Bizid]][iconz]))// return SendClientMessage(playerid, COLOR_RED, "Du bist nicht in der Nähe eines Hauses!");

    Spoiler anzeigen
    new pname[20];

    Spoiler anzeigen
    GetPlayerName(playerid, pname, 20);

    Spoiler anzeigen
    if (strcmp(bInfo[GetPVarInt(playerid, "DynID")][Name],pname,false)) return SendClientMessage(playerid, COLOR_RED, "Dies ist nicht dein Geschäft!");

    Spoiler anzeigen
    new string1[128];

    Spoiler anzeigen
    format(bInfo[GetPVarInt(playerid, "DynID")][Name],255,"Keiner");

    Spoiler anzeigen
    format(string1, sizeof(string1),"Besitzer: %s\nKosten: %d\nStunden: %d\nBizid: %d\nVerdienst: %d", bInfo[GetPVarInt(playerid, "DynID")][Name], bInfo[GetPVarInt(playerid, "DynID")][Kosten], bInfo[GetPVarInt(playerid, "DynID")][bStunden],GetPVarInt(playerid, "DynID"), bInfo[GetPVarInt(playerid, "DynID")][Verdienst]);

    Spoiler anzeigen
    UpdateDynamic3DTextLabelText(bInfo[GetPVarInt(playerid, "DynID")][bText],GREEN,string1);

    Spoiler anzeigen
    SendClientMessage(playerid, GREEN, "Du hast dein Geschäft verkauft!");

    Spoiler anzeigen
    SetPVarInt(playerid, "Geld", GetPlayerMoney(playerid)+bInfo[GetPVarInt(playerid, "DynID")][Kosten]);

    Spoiler anzeigen
    GivePlayerMoney(playerid, bInfo[GetPVarInt(playerid, "DynID")][Kosten]);

    Spoiler anzeigen
    valstr(string1,GetPVarInt(playerid, "DynID"));

    Spoiler anzeigen
    mysql_SetString("biz", "Name", "Keiner", "Bizid", string1);

    Spoiler anzeigen
    Spieler[playerid][Bizid] = -255;

    Spoiler anzeigen
    else return SendClientMessage(playerid, COLOR_RED, "Du bist weder in der Nähe deines Hauses noch Geschäfts.");

    Spoiler anzeigen
    return 1;

    Spoiler anzeigen
    }

    Ohne Kommentare geht hier nix mehr :D Ihr seht, ich weiß wovon ich rede. Nehmt euch lieber die Zeit und fügt ein, was getan wird, und warum es getan wird. Es wird euch viel Zeit ersparen, solltet ihr jemals eine längere Pause eingelegt haben oder euren Code z.B. hier im Forum zu posten, weil Ihr HIlfe benötigt.

    Spoiler anzeigen
    Natürlich macht es keinen Sinn bei sich selbsterklärenden Stellen im Code jedes Mal einen Kommentar zu hinterlassen. Längere oder komplexe Codesegmente sowie wichtige Funktionen sollten jedoch dokumentiert werden.


    Ich hoffe ich konnte Euch einige Gedanken näher bringen. :) Lasst es euch nicht nehmen, Eure eigenen Ansichten Kund zu tun. :P

    Einmal editiert, zuletzt von ]hp[ () aus folgendem Grund: Beispiele verbessert, Fehler beiseitigt, Funktion korrigiert.

  • Ich persönlich finde keinen Sinn bei jedem scheiß Kommentare zu setzen.
    Im Falle der Veröffentlichung ist es doch nicht mein Problem ob es jemand versteht, immerhin wird es "kostenlos" angeboten.
    Ebenso mit Funktionen, Ich habe mir mal sagen lassen, dass viieele Funktionen viele Ressourcen fressen, bestes bsp: Spielername.
    Ob es stimmt ist mir nie aufgefallen aber naja.
    Ansonsten finde ich es relativ unnötig dafür einen Thread zu machen, jeder der mit Scripten anfängt, wird sich aufregen das sein Code unübersichtlich ist,
    Somit wird er es selber korrigieren und brauch nicht extra ein tutorial.


    Aber naja meine Meinung.

  • Wo ich das gesehen hab hab ich mir nur gedacht was das bringen soll...
    GetPlayerName(playerid){
    new pName[MAX_PLAYER_NAME];
    return GetPlayerName(playerid, pName, sizeof(pName));
    }
    Ich hoffe, das dir bewusst ist was GetPlayerName returnt...
    Desweiteren würd ne definierung da besser aufgehoben sein als ne extra funktion

    Falls du es nicht weißt
    The length of the player's name. 0 if player specified doesn't exist.


    Zu den Kommentaren fällt mir nix ein denn ich find es Sinnlos
    Das was wichtig ist schreibe ich ganz oben rein aber jedesmal hinter nen Command was schreiben find ich Zeitverschwendung.

    All in all it's just another brick in the wall

  • dcmd_setlevel(playerid,params[])
    {
    new pID, Level;
    if(sscanf(params, "ud",pID,Level))return SendClientMessage(playerid,0xFF0000FF,"Benutze: /setlevel [ID][Level]!"); //Falls nur /setlevel eingegeben wurde
    if (pID == INVALID_PLAYER_ID)return SendClientMessage(playerid,0xFF0000FF,"Spielerid ist nicht vorhanden!");
    // Falls eine falsche ID angegeben wurde, wird 0 returnt ;)
    if(Spieler[playerid][AdminLevel]< 3)return SendClient..
    new adminstring[128],levelsetter[MAX_PLAYER_NAME], playername[MAX_PLAYER_NAME];
    GetPlayerName(pID,playername,sizeof playername);
    format(adminstring,sizeof(adminstring),"Admins/%s.sav",playername);
    GetPlayerName(playerid,levelsetter,sizeof(levelsetter));
    Spieler[pID][AdminLevel] = Level; //Hier wird die Levelvariable mit dem neu eingegebenen Level überschrieben
    dini_IntSet(adminstring,"AdminLevel",Level); // Und hier das neue Level in der Datei gespeichert.
    format(adminstring,sizeof(adminstring),"%s hat dein Level auf %d gesetzt!",levelsetter, Level);
    SendClientMessage(pID,0x33FF33FF, adminstring);
    SendClientMessage(playerid, 0xFF0000FF, "Dein Level reicht nicht aus!");
    return 1;
    }
    So spart man sich das Problem mit dem Einrücken.


    CMD:test(playerid, params[])
    {
    if(IsPlayerAdmin(playerid){
    if(IsPlayerInAnyVehicle(playerid){
    if(IsPlayerNPC(playerid){
    }
    }
    }
    return 0;
    }
    Ist auch nicht sehr schön.
    CMD:..
    if(IsPlayerAdmin(playerid) && IsPlayerInAnyVehicle(playerid) && IsPlayerNPC(playerid))//wenn diese Bedingungen erfüllt sind, dann..
    So spart man sich die Unübersichtlichkeit.


    GetPlayerName(playerid){
    new pName[MAX_PLAYER_NAME];
    return GetPlayerName(playerid, pName, sizeof(pName));
    }
    Wurde bereits 100x besprochen, warum das Müll ist.

  • Zitat

    Zu den Kommentaren fällt mir nix ein denn ich find es Sinnlos
    Das was wichtig ist schreibe ich ganz oben rein aber jedesmal hinter nen Command was schreiben find ich Zeitverschwendung.


    Wichtige Stellen sollten kommentiert werden, offensichtliches kann man sich natürlich sparen. Das werde ich noch hinzufügen, danke. :thumbup:


    GetPlayerName returnt ja tatsächlich nur die Länge des Namens... gruselig, da die Funktion GetPlayerName und nicht GetPLayerNameLength heißt :D das werde ich korrigieren.


    Zitat

    Ansonsten finde ich es relativ unnötig dafür einen Thread zu machen, jeder der mit Scripten anfängt, wird sich aufregen das sein Code unübersichtlich ist,
    Somit wird er es selber korrigieren und brauch nicht extra ein tutorial.


    Vermutlich hast du Recht, dass Neulinge nicht zuerst auf die Formatierung ihres Codes achten werden. Bei größeren Scripten ist ordentlicher Code jedoch ein Hilfsmittel um unnötige Fehler zu vermeiden. Somit schadet es definitiv nicht, mal ein paar Gedanken zu ordentlichem Code zu äußern. ^^

  • Ich persönlich finde keinen Sinn bei jedem scheiß Kommentare zu setzen.


    Mir persönlich helfen Kommentare immens, falls ich nach einer langen Pause mal wieder an meinem Projekt weiterarbeiten möchte. In dem Fall sind die Kommentare nur für mich und ich setze diese auch an bestimmten Passagen im Code, um mich schnell wieder zurechtzufinden.


  • Mir persönlich helfen Kommentare immens, falls ich nach einer langen Pause mal wieder an meinem Projekt weiterarbeiten möchte. In dem Fall sind die Kommentare nur für mich und ich setze diese auch an bestimmten Passagen im Code, um mich schnell wieder zurechtzufinden.


    Wie gesagt jedem seine meinung, aber..
    Wenn du ein Grooßes Script mit sagen wir mal 40k Zeilen hast und bei jedem Zeug n Kommentar dabei ist, bezweifel ich, dass es übersichtlich ist.

  • do.de - Domain-Offensive - Domains für alle und zu super Preisen
  • Ich nutze ebenfalls kaum Kommentare.
    Ich strukturiere mein Code so, dass ich es auch ohne Kommentare hinbekommen. Es gibt nur eine Sache, wo ich Kommentare verwende(n würde),
    nähmlich in der Kopfzeile, wo ich reinschreiben würde, "wann" ich "was" gemacht habe. Denn wenn ich zu lange nicht gescriptet habe, weiß ich
    dann immer direkt, was ich zuletzt gemacht habe, und was ich weitermachen muss.


    Zum Tutorial:
    Gut für Anfänger, aber für Fortgeschrittene eher nicht, denn sowas beachten sie (eigentlich). Ich würde niemals scriptet können, ohne alles ordentlich zu strukturieren
    oder einzurücken. Da kommt man ja mit seinem eigenen Code nichtmehr klar, wenn man nichtmal weiß, welche Klammer wo geöffnet / geschlossen wurde. (Siehe TT: "Klammern").
    Aber auf jeden Fall gutes Tutorial für Anfänger. Ich kenne / kannte selber schon Leute, die nichts eingerückt haben, und dann die Übersicht verloren :)