需要使我的代码对团队中的其他程序员更具可读性


11

我正在delphi中工作一个项目,并且正在为该应用程序创建安装程序,主要包括三个部分。

  1. PostgreSQL安装/卸载
  2. myapplication(使用nsi创建myapplication的安装程序)安装/卸载。
  3. 通过脚本(批处理文件)在Postgres中创建表

一切正常运行,但是如果出现故障,我创建了一个LogToFileger,它将在过程的每个步骤中记录LogToFile,
如下所示

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

函数LogToFileToFile.LogToFile()This将内容写入文件。这工作得很好,但是问题是这使代码混乱了,因为它变得很难读取代码,因为一个ca只能LogToFileToFile.LogToFile()在代码中到处看到函数调用。

一个例子

 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;

如您所见LogToFileToFile.LogToFile()
之前有很多电话

 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)

现在我的整个代码就是这种情况。
它很难阅读。

有人可以建议我一种很好的方法来整理对LogToFile的调用吗?

喜欢


  1. 像这样缩进'LogToFileToFile.LogToFile()`调用

       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. 独立单元喜欢LogToFileger
    这个单位将在所有LogToFile消息switch case像这样

     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;
    

因此,只要需要,我就可以调用LogToFilegingMyMessage(1)。

谁能告诉我哪种方法更好和更干净地使用LogToFileging?


5
回答您的主题:您是否曾尝试询问您的团队他们是否理解或是否全部有意义?如果是,那么它应该“足够”可读。
Spoike

@Spoike:我确实问过,它很难读,因为到处都有logBook.log()
PresleyDias'2

“有两个主要部分”从1到3编号。我想我明白您为什么对可读性有疑问。您可能想要找到可以“编辑”以保持一致性的人员。
S.Lott

@ S.Lott我将错误的“两个”编辑为“三个” ..sory
PresleyDias

Answers:


11

添加日志记录时,您引入了两件事:

  1. 代码变得更大,因为几乎对于每个操作,您都添加了一条记录该操作(或其失败)的行
  2. 日志行本身看起来很肿,并且占用了很大的空间,因而使可读性降低。

每个问题都有其自己的相对简单的解决方案:

  1. 将代码分解为较小的功能。您可以引入一个函数“ CopyFile”来复制一个文件并记录其自身的结果,而不是使用一个包含所有副本以及错误/成功日志消息的巨型函数。这样,您的主要代码将仅包含CopyFile调用,并且将保持易于阅读。

  2. 您可以使记录器更智能。您可以传递枚举值,使情况更清晰,而不必传递包含大量重复信息的巨大字符串。或者,您可以定义更专门的Log()函数,即LogFileCopy,LogDbInsert ...无论您重复多少,请考虑将其分解为自己的函数。

如果您遵循(1),则可能会有类似以下的代码:

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

然后,您的CopyFile()仅需要几行代码即可执行操作并记录其结果,因此所有代码都保持简洁且易于阅读。

我将不使用您的方法2,因为您正在将应保留在一起的信息分离到不同的模块中。您只是在要求您的主要代码与您的日志语句不同步。但是看着LogMyMessage(5),您将永远不会知道。

更新(回应评论): 我不熟悉您使用的确切语言,因此这部分可能需要改编。看来您的所有日志消息都可以识别3件事:组成部分,操作,结果。

我认为这几乎是MainMa建议的。定义常量(而不是传递实际的字符串)(在C / C ++ / C#中,它们将成为枚举枚举类型的一部分)。因此,例如,对于组件,您可能具有:DbInstall,AppFiles,Registry,快捷方式...任何使代码较小的内容都将使其易于阅读。

如果您的语言支持可变参数传递,这也将有所帮助,不确定是否可行。因此,例如,如果操作为“ FileCopy”,则可以将该操作定义为具有两个其他用户参数:文件名和目标目录。

因此,您的文件复制行将如下所示:

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

*请注意,如果您可以将操作结果存储在单独的局部变量中并将该变量传递到Log(),也没有理由将日志行复制/粘贴两次。

您在这里看到主题吧?较少重复的代码->更具可读性的代码。


+1,您能详细介绍you could pass in enumerations values 一下吗?
PresleyDias'2

@PresleyDias:更新后
DXM

好的,是的,是重复性较小的
PresleyDias'2

2
+1“将代码分解为较小的功能。” 你不能那么强调。它只会使许多问题消失。
奥利弗·韦勒

10

看起来您需要抽象出“ LoggableAction”的概念。我在您的示例中看到了一个模式,其中所有调用都返回布尔值以指示成功或失败,唯一的区别是日志消息。

自从我写了delphi已经好几年了,所以这几乎是c#启发的伪代码,但我本以为您想要类似

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

然后您的调用代码变为

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

我不记得函数指针的Delphi语法,但是无论实现细节如何,您都在寻找围绕日志例程的某种抽象。


我本人可能会这样做,但是在不了解OP的代码结构如何的情况下,很难说这是否比定义几个额外的要调用的方法好,而又不增加方法指针的潜在混淆(取决于在OP多少知道这样的事情。
S.Robins

+1,LoggableAction()这很好,我可以直接编写返回值,而不用检查和编写。
PresleyDias

我希望+100,好答案,但我只能接受一个答案:( ..我将在我的下一个应用程序中尝试此建议,感谢您的想法
PresleyDias 2012年

3

一种可能的方法是通过使用常量来减少代码。

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

会成为:

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)

在计算屏幕上的字符数时,它具有更好的日志代码/其他代码比率。

这与您在问题的第二点中所建议的相近,除了我不会走得太远:Log(9257)明显比短Log(2, SqlInstal, Action, CopyMapSuccess, sOSdrive),但也很难阅读。9257是什么?成功了吗?一种行为?与SQL相关吗?如果您在过去十年中一直在使用此代码库,那么您将全心学习这些数字(如果有逻辑,例如9xxx是成功代码,x2xx与SQL相关,等等),但是对于那些发现新知识的开发人员而言在代码库中,短代码将是一场噩梦。

您可以将两种方法混合使用,以走得更远:使用单个常量。就个人而言,我不会那样做。您的常数都会增大:

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

否则常量将保持简短,但不是很明确:

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?

这也有两个缺点。您必须:

  • 保留一个单独的列表,将日志信息与它们各自的常数相关联。有了一个常数,它将快速增长。

  • 寻找一种在团队中实施单一格式的方法。例如,如果T2SSQ某人决定代替写作,该ST2SQL怎么办?


+1,是干净的log电话,但是您能不能向我解释它所不理解的更多内容Log(2, SqlInstal, Action, CopyMapFailure, sOSdrive),您的意思是说SqlInstal将是我定义的变量,例如SqlInstal:=[POSTGRESQL INSTALLATION]
PresleyDias

@PresleyDias:SqlInstal可以是任何值,例如value 3。然后,在中Log(),此值将在[POSTGRESQL INSTALLATION]与日志消息的其他部分串联之前被有效地转换为。
阿森尼·穆尔坚科

single format in your team是一个不错的选择
PresleyDias 2012年

3

尝试提取一系列小功能来处理所有看起来很乱的东西。有很多重复的代码可以很容易地在一个地方完成。例如:

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;

诀窍是查看代码中的所有重复项,并找到将其删除的方法。使用大量空格,并利用开头/结尾来发挥自己的优势(更多空格,并且易于查找/折叠代码块)。确实应该不太困难。这些方法可能是记录器的一部分……这完全取决于您。但这似乎是一个不错的起点。


+1,空格是一种不错的方法.. success := CopyFile()感谢您的想法,这将减少我的情况下不必要的代码行
PresleyDias 2012年

@ S.Robins我是否正确阅读了您的代码?您的方法称为LogIfFileDoesNotExist复制文件?
若昂里斯本

1
@JoãoPortela是的...这不是很漂亮,也不遵循单一责任原则。请记住,这是重构我头脑的第一步,目的是帮助OP实现他的目标,以减少代码中的混乱情况。首先,该方法的名称选择可能不佳。我会对其进行一些调整。:)
S.Robins '02

很高兴看到您花时间解决了这个问题+1。
若昂里斯本

2

我要说的是,方案2背后的想法是最好的。但是,我认为您采取的方向会使事情变得更糟。整数没有任何意义。在查看代码时,您会看到正在记录某些内容,但是您不知道是什么。

相反,我会做这样的事情:

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

这样可以保留消息结构,但可以使您的代码灵活。您可以根据需要为阶段定义常量字符串,并且只能将其用作阶段参数。这样一来,您就可以在一处更改实际文本并影响所有内容。辅助函数的另一个好处是,重要的文本与代码一起使用(就像注释一样),但是仅提取了对日志文件重要的文本。

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

这不是您在问题中提到的,但是我注意到了您的代码。您的缩进不一致。第一次使用begin时不缩进,但第二次使用。您使用进行类似的操作else。我会说这比日志行重要得多。当缩进不一致时,将很难扫描代码并遵循流程。扫描时很容易过滤掉许多重复的日志行。


1

沿着这条线怎么样:

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

NewEntry()方法将构建文本行(包括在适当的条目周围添加[&]),并保持该状态,直到调用成功()或failure()方法为止,然后在该行后附加'success'或单击“失败”,然后将行输出到日志。您还可以使用其他方法,例如info()来确定日志条目是否用于成功/失败之外的其他事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.