Devo rendere il mio codice più leggibile dagli altri programmatori del mio team


11

Sto lavorando a un progetto in Delphi e sto creando un programma di installazione per l'applicazione, ci sono tre parti principali.

  1. Installazione / disinstallazione di PostgreSQL
  2. installazione / disinstallazione di myapplication (l'installazione di myapplication viene creata utilizzando nsi).
  3. Creazione di tabelle in Postgres tramite script (file batch).

Ogni cosa funziona bene e senza intoppi, ma se qualcosa fallisce ho creato un LogToFileger che LogToFile ad ogni fase del processo, in
questo modo

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

La funzione LogToFileToFile.LogToFile()Questo scriverà il contenuto in un file. Funziona bene, ma il problema è che questo ha incasinato il codice poiché in esso è diventato difficile leggere il codice poiché si vede solo la LogToFileToFile.LogToFile()chiamata di funzione ovunque nel codice

un esempio

 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;

come puoi vedere ci sono molte LogToFileToFile.LogToFile()chiamate,
prima che lo fosse

 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)

questo è il caso nel mio intero codice ora.
è difficile da leggere.

qualcuno mi può suggerire un bel modo per disordinare le chiamate a LogToFile?

piace

  1. Rientro della chiamata 'LogToFileToFile.LogToFile () `in
    questo modo

       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. Unità separata come LogToFileger
    Questa unità avrà tutti i messaggi LogToFile in switch casequesto modo

     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;
    

così posso semplicemente chiamare LogToFilegingMyMessage (1) dove mai richiesto.

Qualcuno può dirmi quale sia un approccio migliore e più pulito a LogToFileging in questo modo?


5
Per rispondere al tuo argomento: hai provato a chiedere al tuo team se lo capiscono o se tutto ha un senso? Se sì, allora dovrebbe essere "abbastanza" leggibile.
Spoike,

@Spoike: l'ho chiesto, è un po 'difficile da leggere, come ovunque logBook.log()si incontra.
PresleyDias,

"ci sono due parti principali" numerate da 1 a 3. Penso di vedere perché hai una domanda sulla leggibilità. Potresti voler trovare qualcuno che possa "modificare" per coerenza.
S. Lott,

@ S. Lott ho modificato il 'due' in 'tre' ..soria per l'errore
PresleyDias

Risposte:


11

Quando hai aggiunto la registrazione, hai introdotto due cose:

  1. Il codice è diventato più grande perché per quasi ogni azione hai aggiunto una riga che registra quell'azione (o il suo fallimento)
  2. Le stesse linee di registro sembrano gonfie e tolgono alla leggibilità perché occupano così tanto spazio.

Ognuno di questi ha problemi ha la sua soluzione relativamente semplice:

  1. Suddividere il codice in funzioni più piccole. Invece di avere una funzione gigante che contiene tutte le tue copie e anche i messaggi di registro per errori / esiti positivi, potresti introdurre una funzione "CopyFile", che copia esattamente un file e registra il proprio risultato. In questo modo il tuo codice principale consisterebbe solo in chiamate CopyFile e rimarrebbe di facile lettura.

  2. Potresti rendere il tuo logger più intelligente. Invece di passare in una stringa gigante che contiene molte informazioni ripetitive, potresti passare in valori di enumerazione che renderebbero le cose più chiare. Oppure potresti definire funzioni Log () più specializzate, ad esempio LogFileCopy, LogDbInsert ... Qualunque cosa tu ripeti molto, considera il factoring fuori nella sua stessa funzione.

Se segui (1), potresti avere un codice simile al seguente:

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

Quindi il tuo CopyFile () necessita solo di poche righe di codice per eseguire l'azione e registrarne i risultati, in modo che tutto il codice rimanga conciso e di facile lettura.

Starei lontano dal tuo approccio n. 2 mentre stai staccando informazioni che dovrebbero stare insieme in diversi moduli. Stai solo chiedendo che il tuo codice principale non sia sincronizzato con le tue dichiarazioni di registro. Ma guardando LogMyMessage (5), non lo saprai mai.

AGGIORNAMENTO (risposta al commento): non ho familiarità con il linguaggio esatto che stai usando, quindi questa parte potrebbe dover essere adattata un po '. Sembra che tutti i tuoi messaggi di registro identificino 3 cose: componente, azione, risultato.

Penso che questo sia praticamente ciò che MainMa ha suggerito. Invece di passare la stringa effettiva, definire le costanti (in C / C ++ / C #, farebbero parte del tipo di enumerazione enum). Ad esempio, per i componenti, potresti avere: DbInstall, AppFiles, Registry, Shortcuts ... Qualsiasi cosa che riduca il codice renderà più semplice la lettura.

Sarebbe anche utile se la tua lingua supportasse il passaggio di parametri variabili, non sono sicuro che sia possibile. Ad esempio, se l'azione è "FileCopy", è possibile definire quell'azione per avere due parametri utente aggiuntivi: nome file e directory di destinazione.

Quindi le tue linee di copia dei file sarebbero simili a queste:

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

* nota, non c'è motivo di copiare / incollare due volte la riga del registro se è possibile memorizzare il risultato dell'operazione in una variabile locale separata e passare quella variabile in Log ().

Vedi il tema qui, giusto? Codice meno ripetitivo -> codice più leggibile.


+1, puoi dirmi di più su you could pass in enumerations values questo?
PresleyDias,

@PresleyDias: post aggiornato
DXM

ok capito, sì meno ripetitivo-> codice più leggibile
PresleyDias

2
+1 "Suddividi il codice in funzioni più piccole." Non puoi stressarlo abbastanza. Fa semplicemente scomparire così tanti problemi.
Oliver Weiler,

10

Sembra che tu abbia bisogno di astrarre il concetto di "LoggableAction". Nel mio esempio vedo un modello in cui tutte le chiamate restituiscono un valore booleano per indicare l'esito positivo o negativo e l'unica differenza è il messaggio di registro.

Sono passati anni da quando ho scritto Delphi, quindi questo è praticamente uno pseudo-codice ispirato a C #, ma avrei pensato che volessi qualcosa di simile

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

Quindi il codice chiamante diventa

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

Non ricordo la sintassi di Delphi per i puntatori a funzioni, ma qualunque siano i dettagli di implementazione, una sorta di astrazione attorno alla routine del registro sembrerebbe essere ciò che stai cercando.


Probabilmente andrei in questo modo da solo, ma senza sapere di più su come è strutturato il codice del PO, è difficile dire se sarebbe meglio che semplicemente definire un paio di metodi extra da chiamare, senza aggiungere la potenziale confusione dei puntatori al metodo (a seconda su quanto il PO sa di tali cose.
S.Robins,

+1, LoggableAction()questo è carino, posso scrivere direttamente il valore restituito invece di controllare e scrivere.
PresleyDias,

desidero +100, ottima risposta, ma posso accettare solo una risposta :( .. proverò questo suggerimento nella mia prossima domanda, grazie per l'idea
PresleyDias

3

Un possibile approccio è quello di ridurre il codice usando le costanti.

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');

potrebbe diventare:

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)

che ha un migliore rapporto tra i registri / altri codici quando si conta il numero di caratteri sullo schermo.

Questo è vicino a quello che hai suggerito nel punto 2 della tua domanda, tranne per il fatto che non andrei così lontano: Log(9257)è ovviamente più breve di Log(2, SqlInstal, Action, CopyMapSuccess, sOSdrive), ma anche abbastanza difficile da leggere. Che cos'è 9257? È un successo? Un'azione? È legato a SQL? Se lavori su questa base di codice negli ultimi dieci anni, imparerai questi numeri a memoria (se c'è una logica, cioè 9xxx sono codici di successo, x2xx sono correlati a SQL, ecc.), Ma per un nuovo sviluppatore che scopre la base di codice, i codici funzione sarà un incubo.

Puoi andare oltre mescolando i due approcci: usa una singola costante. Personalmente, non lo farei. O le tue costanti cresceranno di dimensioni:

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

o le costanti rimarranno brevi, ma non molto esplicite:

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?

Questo ha anche due inconvenienti. Dovrai:

  • Mantenere un elenco separato che associa le informazioni del registro alle rispettive costanti. Con una sola costante, crescerà rapidamente.

  • Trova un modo per applicare un unico formato nel tuo team. Ad esempio, se invece di T2SSQqualcuno decidesse di scrivere ST2SQL?


+1, per la logchiamata pulita , ma puoi spiegarmi di più che non ha capito Log(2, SqlInstal, Action, CopyMapFailure, sOSdrive), vuoi dire che SqlInstalsarà la mia variabile definita come SqlInstal:=[POSTGRESQL INSTALLATION] ?
PresleyDias,

@PresleyDias: SqlInstalpuò essere qualsiasi cosa, ad esempio un valore 3. Quindi, in Log(), questo valore verrà effettivamente tradotto in [POSTGRESQL INSTALLATION]prima di essere concatenato con altre parti del messaggio di registro.
Arseni Mourzenko,

single format in your teamè una buona / ottima opzione
PresleyDias,

3

Prova a estrarre una serie di piccole funzioni per gestire tutte le cose dall'aspetto disordinato. Esistono molti codici ripetuti che possono essere facilmente eseguiti in un unico posto. Per esempio:

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;

Il trucco è guardare qualsiasi duplicazione nel tuo codice e trovare modi per rimuoverlo. Usa un sacco di spazi bianchi e usa l'inizio / la fine a tuo vantaggio (più spazi bianchi e blocchi di codice facili da trovare / piegare). Non dovrebbe essere troppo difficile. Questi metodi potrebbero far parte del tuo logger ... dipende davvero da te. Ma sembra un buon punto di partenza.


+1, gli spazi bianchi sono belli .. success := CopyFile()grazie per l'idea, questo ridurrà alcune righe di codice non necessarie nel mio caso
PresleyDias,

@ S.Robins ho letto correttamente il tuo codice? il tuo metodo chiamato LogIfFileDoesNotExistcopia i file?
João Portela,

1
@ JoãoPortela Sì ... non è molto carino e non si attiene al principio della singola responsabilità. Tieni presente che questo è stato un refactoring di primo passaggio dalla parte superiore della mia testa e mirava ad aiutare l'OP a soddisfare il suo obiettivo di ridurre un po 'di disordine nel suo codice. È probabilmente una cattiva scelta del nome per il metodo in primo luogo. Lo migliorerò un po 'per migliorare. :)
S.Robins,

bello vedere che hai preso il tempo per affrontare quel problema, +1.
João Portela,

2

Direi che l'idea alla base dell'opzione 2 è la migliore. Tuttavia, penso che la direzione che hai preso peggiori le cose. L'intero non significa nulla. Quando guardi il codice, vedrai che viene registrato qualcosa, ma non sai cosa.

Invece farei qualcosa del genere:

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

Ciò mantiene la struttura del messaggio ma consente al codice di essere flessibile. È possibile definire stringhe costanti in base alle esigenze per le fasi e utilizzarle solo come parametro di fase. Ciò ti consente di essere in grado di apportare modifiche al testo effettivo in un unico posto ed effettuare tutto. L'altro vantaggio della funzione di supporto è che il testo importante è con il codice (come se fosse un commento), ma il testo che è importante solo per il file di registro viene estratto.

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');
    }
}

Questo non è qualcosa che hai menzionato nella tua domanda, ma ho notato il tuo codice. Il tuo rientro non è coerente. La prima volta che lo usi beginnon è rientrato, ma la seconda volta lo è. Fai una cosa simile con else. Direi che questo è molto più importante delle linee di registro. Quando il rientro non è coerente, è difficile scansionare il codice e seguire il flusso. Molte linee di log ripetitive sono facili da filtrare durante la scansione.


1

Che ne dici di qualcosa del genere:

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();

Il metodo NewEntry () costruisce la riga di testo (inclusa l'aggiunta di [&] attorno alle voci appropriate) e lo tiene in attesa fino a quando non vengono chiamati i metodi success () o failure (), che aggiungono la riga a "success" o 'fallimento', quindi l'output della riga nel registro. Potresti anche creare altri metodi, come info () per quando la voce di registro è per qualcosa di diverso da successo / fallimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.