The Last Line Effect – Tippfehler und Spaß

The Last Line Effect – Tippfehler und Spaß

Unser Team hat eine große Anzahl von Projekten geprüft – mehr als 250 – und etwa 9800 Fehler gesammelt. Daher haben wir viele Fehler untersucht, die durch die Verwendung der Copy-Paste-Methode verursacht werden, und können Ihnen versichern, dass Programmierer am häufigsten dazu neigen, Fehler im letzten Fragment eines homogenen Codeblocks zu machen. Wir haben dieses Phänomen noch nie in Programmierbüchern gesehen, also haben wir uns entschieden, darüber zu schreiben und es den „Letzte-Zeile-Effekt“ zu nennen.

 

Letzte Zeile Effekt

Beim Schreiben von Programmcode müssen Programmierer oft eine Reihe ähnlicher Konstrukte schreiben. Den gleichen Code mehrmals einzugeben ist langweilig und ineffizient. Deshalb verwenden sie die Copy-Paste-Methode:Ein Codefragment wird mehrmals kopiert und eingefügt, mit weiterer Bearbeitung. Jeder weiß, was an dieser Methode schlecht ist:Sie laufen Gefahr, leicht zu vergessen, etwas in den eingefügten Zeilen zu ändern, und so Fehler zu verursachen. Leider gibt es oft keine bessere Alternative.

Lassen Sie uns nun über das Muster sprechen, das wir entdeckt haben. Wir haben herausgefunden, dass Fehler am häufigsten im zuletzt eingefügten Codeblock gemacht werden.

Hier ist ein einfaches und kurzes Beispiel:

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

Beachten Sie die Zeile „z +=other.y;“. Der Programmierer hat vergessen, darin „y“ durch „z“ zu ersetzen.

Sie mögen denken, dass dies ein künstliches Beispiel ist, aber es stammt tatsächlich aus einer echten Anwendung. Weiter unten in diesem Artikel werden wir Sie davon überzeugen, dass dies ein sehr häufiges und häufiges Problem ist. So sieht der „Letzte-Zeile-Effekt“ aus. Programmierer machen am häufigsten Fehler ganz am Ende einer Folge ähnlicher Bearbeitungen.

Eine zufällige Tatsache:Bergsteiger stürzen oft auf den letzten paar Dutzend Metern ihres Aufstiegs. Nicht weil sie müde sind; Sie freuen sich einfach zu sehr darüber, dass sie fast den Gipfel erreicht haben – sie erwarten den süßen Geschmack des Sieges, werden unaufmerksamer und machen einen fatalen Fehler. Wir vermuten, dass Programmierern etwas Ähnliches passiert.

Nun einige Zahlen.

Nach dem Studium der Fehlerdatenbank haben wir 84 Codefragmente herausgegriffen, die unserer Meinung nach mit der Copy-Paste-Methode geschrieben wurden. Davon enthalten 41 Fragmente Fehler irgendwo in der Mitte der kopierten und eingefügten Blöcke. 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 Zeichenfolge „THREADS=“ beträgt 8 Zeichen, nicht 6.

In den anderen 43 Fällen wurden Fehler im zuletzt kopierten Codeblock gefunden.

Nun, die Zahl 43 sieht nur etwas größer aus als 41. Aber denken Sie daran, dass es ziemlich viele homogene Blöcke geben kann, sodass Fehler im ersten, zweiten, fünften oder sogar zehnten Block gefunden werden können. Wir erhalten also eine relativ gleichmäßige Verteilung der Fehler über die Blöcke hinweg und eine scharfe Spitze am Ende.

Wir haben angenommen, dass die Anzahl der homogenen Blöcke im Durchschnitt 5 beträgt.

Es scheint also, dass die ersten 4 Blöcke 41 Fehler enthalten, die über sie verteilt sind; das macht etwa 10 Fehler pro Block.

Und für den fünften Block sind noch 43 Fehler übrig!

Zur Verdeutlichung hier ein grobes Diagramm:

Abbildung 1. Ein grobes Diagramm der Fehlerverteilung in fünf homogenen Codeblöcken.

Was wir also erhalten, ist das folgende Muster:

Die Wahrscheinlichkeit, im zuletzt eingefügten Codeblock einen Fehler zu machen, ist viermal höher als in jedem anderen Block.

Wir ziehen daraus keine großen Schlüsse. Es ist nur eine interessante Beobachtung, die aus praktischen Gründen nützlich sein kann – Sie werden wachsam bleiben, wenn Sie die letzten Codefragmente schreiben.

Beispiele

Hier sind einige Beispiele, die das beweisen:

Wir werden natürlich nicht alle Beispiele nennen – nur die einfachsten oder repräsentativsten.

Quell-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 Funktion SetW() sollte am Ende aufgerufen werden.

Chrom

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;

Der letzte Block und der davor 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 letzte Zeile wurde mechanisch eingefügt und ist überflüssig. Es gibt nur 3 Elemente im Array.

Quell-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, im letzten Block „BackgroundColor.y“ durch „BackgroundColor.z“ zu ersetzen.

Transproteomische 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, in der letzten Bedingung „prob> max6“ durch „prob> max7“ zu ersetzen.

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

Das rglFSlider-Array hätte in der letzten Zeile verwendet werden sollen.

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“ fehlt im allerletzten Block. Der korrekte Code sieht wie folgt aus:

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

Die Variable „mjstride“ ist immer gleich eins. Die letzte Zeile hätte so geschrieben werden sollen:

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

Eine verdächtige Zeichenfolge „ftp“ am Ende – wurde bereits verglichen mit.

Quake-III-Arena

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

Der Wert aus der Zelle dir[2] bleibt unmarkiert.

Klang

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

Ganz am Ende des Blocks 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 „r“ vergessen. in der letzten Zeile.

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

Der Programmierer hat vergessen, 2 Änderungen in der letzten Zeile vorzunehmen. Erstens sollte „>=“ durch „<=“ ersetzt werden; Zweitens sollte Minus durch Plus ersetzt 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))

Beim allerletzten Aufruf der Funktion qIsFinite hätte die Variable ‚h‘ als Argument verwendet werden sollen.

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 Länge der Zeichenfolge „BITLIST“ beträgt 7, nicht 3 Zeichen.

Lassen Sie uns hier aufhören. Wir hoffen, dass die Beispiele, die wir demonstriert haben, mehr als genug sind.

Schlussfolgerung

Aus diesem Artikel haben Sie gelernt, dass es bei der Copy-Paste-Methode viermal wahrscheinlicher ist, einen Fehler im zuletzt eingefügten Codeblock zu machen als in jedem anderen Fragment. Dieser Fehler wurde mit dem statischen Analysetool von PVS-Studio gefunden.

Es hat mit den Besonderheiten der menschlichen Psychologie zu tun, nicht mit beruflichen Fähigkeiten. Wir haben Ihnen in diesem Artikel gezeigt, dass selbst hochqualifizierte Entwickler von Projekten wie Clang oder Qt dazu neigen, solche Fehler zu machen.

Wir hoffen, dass diese Beobachtungen für Programmierer nützlich sind, und fordern sie vielleicht auf, unsere Fehlerdatenbank zu untersuchen.

OK. Hier sind einige urkomische Tippfehler, die aus dem Netz gesammelt wurden. Zeit, sich zu entspannen und ein bisschen zu lächeln.