Mein Name ist Andrey Karpov. Ich habe Hunderte von Fehlern untersucht, die durch "Copy-Paste" verursacht wurden. Man kann mit Sicherheit sagen, dass Programmierer oft im letzten Block eines großen Codeabschnitts Fehler machen. Kein Programmierbuch scheint dieses Phänomen behandelt zu haben, also beschloss ich, selbst darüber zu schreiben. Ich nenne es den "Effekt der letzten Zeile".

copy-paste

Der Effekt der letzten Zeile

Beim Programmieren müssen Programmierer oft eine Reihe ähnlicher Konstrukte schreiben. Zeile für Zeile zu tippen ist langweilig und ineffizient. Deshalb verwenden sie die "Copy-Paste"-Technik: Ein Codestück wird mehrmals kopiert und eingefügt und dann geändert. Jeder kennt den Nachteil: Es ist leicht, nach dem Einfügen zu vergessen, etwas zu ändern, was schließlich Fehler hervorbringt. Leider gibt es oft keinen besseren Weg.

Welches Muster habe ich also entdeckt? Ich fand heraus, dass Fehler oft im zuletzt eingefügten Codeblock auftreten.

Hier ist ein kurzes Beispiel:

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

Beachten Sie diese Zeile: "z += other.y;". Der Programmierer hat vergessen, 'y' durch 'z' zu ersetzen.

Sie könnten denken, dies sei ein hypothetisches Beispiel, aber es stammt tatsächlich aus einer realen Anwendung. Als Nächstes werde ich Sie davon überzeugen, dass dies ein häufiger, hochfrequenter Fehler ist. Programmierer machen diesen Fehler oft am Ende einer Reihe ähnlicher Operationen.

Ich habe gehört, dass Felskletterer oft in den letzten paar Dutzend Metern abstürzen. Das liegt nicht daran, dass sie müde sind, sondern gerade daran, dass sie wegen des nahenden Endes übermäßig aufgeregt sind; sie stellen sich die Freude über den Erfolg vor, werden unvorsichtig und rutschen schließlich ab. Ich vermute, Programmierern geht es genauso.

Nun sehen wir uns eine Datenmenge an.

Nachdem ich die Datenbank untersucht hatte, habe ich 84 Codefragmente isoliert, die durch die Copy-Paste-Technik entstanden sind. In 41 davon trat der Fehler in einem mittleren eingefügten Block auf. Zum Beispiel:

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

Die Länge der Zeichenkette "THREADS=" beträgt 8 Zeichen, nicht 6.

In den anderen 43 Codefragmenten trat der Fehler im zuletzt eingefügten Block auf.

Natürlich ist 43 nicht viel größer als 41. Aber beachten Sie, dass ein Programm viele ähnliche Codeblöcke haben kann, sodass Fehler im ersten, zweiten, fünften oder sogar zehnten Block auftreten könnten. Daher haben wir in den anderen Blöcken eine relativ gleichmäßige Verteilung, während der letzte Block eine Spitze aufweist.

Im Durchschnitt beträgt die Gesamtzahl ähnlicher Codeblöcke 5.

Somit sind 41 Fehler gleichmäßig auf die ersten 4 Codeblöcke verteilt, mit durchschnittlich 10 Fehlern pro Block.

Der letzte Block enthält jedoch 43 Fehler!

Das folgende Verteilungsdiagramm hebt dieses Phänomen hervor:

copy-paste-1

Übersichtsdiagramm der Fehlerverteilung in fünf ähnlichen Codeabschnitten

Daher können wir eine Regel zusammenfassen:

Die Wahrscheinlichkeit, im zuletzt eingefügten Codeblock einen Fehler zu machen, ist 4-mal so hoch wie bei anderen Codeblöcken.

Diese Regel ist möglicherweise nicht allgemeingültig. Sie ist nur eine interessante Feststellung, und ihr praktischer Wert besteht darin, Sie daran zu erinnern, beim Schreiben des letzten Blocks wachsam zu bleiben.

Beispiel:

Jetzt werde ich beweisen, dass dies nicht nur meine wilde Fantasie ist, sondern ein echter Trend. Sehen Sie sich die folgenden Beispiele an.

Natürlich werde ich nicht alle Beispiele auflisten, nur einfache und repräsentative.

Source Engine SDK

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

Die letzte Zeile sollte SetW() sein.

Chromium

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;

Die letzten beiden Zeilen sind identisch.

ReactOS

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

Multi Theft Auto

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

Die redundante letzte Zeile stammt vom gewohnheitsmäßigen Einfügen. Die Größe des Arrays ist 3.

Source Engine 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));

Der Programmierer hat vergessen, "BackgroundColor.y" in der letzten Zeile zu "BackgroundColor.z" zu ändern.

Trans-Proteomic Pipeline

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 ) ... }
  ....
}

Der Programmierer hat vergessen, "prob > max6" in der letzten Bedingung zu "prob > max7" zu ändern.

SeqAn

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];
}

Die letzte Zeile sollte rglFSlider verwenden.

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
}

Im letzten Block fehlt 'patternRepeatX'. Der korrekte Code sollte lauten:

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' ist immer gleich 1. Die letzte Zeile sollte lauten:

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")) {          <<<---

Das letzte "ftp" ist verdächtig; es wurde bereits zuvor verglichen.

Quake-III-Arena

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

Der Programmierer hat vergessen, den Wert von dir zu prüfen.

Clang

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

Im letzten Block wird der Ausdruck "SM.getExpansionColumnNumber(ContainerREnd)" mit sich selbst verglichen.

MongoDB

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

Der Programmierer hat in der letzten Zeile das 'r' vergessen.

Unreal Engine 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;
}

In der letzten Zeile hat der Programmierer zwei Dinge vergessen. Erstens sollte ">=" in "<=" geändert werden, und zweitens sollte das Minuszeichen in ein Pluszeichen geändert werden.

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

Im letzten qlsFinite sollte das übergebene Argument 'h' sein.

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;

Die Zeichenkette "BITLIST" hat eine Länge von 7, nicht 3.

Lassen Sie uns hier aufhören. Die Beispiele, die ich gegeben habe, reichen aus, um den Punkt zu verdeutlichen, oder?

Fazit

Dieser Artikel zeigt Ihnen, dass die Copy-Paste-Technik im letzten eingefügten Codeblock wahrscheinlich eine 4-mal höhere Fehlerwahrscheinlichkeit aufweist als in anderen Blöcken.

Dies hängt mit menschlicher Psychologie zusammen, nicht mit technischem Können. Der Artikel zeigt, dass selbst erfahrene Programmierer in Projekten wie Clang oder Qt diese Art von Fehler machen.

Ich hoffe, dass diese Entdeckung des Phänomens für Programmierer hilfreich ist und sie vielleicht dazu ermutigt, unsere Fehlerdatenbank zu untersuchen. Ich glaube, dass dies helfen wird, neue Muster in diesen Fehlern zu finden und neue Programmierempfehlungen zusammenzufassen.

Dieser Artikel wurde reproduziert von: http://www.vaikan.com/the-last-line-effect/