最終行効果

コピーペースト

コードのコピーに起因する多くのエラーを学びました。 そして、私は、ほとんどの場合、同じコードの最後の断片で間違いが行われることを確認します。 以前、私は本でこの現象の説明を見ていなかったので、それについて書くことにしました。 私はそれを「最終ライン効果」と呼びました。



はじめに



私の名前はアンドレイ・カルポフです。 私は異常な活動をしています。 静的アナライザーを使用してアプリケーションコードを調べ、見つかったエラーと欠点について説明します。 これは、実用的で利己的な動機のためです。 そのため、当社はPVS-Studioツールを宣伝しています。 エラーが見つかりました。 記事で説明されています。 注目を集めました。 利益 しかし、今日の記事はアナライザーに関するものではありません。



プロジェクトを分析する過程で、気付いたエラーと対応するコードフラグメントを特別なデータベースに保存します。 ところで、誰でもこのデータベースの内容を知ることができます。 これを一連のhtmlページに変換し、「 識別されたエラー 」セクションのサイトに配置します。



ベースはユニークです! 現在、約1500個のエラーのあるコードが含まれています。 彼女は、これらのエラーのパターンを特定できる人々を待っています。 これは、本や記事の多くの研究や資料の基礎として役立ちます。



具体的には、蓄積された資料については調査しませんでした。 それにもかかわらず、1つの規則性が非常に明確に現れるため、私はそれをより詳細に研究することにしました。 記事で私はしばしば「最後の行に注意を払う」ことを書かなければなりません。 これは偶然ではないと判断しました。



最終行効果



プログラムのソースコードでは、多くの場合、同じタイプの複数の構造を連続して記述する必要があります。 同じ種類のコードを数回入力するのは退屈で効率的ではありません。 したがって、Copy-Pasteメソッドが使用されます。 コードスニペットは数回コピーされ、その後編集が行われます。 この方法が悪いことは誰もが知っています。 何かを変更するのを忘れがちであり、その結果、コードにエラーが含まれます。 残念ながら、多くの場合、良い代替手段はありません。



次にパターンについて説明します。 ほとんどの場合、最後にコピーしたコードブロックで間違いが発生することがわかりました。



簡単な短い例:

inline Vector3int32& operator+=(const Vector3int32& other) { x += other.x; y += other.y; z += other.y; return *this; }
      
      





「z + = other.y;」という行に注意してください。 彼らはその中で「y」を「z」に変更するのを忘れていました。



例は人工的なようです。 しかし、このコードは実際のアプリケーションから取得されています。 さらに、これは非常に頻繁で一般的な状況であることを納得できるように示します。 これがまさに「最終ライン効果」の外観です。 多くの場合、同じタイプの修正を行った最後の最後に間違いを犯します。



登山者が最後の数十メートルの上り坂でしばしば故障することをどこかで聞いた。 疲れているからではありません。 彼らは、ほんのわずかしか残っていないことをただ喜んでいます。 彼らは勝利の甘い味を楽しみにしています。 その結果、彼らは注意を弱め、致命的な間違いを犯します。 どうやら、プログラマーでこのようなことが起こります。



今、いくつかの数字。



エラーベースを研究した後、私はコピー-ペーストを使用して書かれたように思える84個のコードフラグメントを特定しました。 これらのうち、41個のフラグメントには、コピーされたブロックの中央のどこかにエラーが含まれています。 以下に例を示します。

 strncmp(argv[argidx], "CAT=", 4) && strncmp(argv[argidx], "DECOY=", 6) && strncmp(argv[argidx], "THREADS=", 6) && strncmp(argv[argidx], "MINPROB=", 8)) {
      
      





文字列 "THREADS ="の長さは6文字ではなく、8文字です。



残りの43のケースでは、最後にコピーされたブロックでエラーが見つかりました。



一見したところ、43という数字は41をかなり上回っています。しかし、同じタイプのブロックがかなりあることに注意してください。 そして、エラーは最初のブロック、2番目、5番目、さらには10番目のブロックにある可能性があります。 ブロック内のエラーが比較的均等に分布し、最後に急激なジャンプが発生します。



平均して、同じタイプのブロックの数は5です。



最初の4ブロックが41個のエラーを占めていることがわかります。 または、ブロックあたり約10個のエラー。



最後の5番目のブロックは43個のエラーを占めています!



明確にするために、次のようなおおよそのスケジュールを作成できます。



図1.同様のコードの5つのブロックのエラー数のおおよそのグラフ。



図1.同様のコードの5つのブロックのエラー数のおおよそのグラフ。



判明した:



最後にコピーされたブロックでミスをする可能性は、他のブロックの4倍です。



私はこれから大々的な結論を下していません。 ただ興味深い観察。 実用的な観点から、それについて知ることは有用です。 その後、最後にリラックスしないように強制することができます。





これらは私の空想ではなく、人生の現実であると読者に納得させることです。 このため、私の言葉を確認する例を示します。



もちろん、すべての例を挙げるわけではありません。 私は自分自身を最も単純または最も示唆的なものに制限します。



ソースエンジンSDK



 inline void Init( float ix=0, float iy=0, float iz=0, float iw = 0 ) { SetX( ix ); SetY( iy ); SetZ( iz ); SetZ( iw ); }
      
      





最後に、SetW()関数を呼び出す必要がありました。



クロム



 if (access & FILE_WRITE_ATTRIBUTES) output.append(ASCIIToUTF16("\tFILE_WRITE_ATTRIBUTES\n")); if (access & FILE_WRITE_DATA) output.append(ASCIIToUTF16("\tFILE_WRITE_DATA\n")); if (access & FILE_WRITE_EA) output.append(ASCIIToUTF16("\tFILE_WRITE_EA\n")); if (access & FILE_WRITE_EA) output.append(ASCIIToUTF16("\tFILE_WRITE_EA\n")); break;
      
      





最後と最後から2番目のブロックは一致します。



Reactos



 if (*ScanString == L'\"' || *ScanString == L'^' || *ScanString == L'\"')
      
      





マルチセフトオート



 class CWaterPolySAInterface { public: WORD m_wVertexIDs[3]; }; CWaterPoly* CWaterManagerSA::CreateQuad (....) { .... pInterface->m_wVertexIDs [ 0 ] = pV1->GetID (); pInterface->m_wVertexIDs [ 1 ] = pV2->GetID (); pInterface->m_wVertexIDs [ 2 ] = pV3->GetID (); pInterface->m_wVertexIDs [ 3 ] = pV4->GetID (); .... }
      
      





最後の行は慣性によって書かれており、余分です。 配列には3つの要素しかありません。



ソースエンジンSDK



 intens.x=OrSIMD(AndSIMD(BackgroundColor.x,no_hit_mask), AndNotSIMD(no_hit_mask,intens.x)); intens.y=OrSIMD(AndSIMD(BackgroundColor.y,no_hit_mask), AndNotSIMD(no_hit_mask,intens.y)); intens.z=OrSIMD(AndSIMD(BackgroundColor.y,no_hit_mask), AndNotSIMD(no_hit_mask,intens.z));
      
      





最後の行では、「BackgroundColor.y」を「BackgroundColor.z」に置き換えるのを忘れていました。



トランスプロテオームパイプライン



 void setPepMaxProb(....) { .... double max4 = 0.0; double max5 = 0.0; double max6 = 0.0; double max7 = 0.0; .... if ( pep3 ) { ... if ( use_joint_probs && prob > max3 ) ... } .... if ( pep4 ) { ... if ( use_joint_probs && prob > max4 ) ... } .... if ( pep5 ) { ... if ( use_joint_probs && prob > max5 ) ... } .... if ( pep6 ) { ... if ( use_joint_probs && prob > max6 ) ... } .... if ( pep7 ) { ... if ( use_joint_probs && prob > max6 ) ... } .... }
      
      





最後の条件では、「prob> max6」を「prob> max7」に置き換えるのを忘れていました。



セカン



 inline typename Value<Pipe>::Type const & operator*() { tmp.i1 = *in.in1; tmp.i2 = *in.in2; tmp.i3 = *in.in2; return tmp; }
      
      





Slimdx



 for( int i = 0; i < 2; i++ ) { sliders[i] = joystate.rglSlider[i]; asliders[i] = joystate.rglASlider[i]; vsliders[i] = joystate.rglVSlider[i]; fsliders[i] = joystate.rglVSlider[i]; }
      
      





最後の行は、rglFSlider配列を使用することでした。



Qt



 if (repetition == QStringLiteral("repeat") || repetition.isEmpty()) { pattern->patternRepeatX = true; pattern->patternRepeatY = true; } else if (repetition == QStringLiteral("repeat-x")) { pattern->patternRepeatX = true; } else if (repetition == QStringLiteral("repeat-y")) { pattern->patternRepeatY = true; } else if (repetition == QStringLiteral("no-repeat")) { pattern->patternRepeatY = false; pattern->patternRepeatY = false; } else { //TODO: exception: SYNTAX_ERR }
      
      





最後のブロックでは、「patternRepeatX」を忘れていました。 する必要があります:

 pattern->patternRepeatX = false; pattern->patternRepeatY = false;
      
      





Reactos



 const int istride = sizeof(tmp[0]) / sizeof(tmp[0][0][0]); const int jstride = sizeof(tmp[0][0]) / sizeof(tmp[0][0][0]); const int mistride = sizeof(mag[0]) / sizeof(mag[0][0]); const int mjstride = sizeof(mag[0][0]) / sizeof(mag[0][0]);
      
      





変数 'mjstride'は常に1に等しくなります。 最後の行は次のようになります。

 const int mjstride = sizeof(mag[0][0]) / sizeof(mag[0][0][0]);
      
      





Mozilla Firefox



 if (protocol.EqualsIgnoreCase("http") || protocol.EqualsIgnoreCase("https") || protocol.EqualsIgnoreCase("news") || protocol.EqualsIgnoreCase("ftp") || <<<--- protocol.EqualsIgnoreCase("file") || protocol.EqualsIgnoreCase("javascript") || protocol.EqualsIgnoreCase("ftp")) { <<<---
      
      





最後に疑わしい行「ftp」。 この行はすでに比較されています。



Quake-iii-arena



 if (fabs(dir[0]) > test->radius || fabs(dir[1]) > test->radius || fabs(dir[1]) > test->radius)
      
      





dirセル[2]の値は確認しませんでした。



クラン



 return (ContainerBegLine <= ContaineeBegLine && ContainerEndLine >= ContaineeEndLine && (ContainerBegLine != ContaineeBegLine || SM.getExpansionColumnNumber(ContainerRBeg) <= SM.getExpansionColumnNumber(ContaineeRBeg)) && (ContainerEndLine != ContaineeEndLine || SM.getExpansionColumnNumber(ContainerREnd) >= SM.getExpansionColumnNumber(ContainerREnd)));
      
      





最後に、式「SM.getExpansionColumnNumber(ContainerREnd)」がそれ自体と比較されます。



モンゴッド



 bool operator==(const MemberCfg& r) const { .... return _id==r._id && votes == r.votes && h == rh && priority == r.priority && arbiterOnly == r.arbiterOnly && slaveDelay == r.slaveDelay && hidden == r.hidden && buildIndexes == buildIndexes; }
      
      





最後に「r。」について忘れていました。



アンリアルエンジン4



 static bool PositionIsInside(....) { return Position.X >= Control.Center.X - BoxSize.X * 0.5f && Position.X <= Control.Center.X + BoxSize.X * 0.5f && Position.Y >= Control.Center.Y - BoxSize.Y * 0.5f && Position.Y >= Control.Center.Y - BoxSize.Y * 0.5f; }
      
      





最後の行では、2つの編集を忘れていました。 最初に、 "> ="を "<=に置き換えます。次に、マイナスをプラスに置き換えます。



Qt



 qreal x = ctx->callData->args[0].toNumber(); qreal y = ctx->callData->args[1].toNumber(); qreal w = ctx->callData->args[2].toNumber(); qreal h = ctx->callData->args[3].toNumber(); if (!qIsFinite(x) || !qIsFinite(y) || !qIsFinite(w) || !qIsFinite(w))
      
      





最新のqIsFinite関数呼び出しでは、 'h'変数を引数として使用する必要があります。



Openssl



 if (!strncmp(vstart, "ASCII", 5)) arg->format = ASN1_GEN_FORMAT_ASCII; else if (!strncmp(vstart, "UTF8", 4)) arg->format = ASN1_GEN_FORMAT_UTF8; else if (!strncmp(vstart, "HEX", 3)) arg->format = ASN1_GEN_FORMAT_HEX; else if (!strncmp(vstart, "BITLIST", 3)) arg->format = ASN1_GEN_FORMAT_BITLIST;
      
      





文字列「BITLIST」の長さは3文字ではなく、7文字です。



これについて詳しく見てみましょう。 上記の例は十分すぎると思います。



おわりに



この記事では、Copy-Pasteを使用すると、最後にコピーされたブロックでエラーが発生する確率が他のブロックよりも4倍高いことを学びました。



これは人間の心理学の特徴であり、専門的なスキルではありません。 この記事では、ClangやQtなどのプロジェクトの優秀な開発者でさえ、最終的にエラーを起こしやすいことがわかりました。



私の観察が役に立つことを願っています。 そして、おそらく、それは人々に蓄積されたエラーベースを研究するように促すでしょう。 多くの興味深いパターンを見つけて、プログラマー向けの新しい推奨事項を策定できると思います。



この記事は英語です。



この記事を英語圏の聴衆と共有したい場合は、翻訳へのリンクを使用してください:Andrey Karpov。 ラストラインエフェクト



記事を読んで質問がありますか?
多くの場合、記事には同じ質問が寄せられます。 ここでそれらに対する回答を収集しました: PVS-StudioおよびCppCatバージョン2014に関する記事の読者からの質問への回答 。 リストをご覧ください。




All Articles