Null, eins, zwei, Freddy wird dich abholen

Bild 1

Hier ist eine Fortsetzung einer Reihe von Artikeln mit dem Titel "Horror für Programmierer". Dieses Mal werden wir über ein typisches Tippfehlermuster sprechen, das mit der Verwendung der Zahlen 0, 1, 2 verbunden ist. Es spielt keine Rolle, ob Sie in C, C ++, C # oder Java schreiben. Wenn Sie die Konstanten 0, 1, 2 verwenden oder wenn diese Zahlen in Variablennamen enthalten sind, wird Freddy Sie höchstwahrscheinlich nachts besuchen. Lesen Sie und sagen Sie später nicht, dass Sie nicht gewarnt wurden.


Einführung


Ich setze eine Reihe von Artikeln fort, die sich mit wahrgenommenen Mustern befassen, wie Menschen Fehler machen. Frühere Veröffentlichungen:
  1. Effekt der letzten Zeile
  2. Die gefährlichste Funktion in der Welt von C / C ++
  3. Das Böse lebt in Vergleichsfunktionen

Diesmal wurde das Muster nicht von mir, sondern von meinem Kollegen Svyatoslav Razmyslov bemerkt. Er machte darauf aufmerksam, dass er in seinen Artikeln ständig Probleme beschreibt, bei denen Variablen mit den Nummern 1 und 2 in seinem Namen vorkommen. Svyatoslav schlug vor, dass ich dieses Thema genauer studiere und es sich wirklich als sehr fruchtbar herausstellte. Es stellte sich heraus, dass unsere Fehlersammlung eine große Anzahl von Codefragmenten enthält, die falsch sind, weil die Indizes 0, 1, 2 oder die Namen von Variablen, die solche Zahlen enthalten, verwirrt sind. Eine neue interessante Regelmäßigkeit wird offenbart, die unten diskutiert wird. Ich bin Svyatoslav für die Aufforderung dankbar, dieses Thema zu untersuchen und ihm diesen Artikel zu widmen.

Abbildung 14

Swjatoslaw Razmyslov, Manager, aufmerksam Bug Catcher und nur eine begabte Person.

Was ist der Zweck dieses Artikels? Zeigen Sie, wie einfach wir alle Fehler und Tippfehler machen. Wenn Programmierer gewarnt werden, werden sie bei der Überprüfung des Codes aufmerksamer sein und sich auf die unglücklichen Werte 0, 1, 2 konzentrieren. Außerdem können Programmierer den Wert statischer Code-Analysatoren, die solche Fehler erkennen, besser spüren. Es geht nicht um PVS-Studio-Werbung (obwohl es auch ist :). Bisher halten viele Programmierer statische Analysen für überflüssig und konzentrieren sich lieber auf ihre eigene Genauigkeit und Codeüberprüfung. Leider ist der Versuch, Code fehlerfrei zu schreiben, gut, aber nicht ausreichend. Dieser Artikel wird dies noch einmal deutlich zeigen.

Niemand ist gegen solche Fehler immun. Unten sehen Sie epische Pannen in so bekannten Projekten wie Qt, Clang, Hive, LibreOffice, Linux-Kernel, .NET Compiler-Plattform, XNU-Kernel und Mozilla Firefox. Und dies sind keine exotischen, seltenen Fehler, sondern die häufigsten. Nicht überzeugt? Dann fangen wir an!

„Geschwätz ist wertlos! Zeig mir die Käfer! "

© modifiziertes Zitat von Linus Torvalds.

Tippfehler in Konstanten beim Indizieren von Arrays


In unseren Artikeln geben wir in der Regel Warnungen, mit deren Hilfe Fehler gefunden werden. Dieses Mal werde ich diese Warnungen weglassen, da der Fehler ohne sie leicht erkennbar und verständlich sein wird. Obwohl diese Fehler in einem kurzen Code sofort erkennbar sind, wissen sie, wie sie sich im Code von Projekten verstecken können.

Beginnen wir mit den Fehlern, wenn Verwirrung mit den numerischen Literalen besteht, die zum Indizieren von Arrays verwendet werden. Trotz der Banalität dieser Fehler gibt es viele davon und sie werden in der Laborarbeit der Studenten nicht offenbart.

XNU-Kernelprojekt, C-Sprache
uint32_t
gss_krb5_3des_unwrap_mbuf(....)
{
  ....
  for (cflag = 1; cflag >= 0; cflag--) {
    *minor = gss_krb5_3des_token_get(
       ctx, &itoken, wrap, &hash, &offset, &length, reverse);
    if (*minor == 0)
      break;
    wrap.Seal_Alg[0] = 0xff;
    wrap.Seal_Alg[0] = 0xff;
  }
  ....
}

Die Zeile wurde kopiert, aber vergessen, den Index zu korrigieren. So wie ich es verstehe, sollte es hier geschrieben werden:
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[1] = 0xff;

LibreOffice-Projekt, C ++
Sequence< OUString > FirebirdDriver::
  getSupportedServiceNames_Static() throw (RuntimeException)
{
  Sequence< OUString > aSNS( 2 );
  aSNS[0] = "com.sun.star.sdbc.Driver";
  aSNS[0] = "com.sun.star.sdbcx.Driver";
  return aSNS;
}

Wie im vorherigen Fall wurde die Zeile kopiert, aber vergessen, 0 durch 1 zu korrigieren. Nur das Zeichenfolgenliteral wurde korrigiert.

Man kann eine philosophische Frage stellen, wie man einen solchen Fehler in einer vierzeiligen Funktion machen kann. Alles ist möglich. Hier ist es, Programmierung.

Quake-III-Arena-Projekt, C-Sprache
int VL_FindAdjacentSurface(....)
{
  ....
  if (fabs(dir[0]) > test->radius ||
      fabs(dir[1]) > test->radius ||
      fabs(dir[1]) > test->radius)
  {
  ....
}

In der kopierten Zeile haben sie vergessen, dir [1] durch dir [2] zu ersetzen . Infolgedessen wird der Wert entlang der Z-Achse nicht gesteuert.

OpenCOLLADA-Projekt, C ++
struct short2
{
  short values[2];
  short2(short s1, short s2)
  {
    values[0] = s1;
    values[2] = s2;
  }
  ....
};

Ja, selbst in einem so kurzen Konstruktor können Sie es schaffen, die Grenze eines Arrays zu überschreiten, wenn es initialisiert wird.

Abbildung 8


Godot Engine-Projekt, C ++
Array PhysicsDirectSpaceState::_cast_motion(....)
{
  ....
  Array ret(true);
  ret.resize(2);
  ret[0]=closest_safe;
  ret[0]=closest_unsafe;
  return ret;
}

Kein Kommentar erforderlich.

Sternchenprojekt, C-Sprache
static void sip_threadinfo_destructor(void *obj)
{
  struct sip_threadinfo *th = obj;
  struct tcptls_packet *packet;

  if (th->alert_pipe[1] > -1) {            // <=
    close(th->alert_pipe[0]);
  }
  if (th->alert_pipe[1] > -1) {
    close(th->alert_pipe[1]);
  }
  th->alert_pipe[0] = th->alert_pipe[1] = -1;
  ....
}

Beim Schreiben des gleichen Blocktyps befindet sich der Fehler in der Regel im zugrunde liegenden. Zuvor waren alle betrachteten Fälle genau das. Hier befindet sich der Tippfehler an einer ungewöhnlichen Stelle, nämlich im ersten Block. Warum es passiert ist, ist schwer zu sagen. Ich habe keine andere Wahl, als ein Bild von einem Achselzucken mit den Schultern zu bringen:

Abbildung 9


Öffnen Sie das CASCADE-Technologieprojekt C ++
inline void Prepend(const Standard_Integer theIndex)
{
  if (myIndex[1] >= 0)
    Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex....");

  myIndex[1] = myIndex[0];
  myIndex[1] = theIndex;
}

Zweimal in derselben Zelle des Arrays werden unterschiedliche Werte kopiert. Ein offensichtlicher Fehler, aber wie man ihn behebt, war mir nicht klar, da mir der Projektcode nicht bekannt ist. Also habe ich mir nur angesehen, wie die Entwickler den Code korrigiert haben, nachdem unser Team sie auf diesen Fehler hingewiesen hat. Die richtige Option:
myIndex[1] = myIndex[0];
myIndex[0] = theIndex;

Transproteomisches Pipeline-Projekt, C ++
void ASAPRatio_getProDataStrct(proDataStrct *data,
                               char **pepBofFiles)
{
  ....
  if (data->indx == -1) {
    data->ratio[0] = -2.;
    data->ratio[0] = 0.;             // <=
    data->inv_ratio[0] = -2.;
    data->inv_ratio[1] = 0.;
    return;
  }
  ....
}

Ich mache mir Sorgen, dass Forschungspakete solche Fehler enthalten. Die Trans-Proteomic Pipeline wurde entwickelt, um Probleme auf dem Gebiet der Biologie zu lösen. Dies kann entschieden und "untersucht" werden. Dieses Paket enthält im Allgemeinen viele interessante Dinge: Check in 2012 , Check in 2013 . Vielleicht können Sie noch einmal versuchen, sich dieses Projekt anzusehen.

ITK-Projekt, C ++ - Sprache

Wir stehen vor einem weiteren Projekt zur Durchführung von Forschungsarbeiten auf dem Gebiet der Medizin: dem Medicine Insight Segmentation and Registration Toolkit (ITK). Das Projekt ist anders, aber die Fehler sind die gleichen.
template< typename TCoordRepType >
void
VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize)
{
  m_VoronoiBoundaryOrigin[0] = vorsize[0];
  m_VoronoiBoundaryOrigin[0] = vorsize[1];
}

ITK-Projekt, C ++


int itkPointSetToSpatialObjectDemonsRegistrationTest(....)
{
  ....
  // Set its position
  EllipseType::TransformType::OffsetType offset;
  offset[0]=50;
  offset[1]=50;
  offset[1]=50;
  ....
}

Clean Copy-Paste.

ReactOS Project, C ++
HPALETTE CardWindow::CreateCardPalette()
{
  ....
  //include button text colours
  cols[0] = RGB(0, 0, 0);
  cols[1] = RGB(255, 255, 255);

  //include the base background colour
  cols[1] = crBackgnd;

  //include the standard button colours...
  cols[3] = CardButton::GetHighlight(crBackgnd);
  cols[4] = CardButton::GetShadow(crBackgnd);
  cols[5] = CardButton::GetFace(crBackgnd);
  ....
}

Höchstwahrscheinlich sollte die crBackgnd- Konstante in die cols- Zelle geschrieben werden [2] .

Coin3D-Projekt, C ++
SoVRMLInline::GLRender(SoGLRenderAction * action)
{
  ....
  if ((size[0] >= 0.0f && size[1] >= 0.0f && size[1] >= 0.0f) &&
      ((vis == ALWAYS) ||
       (vis == UNTIL_LOADED && child == NULL))) {
  ....
}

Das Element des Arrays size [1] wird doppelt geprüft , und das Element der Größe [2] wird nicht geprüft. So erscheinen seltsame Artefakte auf Bildern.

OpenCV-Projekt, C ++
bool Jpeg2KDecoder::readHeader()
{
  ....
  cmptlut[0] = ....
  cmptlut[1] = ....
  cmptlut[2] = ....
  if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[0] < 0 )
    result = false;
  ....
}

Es ist direkt zu spüren, dass der Ausdruck cmptlut [0] <0 zweimal durch Kopieren dupliziert wurde, aber Null an nur einer Stelle korrigiert wurde.

Visualization Toolkit (VTK) -Projekt, C ++
void vtkImageStencilRaster::PrepareForNewData(....)
{
  ....
  if (allocateExtent &&
      allocateExtent[1] >= allocateExtent[1])
  ....
}

Im Folgenden werde ich nicht auf viele solcher Fehler eingehen. Was gibt es zu kommentieren? Die Hauptsache beim Betrachten solcher Codefragmente ist das Gefühl, dass der Fehler zwar einfach ist, dies jedoch nicht bedeutet, dass er vom Programmierer bemerkt wird.

Visualization Toolkit (VTK) -Projekt, C ++
template <class iterT>
void vtkDataSetAttributesCopyValues(....)
{
  ....
  inZPtr +=
    (outExt[0] - outExt[0])*inIncs[0] * data_type_size +
    (outExt[2] - outExt[2])*inIncs[1] * data_type_size +
    (outExt[4] - outExt[4])*inIncs[2] * data_type_size;
  ....
}

Hier hatte der Programmierer eindeutig die Eile, Code schneller zu schreiben. Es ist schwierig auf andere Weise zu erklären, wie er dreimal einen Fehler gemacht hat. Elemente des Arrays werden von sich selbst subtrahiert. Das Ergebnis ist, dass dieser Code äquivalent ist:
inZPtr +=
  (0)*inIncs[0] * data_type_size +
  (0)*inIncs[1] * data_type_size +
  (0)*inIncs[2] * data_type_size;

Dieser Code kann jedoch noch weiter reduziert werden:
inZPtr += 0;

Üppig. Der Code enthält einen langen, ernsthaft aussehenden Ausdruck, der tatsächlich nichts bewirkt. Ich liebe solche Fälle.

Visualization Toolkit (VTK) -Projekt, C ++ - Sprache

Ein ähnlicher Fall von hastigem Schreiben von Code.
void vtkPiecewiseControlPointsItem::SetControlPoint(
  vtkIdType index, double* newPos)
{
  double oldPos[4];
  this->PiecewiseFunction->GetNodeValue(index, oldPos);
  if (newPos[0] != oldPos[0] || newPos[1] != oldPos[1] ||
      newPos[2] != oldPos[2] || newPos[2] != oldPos[2])
    {
      this->PiecewiseFunction->SetNodeValue(index, newPos);
    }
}

Der Vergleich newPos [2]! = OldPos [2] wird zweimal wiederholt .

ACAP-Projekt (ADAPTIVE Communication Environment), C ++
bool URL_Base::strip_scheme (ACE_CString& url_string)
{
  ....
  ACE_CString::size_type pos = url_string.find (':');
  if (pos > 0 &&
      url_string[pos+1] == '/' &&
      url_string[pos+1] == '/')
  {
    ....
    // skip '<protocol>://'
    url_string = url_string.substr (pos+3);
  }
  ....
}

Die Bedingung muss sicherstellen, dass zwei Schrägstriche nach dem Doppelpunkt auftreten. Mit anderen Worten, der Teilstring ": //" wird durchsucht. Aufgrund eines Tippfehlers ist der Scheck "blind" und kann jedes Zeichen als zweiten Schrägstrich zählen.

IPP-Beispielprojekt, C ++
void MeBase::MakeVlcTableDecision()
{
  ....
  Ipp32s BestMV =
    IPP_MIN(IPP_MIN(m_cur.MvRate[0],m_cur.MvRate[1]),
                    IPP_MIN(m_cur.MvRate[2],m_cur.MvRate[3]));
  Ipp32s BestAC =
    IPP_MIN(IPP_MIN(m_cur.AcRate[0],m_cur.AcRate[1]),
                    IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2]));
  ....
}

Ein Tippfehler liegt hier in den an das Makro übergebenen Argumenten:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])

Es stellt sich heraus, dass mindestens zwei identische Werte ausgewählt sind. In der Tat sollte geschrieben werden:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3])

Dieser Code kann übrigens die Nützlichkeit der Standardbibliothek demonstrieren. Wenn Sie so schreiben:
Ipp32s BestMV = std::min_element(begin(m_cur.MvRate), end(m_cur.MvRate));
Ipp32s BestAC = std::min_element(begin(m_cur.AcRate), end(m_cur.AcRate));

Dieser Code wird kürzer und weniger fehleranfällig. Je weniger dieselbe Art von Code vorhanden ist, desto wahrscheinlicher ist es, dass er korrekt geschrieben wird.

Projekt Audacity, C ++
sampleCount VoiceKey::OnBackward (....) {
  ....
  int atrend = sgn(buffer[samplesleft - 2]-
                   buffer[samplesleft - 1]);
  int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                   buffer[samplesleft - WindowSizeInt-2]);
  ....
}

Der richtige Ausdruck lautet:
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                 buffer[samplesleft - WindowSizeInt-1]);

PDFium-Projekt, C ++ - Sprache
void sycc420_to_rgb(opj_image_t* img) {
  ....
  opj_image_data_free(img->comps[0].data);
  opj_image_data_free(img->comps[1].data);
  opj_image_data_free(img->comps[2].data);
  img->comps[0].data = d0;
  img->comps[1].data = d1;
  img->comps[2].data = d2;
  img->comps[1].w = yw;                 // 1
  img->comps[1].h = yh;                 // 1
  img->comps[2].w = yw;                 // 1
  img->comps[2].h = yh;                 // 1
  img->comps[1].w = yw;                 // 2
  img->comps[1].h = yh;                 // 2
  img->comps[2].w = yw;                 // 2
  img->comps[2].h = yh;                 // 2
  img->comps[1].dx = img->comps[0].dx;
  img->comps[2].dx = img->comps[0].dx;
  img->comps[1].dy = img->comps[0].dy;
  img->comps[2].dy = img->comps[0].dy;
}

Eine Reihe von Aktionen zum Initialisieren der Struktur wird dupliziert. Die mit Kommentar // 2 gekennzeichneten Zeilen können gelöscht werden, und es ändert sich nichts. Ich bezweifelte, ob ich diesen Code in den Artikel aufnehmen sollte. Dies ist kein Fehler, und nicht ganz mit den Indizes. Dieser zusätzliche Code erschien jedoch höchstwahrscheinlich genau deshalb, weil der Programmierer in all diesen Klassenmitgliedern und Indizes 1, 2 verwirrt war. Ich denke, dieser Code ist geeignet, um zu demonstrieren, wie leicht es ist, in Zahlen verwirrt zu werden.

CMake-Projekt, C

Der unten beschriebene Code wurde nicht von den CMake-Entwicklern geschrieben, sondern ausgeliehen. Gemessen am Kommentar am Anfang der Datei ist die Funktion utf8_encodeEs wurde 2007 von Tim Kientzle geschrieben. Seitdem ist diese Funktion von Projekt zu Projekt gewandert, und es gibt viele davon. Ich habe das Thema der Originalquelle nicht untersucht, da dies jetzt nicht der Punkt ist. Da sich dieser Code im CMake-Projekt befindet, gilt der Fehler für CMake.
static char *
utf8_encode(const wchar_t *wval)
{
  ....
  p[0] = 0xfc | ((wc >> 30) & 0x01);
  p[1] = 0x80 | ((wc >> 24) & 0x3f);
  p[1] = 0x80 | ((wc >> 18) & 0x3f);
  p[2] = 0x80 | ((wc >> 12) & 0x3f);
  p[3] = 0x80 | ((wc >> 6) & 0x3f);
  p[4] = 0x80 | (wc & 0x3f);
  p += 6;
  ....
}

Wie Sie sehen können, gibt es eine Art Verwechslung mit den Indizes. Zweimal gibt es einen Datensatz im Array-Element p [1] . Wenn Sie den Code in der Nachbarschaft studieren, wird klar, dass der richtige Code folgendermaßen aussehen sollte:
p[0] = 0xfc | ((wc >> 30) & 0x01);
p[1] = 0x80 | ((wc >> 24) & 0x3f);
p[2] = 0x80 | ((wc >> 18) & 0x3f);
p[3] = 0x80 | ((wc >> 12) & 0x3f);
p[4] = 0x80 | ((wc >> 6) & 0x3f);
p[5] = 0x80 | (wc & 0x3f);
p += 6;

Hinweis

Bitte beachten Sie, dass alle in diesem Kapitel beschriebenen Fehler mit C- oder C ++ - Code zusammenhängen. Kein C # oder Java Code!

Das ist sehr interessant, das habe ich nicht erwartet. Meiner Meinung nach hängen die berücksichtigten Tippfehler nicht von der Sprache ab. In den folgenden Kapiteln werden tatsächlich Codefehler in anderen Sprachen auftreten. Ich denke, das ist nur ein Zufall. PVS-Studio Analyzer wurde viel später gestartet, um C # / Java-Sprachen im Vergleich zu C / C ++ zu unterstützen, und wir haben es einfach nicht geschafft, die entsprechenden Fehlerbeispiele in der Datenbank zu akkumulieren.

Die Beobachtung ist jedoch immer noch interessant. Anscheinend verwenden C- und C ++ - Programmierer gerne die Nummern 0, 1 und 2 mehr, wenn sie mit Arrays arbeiten :).

Rechtschreibfehler in Namen


Dies wird der größte Abschnitt sein. Es ist sehr leicht für Leute, durch Namen wie a1 und a2 verwirrt zu werden . Es scheint, als könnten Sie hier verwirrt werden? Kann. Einfach. Und jetzt kann der Leser dies überprüfen.

Hive-Projekt, Java
@Override
public List<ServiceInstance> getAllInstancesOrdered() {
  List<ServiceInstance> list = new LinkedList<>();
  list.addAll(instances.values());
  Collections.sort(list, new Comparator<ServiceInstance>() {
    @Override
    public int compare(ServiceInstance o1, ServiceInstance o2) {
      return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity());
    }
  });
  return list;
}

Die Vergleichsfunktion compare verwendet zwei Objekte: o1 und o2 . Aufgrund eines Tippfehlers wird jedoch nur o2 weiter verwendet .

Interessanterweise wurde dieser Fehler dank Copy-Paste auf eine andere Funktion migriert:
@Override
public List<ServiceInstance> getAllInstancesOrdered() {
  List<ServiceInstance> list = new LinkedList<>();
  readLock.lock();
  try {
    list.addAll(instances.values());
  } finally {
    readLock.unlock();
  }
  Collections.sort(list, new Comparator<ServiceInstance>() {
    @Override
    public int compare(ServiceInstance o1, ServiceInstance o2) {
      return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity());
    }
  });
  return list;
}

Abbildung 10


Infer.NET-Projekt, C # -Sprache
private void MergeParallelTransitions()
{
  ....
  if (double.IsInfinity(transition1.Weight.Value) &&    
      double.IsInfinity(transition1.Weight.Value))
  ....
}

Doom 3-Projekt, C ++
uint AltOp::fixedLength()
{
  uint l1 = exp1->fixedLength();
  uint l2 = exp1->fixedLength();

  if (l1 != l2 || l1 == ~0u)
    return ~0;

  return l1;
}

Wenn jemand einen Tippfehler nicht sofort bemerkt hat, müssen Sie sich die Zeile ansehen, in der die Variable l2 initialisiert wird . Sollte verwenden exp2 .

Source Engine SDK-Projekt, C ++
void GetFPSColor( int nFps, unsigned char ucColor[3] )
{
  ....
  int nFPSThreshold1 = 20;
  int nFPSThreshold2 = 15;

  if (IsPC() &&
      g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 95)
  {
    nFPSThreshold1 = 60;
    nFPSThreshold1 = 50;
  }
  ....
}

Korrekt:
nFPSThreshold1 = 60;
nFPSThreshold2 = 50;

Linux-Kernel-Projekt, C-Sprache

Tippfehler können übrigens nicht nur in Variablennamen, sondern auch in Makronamen vorkommen. Nun wird es mehrere solcher Beispiele geben.
int private_ioctl(struct vnt_private *pDevice, struct ifreq *rq)
{
  ....
  if (sStartAPCmd.byBasicRate & BIT3) {
    pMgmt->abyIBSSSuppRates[2] |= BIT7;
    pMgmt->abyIBSSSuppRates[3] |= BIT7;
    pMgmt->abyIBSSSuppRates[4] |= BIT7;
    pMgmt->abyIBSSSuppRates[5] |= BIT7;
  } else if (sStartAPCmd.byBasicRate & BIT2) {
    pMgmt->abyIBSSSuppRates[2] |= BIT7;
    pMgmt->abyIBSSSuppRates[3] |= BIT7;
    pMgmt->abyIBSSSuppRates[4] |= BIT7;
  } else if (sStartAPCmd.byBasicRate & BIT1) {  // <=
    pMgmt->abyIBSSSuppRates[2] |= BIT7;
    pMgmt->abyIBSSSuppRates[3] |= BIT7;
  } else if (sStartAPCmd.byBasicRate & BIT1) {  // <=
    pMgmt->abyIBSSSuppRates[2] |= BIT7;
  } else {
    /* default 1,2M */
    pMgmt->abyIBSSSuppRates[2] |= BIT7;
    pMgmt->abyIBSSSuppRates[3] |= BIT7;
  }
  ....
}

Wie Sie sehen können, wird eine Maske namens BIT1 zweimal verwendet , was die zweite Prüfung sinnlos macht. Der Hauptteil der zweiten kommentierten bedingten Anweisung wird niemals ausgeführt.

CMaNGOS-Projekt, C ++
void AttackedBy(Unit* pAttacker) override
{
  ....
  DoScriptText(urand(0, 1) ?
               SAY_BELNISTRASZ_AGGRO_1 :
               SAY_BELNISTRASZ_AGGRO_1,
               m_creature, pAttacker);
  ....
}

Zufälliges Verhalten wurde im Spiel geplant, aber es wird immer dieselbe Konstante SAY_BELNISTRASZ_AGGRO_1 ausgewählt .

Vangers-Projekt: Eins für die Straße, C ++
const char* iGetJoyBtnNameText(int vkey,int lang)
{
  ....
  if (vkey >= VK_STICK_SWITCH_1 && vkey <= VK_STICK_SWITCH_9)
  {
     ret = (lang)
      ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
      : iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1];
    return ret;
  }
  ....
}

Nach dem daneben geschriebenen Code zu urteilen, sollte die richtige Option folgendermaßen aussehen:
ret = (lang)
  ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
  : iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1];

RT-Thread-Projekt, C-Sprache
uint8_t can_receive_message_length(uint32_t can_periph,
                                   uint8_t fifo_number)
{
  uint8_t val = 0U;

  if(CAN_FIFO0 == fifo_number){
    val = (uint8_t)(CAN_RFIFO0(can_periph) & CAN_RFIFO_RFL0_MASK);
  }else if(CAN_FIFO0 == fifo_number){
    val = (uint8_t)(CAN_RFIFO1(can_periph) & CAN_RFIFO_RFL0_MASK);
  }else{
    /* illegal parameter */
  }
  return val;
}

RT-Thread ist ein Open-Source-Echtzeitbetriebssystem für eingebettete Geräte. Hier sehen wir die Verwechslung zwischen FIFO 0 und FIFO 1. Und irgendwo wird jemand auf ein fehlerhaftes Gerät stoßen.

Abbildung 11


Der Fehler ist hier:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO0 == fifo_number){

Die zweite Prüfung ergibt immer false. Korrekt:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO1 == fifo_number){

Hive-Projekt, Java
private void
generateDateTimeArithmeticIntervalYearMonth(String[] tdesc) throws Exception {
  String operatorName = tdesc[1];
  String operatorSymbol = tdesc[2];
  String operandType1 = tdesc[3];
  String colOrScalar1 = tdesc[4];
  String operandType2 = tdesc[5];
  String colOrScalar2 = tdesc[6];
  ....
  if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column")) {
    ....
  } else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) {
    ....
}

Der PVS-Studio-Analysator zeigt sofort 2 Fehler an:
  1. Eine in colOrScalar1 gespeicherte Zeichenfolge kann nicht sowohl Col- als auch Column-Zeichenfolgen entsprechen.
  2. Die in colOrScalar1 gespeicherte Zeichenfolge kann nicht gleichzeitig den Zeichenfolgen Col und Scalar entsprechen.

Es gibt eindeutig Verwirrung über Variablennamen.

Shareaza-Projekt, C ++ - Sprache
void CDownloadWithSources::MergeMetadata(const CXMLElement* pXML)
{
  CQuickLock pLock( Transfers.m_pSection );

  CXMLAttribute* pAttr1 =
    m_pXML->GetAttribute(CXMLAttribute::schemaName);
  CXMLAttribute* pAttr2 =
    pXML->GetAttribute(CXMLAttribute::schemaName);

  if (pAttr1 && pAttr2 &&
      !pAttr1->GetValue().CompareNoCase(pAttr1->GetValue()))
    ....
}

Korrekt:
pAttr1->GetValue().CompareNoCase(pAttr2->GetValue())

Hinweis

Machen wir eine kurze Pause. Es besteht die Befürchtung, dass wir beim Blick durch einen Berg banaler Fehler vergessen werden, warum wir dies tun.

Die Aufgabe besteht nicht darin, über den Code eines anderen zu lachen. All dies ist kein Grund, mit dem Finger zu stechen und zu sagen: "Ha ha, nun, du musst." Dieser Grund zum Nachdenken!

Die Veröffentlichungen unseres Teams sollen zeigen, dass keiner von uns vor Fehlern gefeit ist. Die im Artikel beschriebenen Fehler erscheinen viel häufiger im Code als erwartet. Es ist auch wichtig, dass die Wahrscheinlichkeit, in 0, 1, 2 verloren zu gehen, fast nicht von den Qualifikationen des Programmierers abhängt.

Es ist nützlich zu erkennen, dass Menschen dazu neigen, Fehler zu machen. Ohne dies können Sie nicht den nächsten Schritt zur Verbesserung der Qualität und Zuverlässigkeit des Codes unternehmen. Da die Menschen verstehen, dass wir uns alle irren, versuchen sie frühzeitig, Fehler mithilfe von Codierungsstandards, Codeüberprüfungen, Komponententests, statischen und dynamischen Analysatoren zu identifizieren. Es ist sehr gut.

Warum sind die Dinge so verständlich geschrieben? Leider müssen wir bei der Kommunikation mit einer großen Anzahl von Entwicklern feststellen, dass dies nicht immer für alle so klar ist. Viele haben ein zu hohes Selbstwertgefühl und lassen einfach nicht zu, dass sie in der Lage sind, einfache Fehler zu machen. Es ist traurig.

Wenn Sie ein Teamleiter / Manager sind, lade ich Sie ein, sich gleichzeitig mit dieser Notiz vertraut zu machen .

Qt-Projekt, C ++
AtomicComparator::ComparisonResult
IntegerComparator::compare(const Item &o1,
                           const AtomicComparator::Operator,
                           const Item &o2) const
{
  const Numeric *const num1 = o1.as<Numeric>();
  const Numeric *const num2 = o1.as<Numeric>();

  if(num1->isSigned() || num2->isSigned())
  ....
}

Korrekt:
const Numeric *const num2 = o2.as<Numeric>();

Android-Projekt, C ++ - Sprache
static inline bool isAudioPlaybackRateEqual(
  const AudioPlaybackRate &pr1,
  const AudioPlaybackRate &pr2)
{
    return fabs(pr1.mSpeed - pr2.mSpeed) <
             AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
           fabs(pr1.mPitch - pr2.mPitch) <
             AUDIO_TIMESTRETCH_PITCH_MIN_DELTA &&
           pr2.mStretchMode == pr2.mStretchMode &&
           pr2.mFallbackMode == pr2.mFallbackMode;
}

Zwei Tippfehler gleichzeitig, aufgrund derer die Variablen pr2.mStretchMode und pr2.mFallbackMode mit sich selbst verglichen werden.

Boost-Projekt, C ++
point3D operator/(const point3D &p1, const point3D &p2)
{
  return point3D(p1.x/p2.x, p1.y/p2.y, p1.z/p1.z);
}

Ganz am Ende haben sie die Variable p1.z selbst versiegelt und geteilt .

Clang-Projekt, C ++
bool haveSameType(QualType Ty1, QualType Ty2) {
  return (Context.getCanonicalType(Ty1) ==
          Context.getCanonicalType(Ty2) ||
          (Ty2->isIntegerType() &&
           Ty2->isIntegerType()));
}

Ja, ja, PVS-Studio Analyzer findet ähnliche Fehler in Compilern. Korrekt:
(Ty1->isIntegerType() &&
 Ty2->isIntegerType())

Clang-Projekt, C ++
Instruction *InstCombiner::visitXor(BinaryOperator &I) {
  ....
  if (Op0I && Op1I && Op0I->isShift() &&
      Op0I->getOpcode() == Op1I->getOpcode() &&
      Op0I->getOperand(1) == Op1I->getOperand(1) &&
      (Op1I->hasOneUse() || Op1I->hasOneUse())) {
  ....
}

Korrekt:
(Op0I->hasOneUse() || Op1I->hasOneUse())

Qt-Projekt, C ++
inline bool qCompare(QImage const &t1, QImage const &t2, ....)
{
  ....
  if (t1.width() != t2.width() || t2.height() != t2.height()) {
  ....
}

NCBI Genome Workbench-Projekt, C ++
static bool s_PCRPrimerSetLess(const CPCRPrimerSet& s1, const CPCRPrimerSet& s2)
{
  if (!s1.IsSet() && s1.IsSet()) {
    return true;
  } else if (s1.IsSet() && !s2.IsSet()) {
    return false;
  } else if (!s1.IsSet() && !s2.IsSet()) {
    return false;
  } else if (s1.Get().size() < s2.Get().size()) {
    return true;
  } else if (s1.Get().size() > s2.Get().size()) {
    return false;
  } else {
  .....
}

Fehler bei der allerersten Prüfung. Es sollte geschrieben werden:
if (!s1.IsSet() && s2.IsSet()) {

NCBI Genome Workbench-Projekt, C ++
CRef<CSeq_align> CNWAligner::Run(CScope &scope, const CSeq_loc &loc1,
                                 const CSeq_loc &loc2, bool trim_end_gaps)
{
  if ((!loc1.IsInt() && !loc1.IsWhole()) ||
      (!loc1.IsInt() && !loc1.IsWhole()))
  {
    NCBI_THROW(CException, eUnknown,
               "Only whole and interval locations supported");
  }
  ....
}

Die erste Zeile der Bedingung wurde weitergegeben, aber dann beeilte sich der Programmierer und vergaß, loc1 durch loc2 zu ersetzen .

FlashDevelop-Projekt, C #
public void SetPrices(....)
{
  UInt32 a0 = _choice.GetPrice0();
  UInt32 a1 = _choice.GetPrice1();
  UInt32 b0 = a1 + _choice2.GetPrice0();   // <=
  UInt32 b1 = a1 + _choice2.GetPrice1();
  ....
}

FreeCAD-Projekt, C ++
inline void insEdgeVec(std::map<int,std::set<int> > &map,
                       int n1, int n2)
{
  if(n1<n2)
    map[n2].insert(n1);
  else
    map[n2].insert(n1);
};

Unabhängig von der Bedingung wird dieselbe Aktion ausgeführt. Es scheint so ein einfacher Fall zu sein. Wie können Sie eine Zeile kopieren und nicht reparieren? Kann.

LibreOffice-Projekt, C ++
class SVX_DLLPUBLIC SdrMarkView : public SdrSnapView
{
  ....
  const Point& GetRef1() const { return maRef1; }
  const Point& GetRef2() const { return maRef1; }
  ....
};

Klassischer Fehler beim Kopieren und Einfügen. Korrekt:
const Point& GetRef2() const { return maRef2; }

LibreOffice-Projekt, C ++
bool CmpAttr(
  const SfxPoolItem& rItem1, const SfxPoolItem& rItem2)
{
  ....
  ::boost::optional<sal_uInt16> oNumOffset1 =
        static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset();
  ::boost::optional<sal_uInt16> oNumOffset2 =
        static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset();
  ....
}

Und noch ein klassischer Copy-Paste-Fehler :). An einer Stelle korrigierten 1 bis 2 und an einer anderen vergaßen sie.

LibreOffice-Projekt, C ++
XMLTransformerOOoEventMap_Impl::XMLTransformerOOoEventMap_Impl(
        XMLTransformerEventMapEntry *pInit,
        XMLTransformerEventMapEntry *pInit2 )
{
  if( pInit )
    AddMap( pInit );
  if( pInit )
    AddMap( pInit2 );
}

Es ist kein Fehler, 1 durch 2 zu ersetzen, aber es wurde einfach keine 2 zur zweiten Bedingung hinzugefügt.

Abbildung 12


Sie können ein wenig müde sein. Daher schlage ich vor, Tee oder Kaffee zuzubereiten, und wir werden unsere Bekanntschaft mit der Welt der Nummern 0, 1 und 2 fortsetzen.

Geant4-Softwareprojekt, C ++ - Sprache
void G4VTwistSurface::GetBoundaryLimit(G4int areacode,
                                       G4double limit[]) const
{
  ....
  if (areacode & sC0Min1Max) {
     limit[0] = fAxisMin[0];
     limit[1] = fAxisMin[1];
  } else if (areacode & sC0Max1Min) {
     limit[0] = fAxisMax[0];
     limit[1] = fAxisMin[1];
  } else if (areacode & sC0Max1Max) {
     limit[0] = fAxisMax[0];
     limit[1] = fAxisMax[1];
  } else if (areacode & sC0Min1Max) {
     limit[0] = fAxisMin[0];
     limit[1] = fAxisMax[1];
  }
  ....
}

Ich hoffe, Sie haben den Rat befolgt und sich ausgeruht. Bereit, einen Fehler in diesem Code zu finden?

Herzlichen Glückwunsch an die Leser, die einen Fehler bemerkt haben. Du bist großartig!

Diejenigen, die zu faul sind, um zu suchen, verstehe ich auch. Die Überprüfung eines solchen Codes ist sehr mühsam und es besteht der Wunsch, schnell etwas Interessanteres zu überprüfen. Hier helfen statische Analysegeräte sehr, weil sie nicht müde werden.

Der Fehler ist, dass diese beiden Überprüfungen gleich sind:
if        (areacode & sC0Min1Max) {
} else if (areacode & sC0Min1Max) {

Wenn Sie den Code studieren, wird klar, dass die allererste Überprüfung fehlerhaft ist. Korrekt:
if        (areacode & sC0Min1Min) {
} else if (areacode & sC0Max1Min) {
} else if (areacode & sC0Max1Max) {
} else if (areacode & sC0Min1Max) {

CryEngine V-Projekt, C ++
bool
CompareRotation(const Quat& q1, const Quat& q2, float epsilon)
{
  return (fabs_tpl(q1.v.x - q2.v.x) <= epsilon)
      && (fabs_tpl(q1.v.y - q2.v.y) <= epsilon)
      && (fabs_tpl(q2.v.z - q2.v.z) <= epsilon) // <=
      && (fabs_tpl(q1.w - q2.w) <= epsilon);
}

TortoiseGit-Projekt, C ++
void CGitStatusListCtrl::OnContextMenuList(....)
{
  ....
  if( (!this->m_Rev1.IsEmpty()) ||
      (!this->m_Rev1.IsEmpty()) )
  ....
}

Geant4-Softwareprojekt, C ++ - Sprache
G4double G4MesonAbsorption::
GetTimeToAbsorption(const G4KineticTrack& trk1,
                    const G4KineticTrack& trk2)
{
  ....
  if(( trk1.GetDefinition() == G4Neutron::Neutron() ||
       trk1.GetDefinition() == G4Neutron::Neutron() ) &&
       sqrtS>1.91*GeV && pi*distance>maxChargedCrossSection)
    return time;
  ....
}

MonoDevelop-Projekt, C #
private bool MembersMatch(ISymbol member1, ISymbol member2)
{
  ....
  if (member1.DeclaredAccessibility !=
      member1.DeclaredAccessibility
   || member1.IsStatic != member1.IsStatic)
  {
    return false;
  }
  ....
}

Wie Sie sehen können, gehen die Codefragmente ohne Erklärung. Eigentlich gibt es hier nichts zu erklären. Du kannst nur seufzen.

Dolphin Emulator Project, C ++
bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const
{
  return (~ComputeKnownZeroBits(Op1) &
          ~ComputeKnownZeroBits(Op1)) == 0;
}

RunAsAdmin Explorer Shim-Projekt, C ++
bool IsLuidsEqual(LUID luid1, LUID luid2)
{
  return (luid1.LowPart == luid2.LowPart) &&
         (luid2.HighPart == luid2.HighPart);
}

IT ++ - Projekt, C ++ - Sprache
Gold::Gold(const ivec &mseq1_connections,
           const ivec &mseq2_connections)
{
  ....
  it_assert(mseq1.get_length() == mseq1.get_length(),
            "Gold::Gold(): dimension mismatch");
}

QuantLib-Projekt, C ++
Distribution ManipulateDistribution::convolve(
  const Distribution& d1, const Distribution& d2) {
  ....
  QL_REQUIRE (d1.xmin_ == 0.0 && d1.xmin_ == 0.0,
              "distributions offset larger than 0");
  ....
}

Samba-Projekt, C ++
static bool samu_correct(struct samu *s1, struct samu *s2)
{
  ....
  } else if (s1_len != s1_len) {
    DEBUG(0, ("Password history not written correctly, "
              "lengths differ, want %d, got %d\n",
          s1_len, s2_len));
  ....
}

Mozilla Firefox-Projekt, C ++
static PRBool IsZPositionLEQ(nsDisplayItem* aItem1,
                             nsDisplayItem* aItem2,
                             void* aClosure) {
  if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
      !aItem1->GetUnderlyingFrame()->Preserves3D()) {
    return IsContentLEQ(aItem1, aItem2, aClosure);
  }
  ....
}

Haiku-Betriebssystemprojekt, C ++
void trans_double_path::reset()
{
  m_src_vertices1.remove_all();
  m_src_vertices2.remove_all();
  m_kindex1 = 0.0;               // <=
  m_kindex1 = 0.0;               // <=
  m_status1 = initial;
  m_status2 = initial;
}

Das Qt-Projekt C ++

Ok, jetzt wollen wir etwas komplizierter werden. Versuchen Sie zum Spaß, den Fehler hier zu finden:
static ShiftResult shift(....)
{
  ....
  qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) +
            (orig->y1 - orig->y2)*(orig->y1 - orig->y1) *
            (orig->x3 - orig->x4)*(orig->x3 - orig->x4) +
            (orig->y3 - orig->y4)*(orig->y3 - orig->y4);
  ....
}

Ein Bild, um die Antwort nicht sofort zu sehen und die Gelegenheit zum Nachdenken zu haben.

Abbildung 13


Das ist richtig, anstelle von orig-> y1 - orig-> y1 sollte orig-> y1 - orig-> y2 geschrieben werden .

.NET Compiler Platform-Projekt, C # -Sprache
public void IndexerMemberRace()
{
  ....
  for (int i = 0; i < 20; i++)
  {
    ....
    if (i % 2 == 0)
    {
      thread1.Start();
      thread2.Start();
    }
    else
    {
      thread1.Start();
      thread2.Start();
    }
    ....
  }
  ....
}

Interessanter Fall. Zu Testzwecken müssen Sie Threads in einer anderen Reihenfolge ausführen. Aufgrund eines Tippfehlers beginnen die Threads jedoch immer auf die gleiche Weise, wodurch der Test weniger prüft, als er sollte.

Korrekt:
if (i % 2 == 0)
{
  thread1.Start();
  thread2.Start();
}
else
{
  thread2.Start();
  thread1.Start();
}

Samba-Projekt, C-Sprache
static int compare_procids(const void *p1, const void *p2)
{
  const struct server_id *i1 = (struct server_id *)p1;
  const struct server_id *i2 = (struct server_id *)p2;

  if (i1->pid < i2->pid) return -1;
  if (i2->pid > i2->pid) return 1;
  return 0;
}

Die Vergleichsfunktion gibt niemals 1 zurück, da die Bedingung i2-> pid> i2-> pid keinen Sinn ergibt.

Natürlich ist dies ein gewöhnlicher Tippfehler, und tatsächlich sollte er geschrieben werden:
if (i1->pid > i2->pid) return 1;

ChakraCore-Projekt, C ++

Der letzte Fall in diesem Kapitel. Hurra!
bool Lowerer::GenerateFastBrSrEq(....,
                                 IR::RegOpnd * srcReg1,
                                 IR::RegOpnd * srcReg2,
                                 ....)
{
  ....
  else if (srcReg1 && (srcReg1->m_sym->m_isStrConst))
  ....
  else if (srcReg1 && (srcReg1->m_sym->m_isStrConst))
  ....
}


Andere Fehler


Lassen Sie uns nun über weniger zahlreiche Fehlermuster sprechen, die mit der Verwendung der Zahlen 0, 1, 2 verbunden sind.

Tippfehler unter Bedingungen, bei denen die Konstante 0/1/2 explizit verwendet wird


ROOT-Projekt, C ++
Int_t TProofMonSenderML::SendSummary(TList *recs, const char *id)
{
  ....
  if (fSummaryVrs == 0) {
    if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn);
  } else if (fSummaryVrs == 0) {
  ....
}

Es ist seltsam, fSummaryVrs zweimal mit 0 zu vergleichen.

.NET CoreCLR-Projekt, C #
void PutIA64Imm22(UINT64 * pBundle, UINT32 slot, INT32 imm22)
{
  if (slot == 0)             // <=
  {
    ....
  }
  else if (slot == 1)
  {
    ....
  }
  else if (slot == 0)        // <=
  {
    .... 
  }
  ....
}

FFmpeg-Projekt, C-Sprache
static int imc_decode_block(....)
{
  ....
  if (stream_format_code & 0x1)
    imc_decode_level_coefficients_raw(....);
  else if (stream_format_code & 0x1)
    imc_read_level_coeffs_raw(....);
  ....
}


Postleitzahl / Name


Früher haben wir Fälle betrachtet, in denen der Index oder Name falsch ist. Und hier ist eine Situation, in der Sie nicht sofort wissen, wie der Fehler klassifiziert werden soll. Dieses Beispiel könnte sowohl dem einen als auch dem anderen Kapitel zugeordnet werden. Deshalb habe ich beschlossen, es separat zu bringen.

Mesa 3D-Grafikbibliotheksprojekt, C ++
bool
ir_algebraic_visitor::reassociate_constant(....)
{
  ....
  if (ir1->operands[0]->type->is_matrix() ||
      ir1->operands[0]->type->is_matrix() ||
      ir2->operands[1]->type->is_matrix() ||
      ir2->operands[1]->type->is_matrix())
   return false;
  ....
}

Dieser Code kann folgendermaßen behoben werden:
if (ir1->operands[0]->type->is_matrix() ||
    ir1->operands[1]->type->is_matrix() ||
    ir2->operands[0]->type->is_matrix() ||
    ir2->operands[1]->type->is_matrix())

Und Sie können es so beheben:
if (ir1->operands[0]->type->is_matrix() ||
    ir2->operands[0]->type->is_matrix() ||
    ir1->operands[1]->type->is_matrix() ||
    ir2->operands[1]->type->is_matrix())


Extra 0


Manchmal ist 0 redundant und schädlich. Aus diesem Grund kann die Zahl in ein Oktal umgewandelt werden, wenn sie nicht benötigt wird. Oder verderben Sie die Formatzeichenfolge.

Die genannten Fehler sind für diesen Artikel nicht geeignet, aber ich denke, dass es wert ist, sie zu erwähnen. Ich werde im Artikel keinen Code mit diesen Fehlern angeben, aber wenn Sie interessiert sind, können Sie sie hier ansehen:
  • V536 Beachten Sie, dass der verwendete konstante Wert durch eine Oktalform dargestellt wird, Beispiele ;
  • V638 In einer Zeichenfolge ist ein Terminal null vorhanden. Die Zeichen '\ 0xNN' wurden gefunden. Vermutlich gemeint: '\ xNN', Beispiele .


Ich habe vergessen, +1 zu schreiben


Haiku-Betriebssystemprojekt, C ++
int
UserlandFS::KernelEmu::new_path(const char *path, char **copy)
{
  ....
  // append a dot, if desired
  if (appendDot) {
    copiedPath[len] = '.';
    copiedPath[len] = '\0';
  }
  ....
}

Die richtige Option:
copiedPath[len] = '.';
copiedPath[len + 1] = '\0';

Hinweis. Die Situation, in der sie vergessen haben, eine Einheit hinzuzufügen, ist überhaupt nicht selten. Ich erinnere mich genau daran, dass ich solche Fälle mehr als einmal getroffen habe. Als ich jedoch ähnliche Beispiele für den Artikel eingeben wollte, fand ich nur dieses Beispiel. Es tut mir leid, dass ich Sie mit diesen Fehlern nicht mehr erschrecken kann. Ich bitte um Entschuldigung.

Formatierungsfehler (C #)


Am häufigsten arbeiten Funktionen zum Erstellen von Zeichenfolgen mit einer kleinen Anzahl von Argumenten. Es stellt sich also heraus, dass Fehler meistens mit der Verwendung von {0}, {1} oder {2} verbunden sind.

Azure PowerShell-Projekt, C #
protected override void ProcessRecordInternal()
{
  ....
  if (this.ShouldProcess(this.Name,
    string.Format("Creating Log Alert Rule '{0}' in resource group {0}",
      this.Name, this.ResourceGroupName)))
  {
    ....
  }
  ....
}

Versiegelt und zweimal {0} geschrieben. Infolgedessen wird der Name this.Name zweimal in die Zeichenfolge eingefügt . Der Name this.ResourceGroupName wird jedoch nicht in die erstellte Zeichenfolge übernommen.

Mono-Projekt, C #
void ReadEntropy ()
{
  if (reader.IsEmptyElement)
    throw new XmlException (
      String.Format ("WS-Trust Entropy element is empty.{2}",
                      LineInfo ()));
  ....
}

Das ist im Allgemeinen seltsam. Sie müssen einfügen, was nicht ist. Höchstwahrscheinlich wurde dieser Code nicht erfolgreich überarbeitet und als fehlerhaft herausgestellt.

Xenko-Projekt, C #
public string ToString(string format,
                                IFormatProvider formatProvider)
{
  if (format == null)
    return ToString(formatProvider);

  return string.Format(
                      formatProvider,
                      "Red:{1} Green:{2} Blue:{3}",
                      R.ToString(format, formatProvider),
                      G.ToString(format, formatProvider),
                      B.ToString(format, formatProvider));
}

Der Programmierer hat vergessen, dass die Nummerierung mit {0} beginnt, nicht mit {1}. Der richtige Code lautet:
return string.Format(
                    formatProvider,
                    "Red:{0} Green:{1} Blue:{2}",
                    R.ToString(format, formatProvider),
                    G.ToString(format, formatProvider),
                    B.ToString(format, formatProvider));

.NET Compiler Platform-Projekt, C #
private void DumpAttributes(Symbol s)
{
  ....
  Console.WriteLine("{0} {1} {2}", pa.ToString());
  ....
}

Argumente sind eindeutig nicht genug.

Schlussfolgerungen und Empfehlungen


Ich musste so viele Beispiele demonstrieren, um zu zeigen, dass Tippfehler in Bezug auf 0, 1 und 2 besondere Aufmerksamkeit verdienen.

Wenn ich einfach sagen würde: „Es ist leicht, o1 und o2 zu verwechseln“, würden Sie zustimmen, aber nicht die Bedeutung hinzufügen, die Sie jetzt durch Lesen oder zumindest Scrollen des Artikels hinzufügen.

Jetzt werden Sie gewarnt, und das ist gut so. Vorgewarnt ist gewappnet. Jetzt werden Sie den Codeüberprüfungen mehr Aufmerksamkeit schenken und den Variablen besondere Aufmerksamkeit schenken, in deren Namen Sie 0, 1, 2 sehen. Es ist

schwierig, Empfehlungen zum Code-Design abzugeben, um solche Fehler zu vermeiden. Wie Sie gesehen haben, werden Fehler auch in solch einfachem Code gefunden, in dem nichts zu tun ist.

Daher werde ich Sie nicht dringend bitten, 0, 1, 2 zu vermeiden und Variablen lange Namen zu geben. Wenn Sie anstelle von Zahlen anfangen, First / Second / Left / Right usw. zu schreiben, ist die Versuchung, den Namen oder Ausdruck zu kopieren, noch größer. Vielleicht wird eine solche Empfehlung letztendlich die Anzahl der Fehler nicht verringern, sondern erhöhen.

Wenn Sie jedoch viel von der gleichen Art von Code schreiben, ist die Empfehlung des "tabellarischen Code-Designs" immer noch relevant. Die tabellarische Formatierung garantiert nicht das Fehlen von Tippfehlern, macht sie jedoch einfacher und schneller zu erkennen. Siehe Kapitel N13 im Minibuch " Die Hauptfrage des Programmierens, Refactorings und all das ".

Es gibt noch eine gute Nachricht. Alle in diesem Artikel beschriebenen Fehler wurden mit dem statischen Code-Analysator PVS-Studio gefunden .. Dementsprechend können Sie durch die Einführung statischer Analysewerkzeuge in den Entwicklungsprozess viele Tippfehler frühzeitig identifizieren.

Vielen Dank für Ihre Aufmerksamkeit. Ich hoffe du warst interessiert und verängstigt. Ich wünsche Ihnen einen zuverlässigen Code und weniger Fehler mit 0, 1, 2, damit Freddy nicht zu Ihnen kommt.



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov. Null, eins, zwei, Freddy kommt für dich .

All Articles