C ++でのプログラミング時にCopy-Pasteテクノロジーを使用した場合の結果とその対処方法

コピー-貼り付け、Ctrl-C、Ctrl-V

C / C ++ / C ++ 0xアプリケーションのソースコードのエラーを検出するPVS-Studioアナライザーを作成しています。 この点で、PVS-Studioを使用して疑わしいコードフラグメントが検出されたさまざまなアプリケーションの大量のソースコードを参照する必要があります。 コードのセクションのコピーとその変更によりエラーが発生した場合に明確に見える十分な例を蓄積しました。 もちろん、これはプログラミング時にCopy-Pasteを使用するのが悪いという新しい考えではありません。 ただし、「コードをコピーしないでください」という推奨事項を無視しないようにして、このトピックをより慎重に見てみましょう。





通常、プログラミングでコピーペーストについて話すときは、次の状況を意味します。 関数またはコードの大部分が全体としてコピーされ、コピーされたコードが変更されます。 このようなアクションは、プログラム内に大量の同様のコードを作成し、メンテナンスを複雑にします。 アルゴリズムの同じフラグメントを異なる機能で変更する必要があり、何かを修正するのを忘れることは非常に簡単です。



この場合、コードはコピーしない方が良いと言うのが本当に適切です。 同様の動作をする関数を作成する場合は、一般的なコードをリファクタリングして個別のメソッド/クラスに分離すると便利です[1]。 または、テンプレートとラムダ関数を使用します。 これは主な問題には当てはまらないため、コードの重複を回避する方法についてはこれ以上詳しく検討しません。 主なことは、さまざまな関数でのコードの重複を避けることです。 彼らはこれについて多くのことを書いており、ほとんどのプログラマーは有用な推奨事項に精通しています。



今、その瞬間に集中しましょう。これは通常、高品質のコードの記述に関する本や記事では沈黙しています。 実際、Copy-Pasteがなければ、プログラミングはできません。 ソビエト連邦のセックスのようなものです。 彼はそうではありませんが、誰もがやっています。



このような何かを書く必要があるとき、私たちはすべて小さなコードをコピーします:



  GetMenu()-> CheckMenuItem(IDC_ LINES_X、MF_BYCOMMAND | nState);
 GetMenu()-> CheckMenuItem(IDC_ LINES_Y、MF_BYCOMMAND | nState); 


文字「X」の代わりに文字「Y」を書く必要があるという点だけが異なる行を入力するのが面倒だと正直に認めてください。 そして、これは正しく論理的です。 Visual AssistやIntelliSenceなどの特別なツールの使用を考慮しても、コピーと編集は2番目の株を再入力するよりも高速です。



ただし、コードの重複について話す意味はありません。 ここに書く方が簡単だとは思わないでください。 そのような例の多くは、任意のプログラムを使用することで提供できます。 ここでの例はGUIに関するものではなく、他のタスクでも同様のものが見つかります。



  int texlump1 = Wads.CheckNumForName( "TEXTURE1"、ns_global、wadnum);
 int texlump2 = Wads.CheckNumForName( "TEXTURE2"、ns_global、wadnum); 


問題は、そのような「マイクロコピー」ではエラーの可能性も非常に高いことです。 そして、大きなブロックをコピーするよりも多くのこのようなコードの小さなコピーがあるため、これは本当に重要な問題です。 この問題に対処する方法は明確ではないため、彼らはそれについて沈黙を保とうとします。 プログラマがコードをコピーすることを禁止することはできません。 これは狂気です。



これらのエラーの多くは、プログラムの最初の起動時に検出され、簡単にすばやく修正できます。 しかし、多くは何年もの間コードに残って生き、翼を待っています。 同様のコード行を見るのは難しく、人の注意がすぐに鈍くなるため、コード内のこのようなエラーを検出することは困難です。 同時に、Copy-Pasteから生じるエラーの存在は、実際にはプログラマのプロ意識に依存しません。 誰でも封印して何かを見ることができます。 この種の欠陥は、非常に有名で高品質のソフトウェア製品にも見られます。



どんな種類のエラーが関係しているのかをよりよく説明するために、オープンソースプロジェクトから取ったいくつかのコード例を見てみましょう。 広告として:ここにリストされているエラーは、PVS-Studio [ 2 ]の一部である汎用アナライザーを使用して発見されました。



コードは、録音および編集プログラムであるAudacityから取得されます。



  sampleCount VoiceKey :: OnBackward(...){
   ...
   int atrend = sgn(
     buffer [samplesleft-2] -buffer [samplesleft-1]);                          
   int ztrend = sgn(
    バッファ[samplesleft-WindowSizeInt-2]-
      バッファ[samplesleft-WindowSizeInt-2]);
   ...
 } 


プログラマーは勇気を持って、変数「atrend」の初期化を正しく書きました。 変数「ztrend」の初期化を書き始めました。 「sgn(buffer [samplesleft-WindowSizeInt-2]」)を書きました。その後、彼はため息をつき、文字列の一部をコピーしました。編集するのを忘れました。その結果、関数 'sgn'は引数として0を取得します。



さらに、シナリオは同様です。 プログラマーは、 Crystal Space 3D SDKに長い条件を記述します。



  inline_ bool Contains(const LSS&lss)
 {
   // LSSに2つが含まれていることを確認します 
   //スイープの開始時と終了時の球体
  帰る
    含む(スフィア(lss.mP0、lss.mRadius))&& 
    含む(Sphere(lss.mP0、lss.mRadius));
 } 


ここでは、「Contains(Sphere(lss.mP0、lss.mRadius))」をコピーして、名前「mP0」を「mP1」に置き換えます。 しかし、誤って忘れることは非常に簡単です。



おそらく、プログラムウィンドウが突然奇妙な動作を開始することに気づいたでしょう。 たとえば、多くのプログラマーはVisual Studio 2010の第1版の検索ボックスを覚えています。このような奇妙な状況は、状況と次のようなコードの適切な組み合わせが原因で発生すると思います



  void COX3DTabViewContainer :: OnNcPaint() 
 {
   ...
   if(rectClient.top <rectClient.bottom &&
      rectClient.top <rectClient.bottom)
   {
     dc.ExcludeClipRect(rectClient);
   }
   ...
 } 


このコードは、よく知られているクラスUltimate ToolBoxのセットから取得されています。 通常、コントロールは描画されるかどうかに関係なく、その場所に依存します。



また、 eLynx Image Processing SDKでは、行全体がコピーされ、タイプミスが複製されました。



  void uteTestRunner :: StressBayer(uint32 iFlags)
 {
   ...
  静的EPixelFormat ms_pfList [] = 
     {PF_Lub、PF_Lus、PF_Li、PF_Lf、PF_Ld};
   const int fsize = sizeof(ms_pfList)/ sizeof(ms_pfList);

   static EBayerMatrix ms_bmList [] = 
     {BM_GRBG、BM_GBRG、BM_RGGB、BM_BGGR、BM_None};
   const int bsize = sizeof(ms_bmList)/ sizeof(ms_bmList);
   ...
 } 


忘れられたポインターの逆参照により、変数「fsize」は1です。そして、このコードは「bsize」を初期化するように適合されました。 コードをコピーしないと、このような間違いを2回続けて犯す可能性はないと思います。



EIB Suiteプロジェクトでは、行「if(_relativeTime <= 143)」がコピーおよび編集されました。 しかし、最後の条件でのみ、変更を忘れていました。



 文字列TimePeriod :: toString()const
 {
   ...
   if(_relativeTime <= 143)
     os <<((int)_relativeTime + 1)* 5 << _( "分");
   else if(_relativeTime <= 167)
     os << 12 * 60 +((int)_relativeTime-143)* 30 << _( "分");
   else if(_relativeTime <= 196)
     os <<(int)_relativeTime-166 << _( "days");
   else if(_relativeTime <= 143)
     os <<(int)_relativeTime-192 << _( "週");
   ...
 } 


したがって、コードは「os <<(int)_relativeTime-192 << _( "weeks");」です。 制御を得ることはありません。



Intelのプログラマでさえ、半神ではなく単なるプログラマです。 TickerTapeプロジェクトでのコピーの失敗:



  void DXUTUpdateD3D10DeviceStats(...)
 {
   ...
   else if(DeviceType == D3D10_DRIVER_TYPE_SOFTWARE)
     wcscpy_s(pstrDeviceStats、256、L「WARP」);
   else if(DeviceType == D3D10_DRIVER_TYPE_HARDWARE)
     wcscpy_s(pstrDeviceStats、256、L「ハードウェア」);
   else if(DeviceType == D3D10_DRIVER_TYPE_SOFTWARE)
     wcscpy_s(pstrDeviceStats、256、L「ソフトウェア」);
   ...
 } 


条件「DeviceType == D3D10_DRIVER_TYPE_SOFTWARE」が2回繰り返されます。



一般に、条件ステートメントの茂みでは、エラーを表示するのは非常に簡単です。 Multi-threaded Dynamic Queueの実装では、IsFixed()関数が何を返すかに関係なく、同じことを行います。



  BOOL CGridCellBase :: PrintCell(...)
 {
   ...
   if(固定())
     crFG =(GetBackClr()!= CLR_DEFAULT)?
       GetTextClr():pDefaultCell-> GetTextClr();
  他に
     crFG =(GetBackClr()!= CLR_DEFAULT)?
       GetTextClr():pDefaultCell-> GetTextClr();
   ...
 } 


ところで、コードのコピーは簡単で楽しいです! 余分な行を書いても構いません。 :)



  void RB_CalcColorFromOneMinusEntity(unsigned char * dstColors){
   ...
   unsigned char invModulate [3];
   ...
   invModulate [0] = 255-backEnd.currentEntity-> e.shaderRGBA [0];
   invModulate [1] = 255-backEnd.currentEntity-> e.shaderRGBA [1];
   invModulate [2] = 255-backEnd.currentEntity-> e.shaderRGBA [2];
   invModulate [3] = 255-backEnd.currentEntity-> e.shaderRGBA [3];
   ...
 } 


配列「invModulate」が3つの要素のみで構成されることは問題ではありません。 このコードは、伝説的なWolfenstein 3Dゲームのプロジェクトから取得されています。



最後に、例はより複雑です。 コードは、非常に便利なツールNotepad ++から取得されています



  void KeyWordsStyleDialog :: updateDlg() 
 {
   ...
  スタイル&w1Style =
     _pUserLang-> _ styleArray.getStyler(STYLE_WORD1_INDEX);
   styleUpdate(w1Style、_pFgColour [0]、_ pBgColour [0]、
     IDC_KEYWORD1_FONT_COMBO、IDC_KEYWORD1_FONTSIZE_COMBO、
     IDC_KEYWORD1_BOLD_CHECK、IDC_KEYWORD1_ITALIC_CHECK、
     IDC_KEYWORD1_UNDERLINE_CHECK);

  スタイル&w2Style =
     _pUserLang-> _ styleArray.getStyler(STYLE_WORD2_INDEX);
   styleUpdate(w2Style、_pFgColour [1]、_ pBgColour [1]、
     IDC_KEYWORD2_FONT_COMBO、IDC_KEYWORD2_FONTSIZE_COMBO、
     IDC_KEYWORD2_BOLD_CHECK、IDC_KEYWORD2_ITALIC_CHECK、
     IDC_KEYWORD2_UNDERLINE_CHECK);

  スタイル&w3Style =
     _pUserLang-> _ styleArray.getStyler(STYLE_WORD3_INDEX);
   styleUpdate(w3Style、_pFgColour [2]、_ pBgColour [2]、
     IDC_KEYWORD3_FONT_COMBO、IDC_KEYWORD3_FONTSIZE_COMBO、
     IDC_KEYWORD3_BOLD_CHECK、IDC_KEYWORD3_BOLD_CHECK、
     IDC_KEYWORD3_UNDERLINE_CHECK);

  スタイル&w4Style =
     _pUserLang-> _ styleArray.getStyler(STYLE_WORD4_INDEX);
   styleUpdate(w4Style、_pFgColour [3]、_ pBgColour [3]、
     IDC_KEYWORD4_FONT_COMBO、IDC_KEYWORD4_FONTSIZE_COMBO、
     IDC_KEYWORD4_BOLD_CHECK、IDC_KEYWORD4_ITALIC_CHECK、
     IDC_KEYWORD4_UNDERLINE_CHECK);
   ...
 } 


ここで間違いを考えるには目をつぶらなければなりません。 したがって、わかりやすくするためにコードを短くします。



  styleUpdate(...
   IDC_KEYWORD1_BOLD_CHECK、IDC_KEYWORD1_ITALIC_CHECK、
   ...);
 styleUpdate(...
   IDC_KEYWORD2_BOLD_CHECK、IDC_KEYWORD2_ITALIC_CHECK、
   ...);
 styleUpdate(...
   IDC_KEYWORD3_BOLD_CHECK、IDC_KEYWORD3_BOLD_CHECK、
   ...);
 styleUpdate(...
   IDC_KEYWORD4_BOLD_CHECK、IDC_KEYWORD4_ITALIC_CHECK、
   ...); 


開発者の手がゆらぎ、間違ったリソース名をコピーしました。



この記事で欠陥コードを提供することはできますが、これはもはや興味深いことではありません。 これらすべての例で、私はそのようなエラーがさまざまなプロジェクトに存在し、初心者と専門家の両方によって行われていることを示したかっただけです。 最後に、これらすべてをどうするかという問題の議論に移ります。



正直に言って、私は完全な答えを知りません。 少なくとも似たような状況に関する本では、私は読みませんでした。 しかし、実際には、プログラムの小さなコピーペーストの結果にしばしば遭遇しました。 自分で含む。 質問に答えて即興で演奏する必要があります。



次の位置から進めます。



プログラマーはコードのセクションをコピーし、便利なようにコピーします。 したがって、このようなエラーは常にプログラムで発生します。



この結論から:



このようなエラーを防ぐことは完全に不可能ですが、エラーが発生する可能性を減らすことはできます。



この種のエラーの数を減らすには、2つの方法があります。 まず、静的コードアナライザーなどのツールを使用するのが賢明です。 これらにより、このクラスの多くのエラーを検出できます。 そして、彼らはそれを最も早い段階で行います。 テスト中に見つかった同じエラーを処理するよりも、コードを記述した直後にエラーを検出して修正する方が安価で簡単です。



2番目の方法は、場合によってはエラーの数を減らすのに役立ちますが、自分自身を規律し、コピーしたコードを特別な方法でフォーマットすることです。 例で説明します。



  int ztrend = sgn(
    バッファー[samplesleft-WindowSizeInt-2] -buffer [samplesleft-WindowSizeInt-2]); 


そのため、コードが次のように見える場合よりもエラーに気付くのははるかに困難です。



  int ztrend = sgn(
    バッファ[samplesleft-WindowSizeInt-2]-
    バッファ[samplesleft-WindowSizeInt-2]); 


コードは、異なる場所が視覚的に列に並ぶようにフォーマットする必要があります。 したがって、間違いを犯すことははるかに困難です。 多くの場合、これが保存されないことは明らかであり、上記のような例を挙げました。 ただし、少なくとも何もしないよりはましです。



残念ながら、少なくとも何らかの形でコピーペーストに関連するエラーの数を減らす他の方法は知りません。 重複するコードや同様のコードに対しては引き続き検索ツールを使用できますが、これは静的アナライザーを使用することのアドバイスによるものです。



読者に訴えます。 これについての考えを共有し、コピーペーストエラーを回避する他の方法を提案する場合、私は興味があります。 おそらく興味深いアイデアが出てきて、多くの人が大きな利益をもたらすでしょう。



書誌リスト



  1. Steve McConnell、「Code Complete、第2版」Microsoft Press、ペーパーバック、第2版、2004年6月発行、914ページ、ISBN:0-7356-1967-0。 (パート24.3。リファクタリングの理由)
  2. プレゼンテーション「PVS-Studio、現代のリソース集約型アプリケーションの開発のための包括的なソリューション。」 http://www.viva64.com/en/pvs-studio-presentation/



All Articles