Preciso tornar meu código mais legível para os outros programadores da minha equipe

11

Estou trabalhando em um projeto no delphi e estou criando um instalador para o aplicativo, existem três partes principais.

  1. Instalação / desinstalação do PostgreSQL
  2. instalação / desinstalação do myapplication (a configuração do myapplication é criada usando o nsi).
  3. Criando tabelas no Postgres por meio de script (arquivos em lote).

Tudo corre bem e sem problemas, mas se algo falhar, eu criei um LogToFileger que irá LogToFile a cada passo do processo,
como este

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

A função LogToFileToFile.LogToFile()Isso gravará o conteúdo em um arquivo. Isso está funcionando bem, mas o problema é que isso atrapalhou o código, pois tornou-se difícil ler o código, pois só é possível ver a LogToFileToFile.LogToFile()chamada de função em todo o código

um exemplo

 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;

como você pode ver, há muitas LogToFileToFile.LogToFile()chamadas
antes de

 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)

este é o caso em todo o meu código agora.
é difícil de ler.

alguém pode me sugerir uma boa maneira de organizar as chamadas para o LogToFile?

gostar

  1. Recuar a chamada 'LogToFileToFile.LogToFile () `
    assim

       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. Unidade separada como LogToFileger
    esta unidade terá todas as mensagens do LogToFile de forma switch casesemelhante a esta

     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;
    

para que eu possa chamar o LogToFilegingMyMessage (1) sempre que necessário.

Alguém pode me dizer qual é uma abordagem melhor e mais limpa para o LogToFileging dessa maneira?

PresleyDias
fonte
5
Para responder ao seu tópico: Você já tentou perguntar à sua equipe se eles entendem ou se tudo faz sentido? Se sim, então deve ser "suficiente" legível.
Spoike
@ Spike: eu perguntei, é um pouco difícil de ler, como em todos os lugares o logBook.log()é encontrado.
PresleyDias
"existem duas partes principais" numeradas de 1 a 3. Acho que vejo por que você tem uma pergunta sobre legibilidade. Você pode encontrar alguém que possa "editar" por consistência.
31512 S.Lott
@ S.Lott eu editei o 'two' para 'three' ..sory pelo erro
PresleyDias
Você também pode tentar codereview.stackexchange.com
Kirk Broadhurst

Respostas:

11

Quando você adicionou o log, introduziu duas coisas:

  1. O código ficou maior porque, para quase todas as ações, você adicionou uma linha que registra essa ação (ou sua falha)
  2. As próprias linhas de toras parecem inchadas e diminuem a legibilidade porque ocupam muito espaço.

Cada um desses problemas possui sua própria solução relativamente simples:

  1. Divida o código em funções menores. Em vez de ter uma função gigante que contém todas as suas cópias, além de mensagens de log para erro / sucesso, você pode introduzir uma função "CopyFile", que copia exatamente um arquivo e registra seu próprio resultado. Dessa forma, seu código principal consistiria apenas em chamadas CopyFile e permaneceria fácil de ler.

  2. Você poderia tornar seu criador de logs mais inteligente. Em vez de passar uma string gigante com muitas informações repetitivas, você poderia passar valores de enumerações que tornariam as coisas mais claras. Ou você pode definir funções Log () mais especializadas, ou seja, LogFileCopy, LogDbInsert ... O que quer que você repita muito, considere levar isso para sua própria função.

Se você seguir (1), poderá ter um código parecido com este:

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

Então seu CopyFile () precisa apenas de algumas linhas de código para executar a ação e registrar seu resultado, para que todo o seu código permaneça conciso e fácil de ler.

Eu ficaria longe da sua abordagem nº 2, pois você está desanexando informações que devem permanecer juntas em diferentes módulos. Você está apenas pedindo que seu código principal fique fora de sincronia com suas instruções de log. Mas olhando para o LogMyMessage (5), você nunca saberá isso.

UPDATE (resposta ao comentário): Não estou familiarizado com o idioma exato que você está usando, portanto, essa parte pode ter que ser um pouco adaptada. Parece que todas as suas mensagens de log identificam três coisas: componente, ação, resultado.

Eu acho que isso é basicamente o que MainMa sugeriu. Em vez de passar a string real, defina constantes (em C / C ++ / C #, elas seriam parte do tipo de enumeração de enum). Por exemplo, para componentes, você pode ter: DbInstall, AppFiles, Registry, Atalhos ... Qualquer coisa que diminua o código facilitará a leitura.

Também ajudaria se o seu idioma suportasse a passagem de parâmetros variáveis, não tendo certeza se isso é possível. Por exemplo, se a ação for "FileCopy", você poderá definir essa ação para ter dois parâmetros adicionais do usuário: nome do arquivo e diretório de destino.

Portanto, suas linhas de cópia de arquivos ficariam assim:

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

* note, também não há razão para copiar / colar a linha de log duas vezes se você puder armazenar o resultado da operação em uma variável local separada e apenas passar essa variável para Log ().

Você vê o tema aqui, certo? Código menos repetitivo -> código mais legível.

DXM
fonte
+1, você pode me dizer mais sobre you could pass in enumerations values isso?
PresleyDias
@PresleyDias: atualização post
DXM
ok tem, sim menos repetitivo> código mais legível
PresleyDias
2
+1 "Divida o código em funções menores." Você não pode enfatizar isso o suficiente. Isso apenas faz com que muitos problemas simplesmente desapareçam.
Oliver Weiler
10

Parece que você precisa abstrair o conceito de "LoggableAction". Estou vendo um padrão no seu exemplo em que todas as chamadas retornam um bool para indicar sucesso ou falha e a única diferença é a mensagem de log.

Faz anos desde que eu escrevi o delphi, então esse é um pseudo-código inspirado em c #, mas eu pensaria que você quer algo como

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

Então o seu código de chamada se torna

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

Não me lembro da sintaxe Delphi para ponteiros de função, mas quaisquer que sejam os detalhes da implementação, algum tipo de abstração em torno da rotina de log parece ser o que você está procurando.

MarcE
fonte
Eu provavelmente iria por esse caminho sozinho, mas sem saber mais sobre como o código do OP está estruturado, é difícil saber se isso seria melhor do que simplesmente definir alguns métodos extras para chamar, sem adicionar a confusão potencial de ponteiros de método (dependendo de quanto o OP sabe sobre essas coisas.
S.Robins
+1, LoggableAction()isso é legal, eu posso escrever diretamente o valor retornado em vez de verificar e escrever.
PresleyDias
Eu desejo +100, ótima resposta, mas posso aceitar apenas uma resposta :( .. tentarei esta sugestão no meu próximo aplicativo, obrigado pela idéia #
7772
3

Uma abordagem possível é reduzir o código usando constantes.

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

se tornaria:

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)

que possui uma melhor relação de código de log / outro código ao contar o número de caracteres na tela.

Isso é parecido com o que você sugeriu no ponto 2 da sua pergunta, exceto que eu não iria tão longe: Log(9257)é obviamente mais curto que Log(2, SqlInstal, Action, CopyMapSuccess, sOSdrive), mas também bastante difícil de ler. O que é 9257? Isso é um sucesso? Uma ação? Está relacionado ao SQL? Se você trabalha nessa base de código nos últimos dez anos, aprenderá esses números de cor (se houver uma lógica, ou seja, 9xxx são códigos de sucesso, x2xx estão relacionados ao SQL etc.), mas para um desenvolvedor novo que descobre na base de código, códigos curtos serão um pesadelo.

Você pode ir além, misturando as duas abordagens: use uma única constante. Pessoalmente, eu não faria isso. Suas constantes aumentarão de tamanho:

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

ou as constantes permanecerão curtas, mas não muito explícitas:

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?

Isso também tem duas desvantagens. Você terá que:

  • Mantenha uma lista separada associando as informações de log às respectivas constantes. Com uma única constante, ele crescerá rapidamente.

  • Encontre uma maneira de impor um único formato em sua equipe. Por exemplo, e se, em vez de T2SSQ, alguém decidir escrever ST2SQL?

Arseni Mourzenko
fonte
+1, para a logchamada limpa , mas você pode me explicar mais que ela não entendeu Log(2, SqlInstal, Action, CopyMapFailure, sOSdrive)? Quer dizer que SqlInstalserá minha variável definida como SqlInstal:=[POSTGRESQL INSTALLATION] ?
PresleyDias
@ PresleyDias: SqlInstalpode ser qualquer coisa, por exemplo, um valor 3. Então, em Log(), esse valor será convertido efetivamente [POSTGRESQL INSTALLATION]antes de ser concatenado com outras partes da mensagem de log.
Arseni Mourzenko
single format in your teamé uma opção boa / ótima
PresleyDias 6/12
3

Tente extrair uma série de pequenas funções para lidar com todas as coisas confusas. Há muitos códigos repetidos que podem ser feitos com muita facilidade em um único local. Por exemplo:

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;

O truque é examinar qualquer duplicação no seu código e encontrar maneiras de removê-lo. Use muito espaço em branco e use o início / fim para sua vantagem (mais espaço em branco e blocos de código fáceis de encontrar / dobrar). Realmente não deve ser muito difícil. Esses métodos podem fazer parte do seu logger ... depende de você realmente. Mas isso parece um bom lugar para começar.

S.Robins
fonte
+1, espaços em branco é bom caminho .. success := CopyFile()obrigado pela idéia, isso vai reduzir algumas linhas desnecessárias de código no meu caso
PresleyDias
@ S.Robins li seu código corretamente? seu método chamado LogIfFileDoesNotExistcopia arquivos?
João Portela
1
@ JoãoPortela Sim ... não é muito bonito e não se atém ao princípio da responsabilidade única. Lembre-se de que este foi um primeiro passe para refatorar o topo da minha cabeça e tinha como objetivo ajudar o OP a cumprir seu objetivo de reduzir um pouco a confusão de seu código. Provavelmente é uma má escolha de nome para o método em primeiro lugar. Vou ajustá-lo um pouco para melhorar. :)
S.Robins
É bom ver que você gastou um tempo para resolver esse problema, +1.
João Portela
2

Eu diria que a idéia por trás da opção 2 é a melhor. No entanto, acho que a direção que você tomou piorou as coisas. O número inteiro não significa nada. Ao olhar para o código, você verá que algo está sendo registrado, mas não sabe o que.

Em vez disso, eu faria algo assim:

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

Isso mantém a estrutura da mensagem, mas permite que seu código seja flexível. Você pode definir cadeias constantes conforme necessário para as fases e usá-las apenas como parâmetro de fase. Isso permite que você seja capaz de alterar o texto real em um único local e efetuar tudo. O outro benefício para a função auxiliar é que o texto importante está com o código (como se fosse um comentário), mas o texto que é importante apenas para o arquivo de log é abstraído.

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

Isso não é algo que você mencionou na sua pergunta, mas notei sobre o seu código. Seu recuo não é consistente. A primeira vez que você o usa beginnão é recuado, mas a segunda vez. Você faz uma coisa semelhante com else. Eu diria que isso é muito mais importante do que as linhas de log. Quando o recuo não é consistente, fica difícil escanear o código e seguir o fluxo. Muitas linhas de log repetitivas são fáceis de filtrar durante a digitalização.

unholysampler
fonte
1

Que tal algo nessa linha:

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

O método NewEntry () criaria a linha de texto (incluindo a adição de [&] em torno das entradas apropriadas) e manteria isso em espera até que os métodos success () ou fail () sejam chamados, que anexam a linha com 'success' ou 'falha' e, em seguida, imprima a linha no log. Você também pode criar outros métodos, como info (), para quando a entrada de log for para algo que não seja sucesso / falha.

GrandmasterB
fonte