Ich muss meinen Code für die anderen Programmierer in meinem Team besser lesbar machen

11

Ich arbeite an einem Projekt in Delphi und erstelle ein Installationsprogramm für die Anwendung. Es gibt drei Hauptteile.

  1. PostgreSQL Installation / Deinstallation
  2. myapplication (Setup von myapplication wird mit nsi erstellt) Installation / Deinstallation.
  3. Erstellen von Tabellen in Postgres über ein Skript (Batch-Dateien).

Alles läuft gut und reibungslos, aber wenn etwas fehlschlägt, habe ich einen LogToFileger erstellt, der jeden Schritt des Prozesses
wie folgt protokolliert

LogToFileToFile.LogToFile('[DatabaseInstallation]  :  [ACTION]:Postgres installation started');

Die Funktion LogToFileToFile.LogToFile()Hiermit wird der Inhalt in eine Datei geschrieben. Dies funktioniert gut, aber das Problem ist, dass dies den Code durcheinander gebracht hat, da es schwierig geworden ist, den Code zu lesen , da man den LogToFileToFile.LogToFile()Funktionsaufruf nur überall im Code sehen kann

ein Beispiel

 if Not FileExists(SystemDrive+'\FileName.txt') then
 begin
    if CopyFile(PChar(FilePathBase+'FileName.txt'), PChar(SystemDrive+'\FileName.txt'), False) then
       LogToFileToFile.LogToFile('[DatabaseInstallation] :  copying FileName.txt to '+SystemDrive+'\ done')
       else
       LogToFileToFile.LogToFile('[DatabaseInstallation] :  copying FileName.txt to '+SystemDrive+'\ Failed');
 end;
 if Not FileExists(SystemDrive+'\SecondFileName.txt')      then
   begin
     if CopyFile(PChar(FilePathBase+'SecondFileName.txt'), PChar('c:\SecondFileName.txt'), False) then
       LogToFileToFile.LogToFile('[DatabaseInstallation] : copying SecondFileName.txt to '+SystemDrive+'\ done')
   else
       LogToFileToFile.LogToFile('[DatabaseInstallation] :  copying SecondFileName.txt to '+SystemDrive+'\ Failed');
 end;

Wie Sie sehen, gibt es viele LogToFileToFile.LogToFile()Anrufe,
bevor es war

 if Not FileExists(SystemDrive+'\FileName.txt') then
    CopyFile(PChar(FilePathBase+'FileName.txt'), PChar(SystemDrive+'\FileName.txt'), False) 
 if Not FileExists(SystemDrive+'\SecondFileName.txt')      then
   CopyFile(PChar(FilePathBase+'SecondFileName.txt'), PChar('c:\SecondFileName.txt'), False)

Dies ist jetzt in meinem gesamten Code der Fall.
es ist schwer zu lesen.

Kann mir jemand einen guten Weg vorschlagen, um die Aufrufe von LogToFile übersichtlich zu gestalten?

mögen

  1. Einrücken des Aufrufs 'LogToFileToFile.LogToFile ()'
    wie folgt

       if Not FileExists(SystemDrive+'\FileName.txt') then
         begin
             if CopyFile(PChar(FilePathBase+'FileName.txt'), PChar(SystemDrive+'\FileName.txt'), False) then
            {Far away--->>}                   LogToFileToFile.LogToFile(2,'[DatabaseInstallation] :  [ACTION]:copying FileName.txt to '+SystemDrive+'\ sucessful')
       else
            {Far away--->>}                   LogToFileToFile.LogToFile(2,'[DatabaseInstallation] :  [ACTION]:copying FileName.txt to '+SystemDrive+'\ Failed');
       end;
    
  2. Separate Einheit wie LogToFileger
    Diese Einheit enthält alle LogToFile-Nachrichten in einer switch caseähnlichen Form

     Function LogToFilegingMyMessage(LogToFilegMessage : integer)
    
     begin
    case  LogToFilegMessage of
    
    1         :  LogToFileToFile.LogToFile(2,'[DatabaseInstallation] :  [ACTION]:copying FileName.txt to '+SystemDrive+'\ sucessful');
    2         :  LogToFileToFile.LogToFile(2,'[DatabaseInstallation] :  [ACTION]:copying FileName.txt to '+SystemDrive+'\ Failed');
       150        :  LogToFileToFile.LogToFile(2,'[somthing] :  [ACTION]: somthing important);
    
    end;
    

Daher kann ich einfach die LogToFilegingMyMessage (1) aufrufen, wo immer dies erforderlich ist.

Kann mir jemand sagen, welcher Ansatz für LogToFileging auf diese Weise besser und sauberer ist?

PresleyDias
quelle
5
Um Ihr Thema zu beantworten: Haben Sie versucht, Ihr Team zu fragen, ob es es versteht oder ob alles Sinn macht? Wenn ja, sollte es "ausreichend" lesbar sein.
Spoike
@Spoike: Ich habe gefragt, es ist wenig schwer zu lesen, da überall das logBook.log()anzutreffen ist.
PresleyDias
"Es gibt zwei Hauptteile" mit den Nummern 1 bis 3. Ich glaube, ich verstehe, warum Sie eine Frage zur Lesbarkeit haben. Vielleicht möchten Sie jemanden finden, der aus Gründen der Konsistenz "bearbeiten" kann.
S.Lott
@ S.Lott ich bearbeitete die 'zwei' bis 'drei' ..sory für den Fehler
PresleyDias
Vielleicht möchten Sie auch codereview.stackexchange.com
Kirk Broadhurst

Antworten:

11

Beim Hinzufügen der Protokollierung haben Sie zwei Dinge eingeführt:

  1. Der Code wurde größer, weil Sie für fast jede Aktion eine Zeile hinzugefügt haben, die diese Aktion (oder ihren Fehler) protokolliert.
  2. Die Protokollzeilen selbst scheinen aufgebläht zu sein und beeinträchtigen die Lesbarkeit, da sie so viel Platz beanspruchen.

Jedes dieser Probleme hat seine eigene, relativ einfache Lösung:

  1. Teilen Sie den Code in kleinere Funktionen auf. Anstatt eine riesige Funktion zu haben, die alle Ihre Kopien sowie Protokollmeldungen für Fehler / Erfolg enthält, könnten Sie eine Funktion "CopyFile" einführen, die genau eine Datei kopiert und das eigene Ergebnis protokolliert. Auf diese Weise würde Ihr Hauptcode nur aus CopyFile-Aufrufen bestehen und leicht lesbar bleiben.

  2. Sie könnten Ihren Logger schlauer machen. Anstatt eine riesige Zeichenfolge mit vielen sich wiederholenden Informationen zu übergeben, können Sie Aufzählungswerte übergeben, die die Dinge klarer machen. Oder Sie können speziellere Log () -Funktionen definieren, z. B. LogFileCopy, LogDbInsert ... Was auch immer Sie häufig wiederholen, sollten Sie dies in eine eigene Funktion einbeziehen.

Wenn Sie (1) folgen, könnten Sie Code haben, der so aussieht:

CopyFile( sOSDrive, 'Mapannotation.txt' )
CopyFile( sOSDrive, 'Mappoints.txt' )
CopyFile( sOSDrive, 'Mapsomethingelse.txt' )
. . . .

Dann benötigt Ihre CopyFile () nur wenige Codezeilen, um die Aktion auszuführen und das Ergebnis zu protokollieren, sodass Ihr gesamter Code präzise und leicht lesbar bleibt.

Ich würde mich von Ihrem Ansatz Nr. 2 fernhalten, da Sie Informationen, die zusammen bleiben sollten, in verschiedene Module aufteilen. Sie fragen nur nach Ihrem Hauptcode, damit er nicht mehr mit Ihren Protokollanweisungen synchronisiert wird. Aber wenn Sie sich LogMyMessage (5) ansehen, werden Sie das nie erfahren.

UPDATE (Antwort auf einen Kommentar): Ich kenne die genaue Sprache, die Sie verwenden, nicht. Daher muss dieser Teil möglicherweise etwas angepasst werden. Es scheint, dass alle Ihre Protokollnachrichten drei Dinge identifizieren: Komponente, Aktion, Ergebnis.

Ich denke, das ist ziemlich genau das, was MainMa vorgeschlagen hat. Definieren Sie Konstanten, anstatt die eigentliche Zeichenfolge zu übergeben (in C / C ++ / C # wären sie Teil des Aufzählungstyps enum). So können Sie beispielsweise für Komponenten Folgendes haben: DbInstall, AppFiles, Registry, Shortcuts ... Alles, was den Code verkleinert, erleichtert das Lesen.

Es wäre auch hilfreich, wenn Ihre Sprache die Übergabe variabler Parameter unterstützen würde und nicht sicher wäre, ob dies möglich ist. Wenn die Aktion beispielsweise "FileCopy" lautet, können Sie für diese Aktion zwei zusätzliche Benutzerparameter definieren: Dateiname und Zielverzeichnis.

Ihre Dateikopierzeilen würden also ungefähr so ​​aussehen:

Bool isSuccess = CopyFile(PChar(sTxtpath+'Mapannotation.txt'), PChar(sOSdrive+'\Mapannotation.txt'), False)
LogBook.Log( DbInstall, FileCopy, isSuccess, 'Mapannotation.txt', sOSDrive )

* Hinweis: Es gibt auch keinen Grund, die Protokollzeile zweimal zu kopieren / einzufügen, wenn Sie das Ergebnis der Operation in einer separaten lokalen Variablen speichern und diese Variable einfach an Log () übergeben können.

Sie sehen das Thema hier, richtig? Weniger sich wiederholender Code -> besser lesbarer Code.

DXM
quelle
+1, kannst du mir mehr darüber erzählen you could pass in enumerations values ?
PresleyDias
@ PresleyDias: aktualisierter Beitrag
DXM
ok verstanden, ja weniger repetitiv-> besser lesbarer Code
PresleyDias
2
+1 "Teilen Sie den Code in kleinere Funktionen auf." Das kann man nicht genug betonen. Es lässt einfach so viele Probleme einfach verschwinden.
Oliver Weiler
10

Sieht so aus, als müssten Sie das Konzept einer "LoggableAction" abstrahieren. In Ihrem Beispiel wird ein Muster angezeigt, bei dem alle Aufrufe einen Bool zurückgeben, um Erfolg oder Misserfolg anzuzeigen. Der einzige Unterschied besteht in der Protokollnachricht.

Es ist Jahre her, seit ich Delphi geschrieben habe, also ist dies ziemlich c # -inspirierter Pseudocode, aber ich hätte gedacht, dass Sie so etwas wollen

void LoggableAction(FunctionToCallPointer, string logMessage)
{
    if(!FunctionToCallPointer)
    {  
        Log(logMessage).
    }
}

Dann wird Ihr Anrufcode

if Not FileExists(sOSdrive+'\Mapannotation.txt') then
    LoggableAction(CopyFile(PChar(sTxtpath+'Mapannotation.txt'), "Oops, it went wrong")

Ich kann mich nicht an die Delphi-Syntax für Funktionszeiger erinnern, aber unabhängig von den Implementierungsdetails scheint eine Art Abstraktion um die Protokollroutine das zu sein, wonach Sie suchen.

MarcE
quelle
Ich würde wahrscheinlich selbst diesen Weg gehen, aber ohne mehr über die Struktur des OP-Codes zu wissen, ist es schwierig zu sagen, ob dies besser wäre, als einfach ein paar zusätzliche aufzurufende Methoden zu definieren, ohne die potenzielle Verwirrung der Methodenzeiger (abhängig davon) hinzuzufügen darüber, wie viel das OP über solche Dinge weiß
Robins
+1, LoggableAction()das ist schön, ich kann den zurückgegebenen Wert direkt schreiben, anstatt zu überprüfen und zu schreiben.
PresleyDias
Ich wünsche +100, tolle Antwort, aber ich kann nur eine Antwort akzeptieren :( .. Ich werde diesen Vorschlag in meiner nächsten Bewerbung versuchen, danke für die Idee
PresleyDias
3

Ein möglicher Ansatz besteht darin, den Code mithilfe von Konstanten zu reduzieren.

if CopyFile(PChar(sTxtpath+'Mapannotation.txt'), PChar(sOSdrive+'\Mapannotation.txt'), False) then
   LogBook.Log(2,'[POSTGRESQL INSTALLATION] :  [ACTION]:copying Mapannotation.txt to '+sOSdrive+'\ sucessful')
   else
   LogBook.Log(2,'[POSTGRESQL INSTALLATION] :  [ACTION]:copying Mapannotation.txt to '+sOSdrive+'\ Failed');

würde werden:

if CopyFile(PChar(sTxtpath+'Mapannotation.txt'), PChar(sOSdrive+'\Mapannotation.txt'), False) then
   Log(2, SqlInstal, Action, CopyMapSuccess, sOSdrive)
   else
   Log(2, SqlInstal, Action, CopyMapFailure, sOSdrive)

Dies hat ein besseres Verhältnis von Protokollcode zu anderem Code, wenn die Anzahl der Zeichen auf dem Bildschirm gezählt wird.

Dies kommt dem nahe, was Sie in Punkt 2 Ihrer Frage vorgeschlagen haben, außer dass ich nicht so weit gehen würde: Log(9257)ist offensichtlich kürzer als Log(2, SqlInstal, Action, CopyMapSuccess, sOSdrive), aber auch ziemlich schwer zu lesen. Was ist 9257? Ist es ein Erfolg? Eine Handlung? Bezieht es sich auf SQL? Wenn Sie in den letzten zehn Jahren an dieser Codebasis arbeiten, lernen Sie diese Zahlen auswendig (wenn es eine Logik gibt, dh 9xxx sind Erfolgscodes, x2xx beziehen sich auf SQL usw.), aber für einen neuen Entwickler, der dies entdeckt Die Codebasis, Short Codes, wird ein Albtraum sein.

Sie können noch weiter gehen, indem Sie die beiden Ansätze mischen: Verwenden Sie eine einzelne Konstante. Persönlich würde ich das nicht tun. Entweder werden Ihre Konstanten größer:

Log(Type2SuccessSqlInstallCopyMapSuccess, sOSdrive) // Can you read this? Really?

oder die Konstanten bleiben kurz, aber nicht sehr explizit:

Log(T2SSQ_CopyMapSuccess, sOSdrive) // What's T2? What's SSQ? Or is it S, followed by SQ?
// or
Log(CopyMapSuccess, sOSdrive) // Is it an action? Is it related to SQL?

Dies hat auch zwei Nachteile. Sie müssen:

  • Führen Sie eine separate Liste, in der die Protokollinformationen den jeweiligen Konstanten zugeordnet sind. Mit einer einzigen Konstante wächst es schnell.

  • Finden Sie einen Weg, um ein einzelnes Format in Ihrem Team durchzusetzen. Was ist zum Beispiel, wenn stattdessen T2SSQjemand beschließt, zu schreiben ST2SQL?

Arseni Mourzenko
quelle
+1, für den sauberen logAufruf, aber können Sie mir mehr erklären, was es nicht verstanden hat Log(2, SqlInstal, Action, CopyMapFailure, sOSdrive), Sie wollen sagen, SqlInstalwird meine definierte Variable sein wie SqlInstal:=[POSTGRESQL INSTALLATION] ?
PresleyDias
@PresleyDias: SqlInstalkann alles sein, zum Beispiel ein Wert 3. In Log()wird dieser Wert dann effektiv übersetzt, [POSTGRESQL INSTALLATION]bevor er mit anderen Teilen der Protokollnachricht verkettet wird.
Arseni Mourzenko
single format in your teamist eine gute / gute Option
PresleyDias
3

Versuchen Sie, eine Reihe kleiner Funktionen zu extrahieren, um all die unordentlich aussehenden Dinge zu handhaben. Es gibt viele wiederholte Codes, die sehr einfach an einem einzigen Ort ausgeführt werden können. Beispielsweise:

procedure CopyIfFileDoesNotExist(filename: string);
var
   success: boolean;
begin
   if Not FileExists(sOSdrive+'\'+filename') then
   begin
      success := CopyFile(PChar(sTxtpath+filename), PChar(sOSdrive+filename), False);

      Log(filename, success);
   end;
end;

procedure Log(filename: string; isSuccess: boolean)
var
   state: string;
begin
   if isSuccess then
   begin
      state := 'success';
   end
   else
   begin
      state := 'failed';
   end;

   LogBook.Log(2,'[POSTGRESQL INSTALLATION] : [ACTION]:copying ' + filename + ' to '+sOSdrive+'\ ' + state);
end;

Der Trick besteht darin, nach Duplikaten in Ihrem Code zu suchen und Möglichkeiten zu finden, diese zu entfernen. Verwenden Sie viele Leerzeichen und nutzen Sie den Anfang / das Ende zu Ihrem Vorteil (mehr Leerzeichen und leicht zu findende / faltbare Codeblöcke). Es sollte wirklich nicht zu schwierig sein. Diese Methoden könnten Teil Ihres Loggers sein ... es liegt wirklich an Ihnen. Aber das scheint ein guter Anfang zu sein.

S.Robins
quelle
+1, Leerzeichen sind ein guter Weg. success := CopyFile()
Vielen
@ S.Robins habe ich deinen Code richtig gelesen? Ihre Methode heißt LogIfFileDoesNotExistKopien kopieren?
João Portela
1
@ JoãoPortela Ja ... es ist nicht sehr hübsch und hält sich nicht an das Prinzip der Einzelverantwortung. Denken Sie daran, dass dies ein erster Durchgang war, der mir aus dem Kopf ging und darauf abzielte, dem OP zu helfen, sein Ziel zu erreichen, etwas Unordnung in seinem Code zu reduzieren. Es ist wahrscheinlich in erster Linie eine schlechte Wahl des Namens für die Methode. Ich werde es ein wenig optimieren, um es zu verbessern. :)
S.Robins
Gut zu sehen, dass Sie sich die Zeit genommen haben, um dieses Problem zu beheben, +1.
João Portela
2

Ich würde sagen, dass die Idee hinter Option 2 die beste ist. Ich denke jedoch, dass die Richtung, in die Sie gegangen sind, die Dinge noch schlimmer macht. Die Ganzzahl bedeutet nichts. Wenn Sie sich den Code ansehen, werden Sie sehen, dass etwas protokolliert wird, aber Sie wissen nicht, was.

Stattdessen würde ich so etwas tun:

void logHelper(String phase, String message) {
   LogBook.Log(2, "[" + phase + "] :  [Action]: " + message);
}

Dadurch bleibt die Nachrichtenstruktur erhalten, Ihr Code kann jedoch flexibel sein. Sie können nach Bedarf konstante Zeichenfolgen für die Phasen definieren und diese nur als Phasenparameter verwenden. Auf diese Weise können Sie an einer Stelle Änderungen am eigentlichen Text vornehmen und alles bewirken. Der andere Vorteil der Hilfsfunktion besteht darin, dass der wichtige Text mit dem Code verknüpft ist (als wäre es ein Kommentar), der Text, der nur für die Protokolldatei wichtig ist, jedoch entfernt wird.

if (!FileExists(sOSdrive+'\Mapannotation.txt')) {
    if (CopyFile(PChar(sTxtpath+'Mapannotation.txt'), PChar(sOSdrive+'\Mapannotation.txt'), False)) {
       logHelper(POSTGRESQL, 'copying Mapannotation.txt to '+ sOSdrive +'\ sucessful')
    } else {
       logHelper(POSTGRESQL, 'copying Mapannotation.txt to '+ sOSdrive +'\ Failed');
    }
}

Dies ist nichts, was Sie in Ihrer Frage erwähnt haben, aber ich habe etwas über Ihren Code bemerkt. Ihre Einrückung ist nicht konsistent. Das erste Mal, wenn Sie es verwenden begin, wird nicht eingerückt, aber das zweite Mal. Sie machen eine ähnliche Sache mit else. Ich würde sagen, das ist viel wichtiger als die Protokollzeilen. Wenn die Einrückung nicht konsistent ist, ist es schwierig, den Code zu scannen und dem Ablauf zu folgen. Viele sich wiederholende Protokollzeilen lassen sich beim Scannen leicht herausfiltern.

unholysampler
quelle
1

Wie wäre es mit etwas in dieser Richtung:

LogBook.NewEntry( 2,'POSTGRESQL INSTALLATION', 'copying Mapannotation.txt to '+sOSdrive);

if CopyFile(PChar(sTxtpath+'Mapannotation.txt'), PChar(sOSdrive+'\Mapannotation.txt'), False) then
    LogBook.Success()
else
    LogBook.Failed();

Die NewEntry () -Methode würde die Textzeile erstellen (einschließlich des Hinzufügens von [&] um die richtigen Einträge) und diese warten, bis die Methoden success () oder failed () aufgerufen werden, die die Zeile mit 'success' oder anhängen 'Fehler', und geben Sie dann die Zeile in das Protokoll ein. Sie können auch andere Methoden festlegen, z. B. info (), wenn der Protokolleintrag nicht für Erfolg / Misserfolg bestimmt ist.

GroßmeisterB
quelle