私のコードをチーム内の他のプログラマーにとって読みやすくする必要がある


11

私はデルファイでプロジェクトを進めており、アプリケーションのインストーラーを作成しています。主に3つの部分があります。

  1. PostgreSQLのインストール/アンインストール
  2. myapplicationmyapplicationのセットアップはnsiを使用して作成されます)インストール/アンインストール。
  3. スクリプト(バッチファイル)を使用してPostgresでテーブル作成します。

すべてがうまくスムーズに実行されますが、何かが失敗した場合は、
このようにプロセスのすべてのステップをLogToFileするLogToFilegerを作成しました

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

関数LogToFileToFile.LogToFile()これは、内容をファイルに書き込みます。これはうまく機能していますが、問題は、コードのどこでも関数呼び出しを見るだけでコードを読むことが難しくなっているため、コードが台無しになっていること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
    は、次のswitch caseようなすべてのLogToFileメッセージが含まれます。

     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:私は尋ねましたが、どこでもlogBook.log()遭遇するので、読むのは少し難しいです。
PresleyDias

「2つの主要部分があります」1〜3の番号が付けられています。なぜ読みやすさについて質問があるのか​​わかります。一貫性を保つために「編集」できる人を探したいかもしれません。
S.Lott

@ S.Lott iは 'two'を 'three'に編集しました。間違いの
理由

また、しようとする場合がありますcodereview.stackexchange.com
カーク・ブロードハースト

回答:


11

ロギングを追加したとき、2つのことを導入しました。

  1. ほとんどすべてのアクションについて、そのアクション(またはその失敗)を記録する行を追加したため、コードが大きくなりました。
  2. ログ行自体は肥大化しているように見え、非常に多くのスペースを占有するため、可読性を損ないます。

これらにはそれぞれ問題があり、比較的簡単な解決策があります。

  1. コードを小さな関数に分割します。すべてのコピーとエラー/成功のログメッセージを含む1つの巨大な関数を使用する代わりに、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、Shortcuts ...があります。コードを小さくすることで読みやすくなります。

あなたの言語が変数パラメータの受け渡しをサポートしている場合にも役立ちますが、それが可能かどうかはわかりません。たとえば、アクションが「FileCopy」の場合、そのアクションを定義して、ファイル名と宛先ディレクトリという2つの追加のユーザーパラメーターを設定できます。

したがって、ファイルをコピーする行は次のようになります。

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

*注:操作の結果を別のローカル変数に保存して、その変数をLog()に渡すことができる場合、ログ行を2回コピー/貼り付けする理由もありません。

ここにテーマがありますよね?繰り返しの少ないコード->より読みやすいコード。


+1、you could pass in enumerations values これについて詳しく教えてください。
PresleyDias

@PresleyDias:更新ポスト
DXM

わかりました、はい、繰り返しは少なくなりました
-PresleyDias

2
+1 "コードを小さな関数に分割します。" あなたはそれを十分に強調することはできません。多くの問題が消えるだけです。
オリバーワイラー

10

「LoggableAction」の概念を抽象化する必要があるようです。あなたの例には、すべての呼び出しが成功または失敗を示すブール値を返すパターンがありますが、唯一の違いはログメッセージです。

私がデルファイを書いてから何年も経っているので、これはほとんど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

3

可能なアプローチの1つは、定数を使用してコードを削減することです。

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)

画面上の文字数をカウントする際のログコード/その他のコードの比率が向上しています。

これは、あなたが質問のポイント2で提案したものに近いですが、私はこれまでのところ行きませんでした。Log(9257)明らかに、より短いですがLog(2, SqlInstal, Action, CopyMapSuccess, sOSdrive)、読むことも非常に困難です。9257とは何ですか?それは成功ですか?行動?SQLに関連していますか?過去10年間このコードベースで作業している場合、これらの数値を暗記します(ロジックがある場合、つまり9xxxが成功コードである場合、x2xxはSQLに関連しているなど)。コードベース、短いコードは悪夢です。

2つのアプローチを組み合わせることで、さらに先へ進むことができます。単一の定数を使用します。個人的には、私はそうしません。定数のサイズが大きくなります:

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?

これには2つの欠点もあります。あなたがする必要があります:

  • ログ情報をそれぞれの定数に関連付ける別のリストを保持します。単一の定数で、それは急速に成長します。

  • チームで単一のフォーマットを実施する方法を見つけてください。たとえば、の代わりにT2SSQ誰かが書くことに決めたらどうなるST2SQLでしょうか?


きれいのための+1、logコールが、あなたはより多くのそれは理解していなかった私を説明することができLog(2, SqlInstal, Action, CopyMapFailure, sOSdrive)、あなたが言うことを意味し、SqlInstalのような私の定義された変数になりますかSqlInstal:=[POSTGRESQL INSTALLATION]
PresleyDias

@PresleyDias:SqlInstalvalueのように、何でもかまいません3。次に、でLog()、この値は[POSTGRESQL INSTALLATION]ログメッセージの他の部分と連結される前に効果的に変換されます。
アルセニムルゼンコ

single format in your team良い/素晴らしいオプションです
-PresleyDias

3

厄介なものをすべて処理するために、一連の小さな関数を抽出してみてください。非常に簡単にすべてを1か所で実行できる多くの反復コードがあります。例えば:

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;

秘Theは、コード内の重複を調べ、それを削除する方法を見つけることです。たくさんの空白を使用し、あなたの利益のために開始/終了を使用します(より多くの空白、およびコードブロックの検索/折りたたみが簡単です)。それほど難しくないはずです。これらのメソッドはロガーの一部になる可能性があります...それは本当にあなた次第です。しかし、それは始めるのに良い場所のように見えます。


+1、空白は良い方法です.. success := CopyFile()アイデアのおかげで、これは私の場合、不必要なコード行をいくつか減らすでしょう
-PresleyDias

@ S.Robinsあなたのコードを正しく読みましたか?あなたのメソッドはLogIfFileDoesNotExistファイルのコピーと呼ばれますか?
ジョアンポルテラ

1
@JoãoPortelaうん...それは非常にきれいではなく、単一の責任原則に固執していません。これは私の頭の上のリファクタリングであり、OPがコードの混乱を減らすという彼の目的を満たすのを支援することを目的とした最初のパスであることに留意してください。そもそも、メソッドの名前の選択が適切でない可能性があります。改善するために少し調整します。:)
S.ロビンズ

その問題に対処するために時間をかけたことがわかります。+ 1。
ジョアンポルテラ

2

私はオプション2の背後にあるアイデアが最高だと思います。しかし、あなたがそれをとった方向は事態を悪化させると思います。整数は何も意味しません。コードを見ると、何かが記録されているのがわかりますが、何がわからないのでしょう。

代わりに、私はこのようなことをします:

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

これにより、メッセージ構造は保持されますが、コードを柔軟にすることができます。フェーズの必要に応じて定数文字列を定義し、それらをフェーズパラメータとしてのみ使用できます。これにより、1つの場所で実際のテキストを変更し、すべてに影響を与えることができます。ヘルパー関数のもう1つの利点は、重要なテキストがコードに含まれていること(コメントのように)であるが、ログファイルにのみ重要なテキストは抽象化されていることです。

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ときはインデントされませんが、2回目にはインデントされます。同様のことをで行い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()メソッドはテキスト行を作成し(適切なエントリの周りに[&]を追加することを含む)、success()またはfailure()メソッドが呼び出されるまで待機します。 「失敗」してから、その行をログに出力します。また、ログエントリが成功/失敗以外の場合のinfo()など、他のメソッドを作成することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.