Zéro, un, deux, Freddy viendra te chercher

Image 1

Voici une suite d'une série d'articles qui peuvent être intitulés "horreurs pour les programmeurs". Cette fois, nous parlerons d'un modèle de faute de frappe typique associé à l'utilisation des nombres 0, 1, 2. Peu importe si vous écrivez en C, C ++, C # ou Java. Si vous utilisez des constantes 0, 1, 2 ou si ces nombres sont contenus dans des noms de variables, alors, très probablement, Freddy vous rendra visite la nuit. Lisez et ne dites pas plus tard que vous n'avez pas été prévenu.


introduction


Je continue une série d'articles consacrés aux modèles remarqués dans la façon dont les gens font des erreurs. Publications précédentes:
  1. Effet de dernière ligne
  2. La fonction la plus dangereuse du monde du C / C ++
  3. Le mal vit dans les fonctions de comparaison

Cette fois, le motif n'a pas été remarqué par moi, mais par mon collègue Svyatoslav Razmyslov. Il a noté qu'il décrivait constamment dans ses articles des problèmes dans lesquels des variables contenant les nombres 1 et 2. Svyatoslav a suggéré que j'étudie ce sujet plus en détail et il s'est avéré vraiment très fructueux. Il s'est avéré que notre collection d'erreurs contient un grand nombre de fragments de code qui sont incorrects du fait que les gens se confondent dans les indices 0, 1, 2 ou dans les noms des variables contenant de tels nombres. Une nouvelle régularité intéressante se révèle, qui sera discutée ci-dessous. Je suis reconnaissant à Sviatoslav de m'avoir invité à enquêter sur ce sujet et à lui dédier cet article.

Figure 14

Svyatoslav Razmyslov, manager, attrapeur de bogues attentif et juste une personne talentueuse.

Quel est le but de cet article? Montrez à quel point nous commettons facilement des erreurs et des fautes de frappe. Si les programmeurs sont avertis, ils seront plus attentifs dans le processus de révision du code, en se concentrant sur le 0, 1, 2 malheureux. De plus, les programmeurs seront mieux en mesure de ressentir la valeur des analyseurs de code statiques qui aident à remarquer de telles erreurs. Il ne s'agit pas de publicité PVS-Studio (bien que ce soit aussi :). Jusqu'à présent, de nombreux programmeurs trouvent l'analyse statique superflue, préférant se concentrer sur leurs propres révisions de précision et de code. Malheureusement, essayer d'écrire du code sans erreur est bon, mais pas suffisant. Cet article le démontrera encore une fois clairement.

Personne n'est à l'abri de telles erreurs. Ci-dessous, vous verrez des bêtises épiques dans des projets aussi connus que Qt, Clang, Hive, LibreOffice, Linux Kernel, .NET Compiler Platform, XNU kernel, Mozilla Firefox. Et ce ne sont pas des erreurs rares exotiques, mais les plus fréquentes. Pas convaincu? Alors commençons!

«Chatter ne vaut rien! Montrez-moi les bugs! ”

© citation modifiée par Linus Torvalds.

Typos dans les constantes lors de l'indexation des tableaux


En règle générale, dans nos articles, nous donnons des avertissements à l'aide desquels des erreurs sont trouvées. Cette fois, j'oublierai ces avertissements, car sans eux, l'erreur sera facilement perceptible et compréhensible. Cependant, bien que ces erreurs soient immédiatement évidentes dans un court morceau de code, elles savent se cacher dans le code des projets.

Commençons par les erreurs en cas de confusion avec les littéraux numériques utilisés pour indexer les tableaux. Malgré la banalité de ces erreurs, elles sont nombreuses et ne sont pas révélées dans le travail de laboratoire des étudiants.

Projet de noyau XNU, langage C
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;
  }
  ....
}

La ligne a été copiée, mais a oublié de corriger l'index. Si je comprends bien, il devrait être écrit ici:
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[1] = 0xff;

Projet LibreOffice, 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;
}

Comme dans le cas précédent, la ligne a été copiée, mais a oublié de corriger 0 par 1. Seul le littéral de chaîne a été corrigé.

On peut se poser une question philosophique, comment peut-on faire une telle erreur dans une fonction à quatre lignes? Tout est possible. Ça y est, la programmation.

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

Dans la ligne copiée, ils ont oublié de remplacer dir [1] par dir [2] . Par conséquent, la valeur le long de l'axe Z n'est pas contrôlée.

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

Oui, même dans un constructeur aussi court, vous pouvez réussir à dépasser la limite d'un tableau lors de son initialisation.

Figure 8


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

Aucun commentaire requis.

Projet Asterisk, langage C
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;
  ....
}

Lors de l'écriture du même type de blocs, l'erreur se situe généralement dans le sous-jacent. Avant cela, tous les cas considérés n'étaient que cela. Ici, la faute de frappe est dans un endroit inhabituel, à savoir, dans le premier bloc. Il est difficile de dire pourquoi cela s'est produit. Je n'ai pas d'autre choix que d'apporter une photo d'une licorne haussant les épaules:

Figure 9


Projet technologique Open CASCADE, C ++
inline void Prepend(const Standard_Integer theIndex)
{
  if (myIndex[1] >= 0)
    Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex....");

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

Deux fois dans la même cellule du tableau, différentes valeurs sont copiées. Une erreur évidente, mais comment y remédier n'était pas claire pour moi, car le code du projet ne m'est pas familier. J'ai donc juste regardé comment les développeurs ont corrigé le code après que notre équipe leur ait signalé cette erreur. L'option correcte:
myIndex[1] = myIndex[0];
myIndex[0] = theIndex;

Projet de pipeline transprotéomique, 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;
  }
  ....
}

Je crains que les packages de recherche contiennent de telles erreurs. Trans-Proteomic Pipeline est conçu pour résoudre des problèmes dans le domaine de la biologie. Cela peut être décidé et «enquêté». Ce paquet trouve généralement beaucoup de choses intéressantes: check in 2012 , check 2013 . Vous pouvez peut-être essayer à nouveau de regarder ce projet.

Projet ITK, langage C ++

Nous sommes confrontés à un autre projet de recherche dans le domaine de la médecine: Medicine Insight Segmentation and Registration Toolkit (ITK). Le projet est différent, mais les erreurs sont les mêmes.
template< typename TCoordRepType >
void
VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize)
{
  m_VoronoiBoundaryOrigin[0] = vorsize[0];
  m_VoronoiBoundaryOrigin[0] = vorsize[1];
}

Projet ITK, C ++


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

Nettoyer le copier-coller.

Projet ReactOS, 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);
  ....
}

Très probablement, la constante crBackgnd devrait être écrite dans la cellule cols [2] .

Projet Coin3D, 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))) {
  ....
}

L'élément du tableau size [1] est revérifié et l'élément de size [2] n'est pas vérifié. C'est ainsi que des artefacts étranges apparaissent sur les images.

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

On sent directement que l'expression cmptlut [0] <0 a été dupliquée deux fois par copie, mais corrigée zéro à un seul endroit.

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

Ci-après, je ne commenterai pas beaucoup de ces erreurs. Sur quoi commenter? La chose principale en regardant de tels fragments de code est de sentir que bien que l'erreur soit simple, cela ne signifie pas qu'elle sera remarquée par le programmeur.

Projet Visualization Toolkit (VTK), 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;
  ....
}

Ici, le programmeur était clairement pressé d'écrire du code plus rapidement. Il est difficile d'expliquer d'une autre manière comment il a fait une erreur trois fois. Les éléments du tableau sont soustraits d'eux-mêmes. Le résultat est que ce code est équivalent:
inZPtr +=
  (0)*inIncs[0] * data_type_size +
  (0)*inIncs[1] * data_type_size +
  (0)*inIncs[2] * data_type_size;

Cependant, ce code peut être encore réduit:
inZPtr += 0;

Somptueusement. Le code contient une expression longue et sérieuse qui, en fait, ne fait rien. J'adore ces cas.

Projet Visualization Toolkit (VTK), langage C ++

Un cas similaire d'écriture précipitée de 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);
    }
}

La comparaison newPos [2]! = OldPos [2] est répétée deux fois .

Projet ADAPTIVE Communication Environment (ACE), 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);
  }
  ....
}

La condition doit vérifier que deux barres obliques après les deux points sont rencontrées. En d'autres termes, la sous-chaîne ": //" est recherchée. En raison d'une faute de frappe, le chèque est "aveugle" et est prêt à compter n'importe quel caractère comme une deuxième barre oblique.

Projet d'échantillons IPP, 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]));
  ....
}

Une faute de frappe se trouve ici, dans les arguments passés à la macro:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])

Il s'avère qu'un minimum de deux valeurs identiques est sélectionné. En fait, il devrait être écrit:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3])

Soit dit en passant, ce code peut démontrer l'utilité de la bibliothèque standard. Si vous écrivez comme ceci:
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));

Ce code deviendra plus court et moins sujet aux erreurs. En fait, moins il y a le même type de code, plus il est probable qu'il sera écrit correctement.

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

L'expression correcte est:
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                 buffer[samplesleft - WindowSizeInt-1]);

Projet PDFium, langage C ++
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;
}

Une série d'actions pour initialiser la structure est dupliquée. Ces lignes marquées d'un commentaire // 2 peuvent être supprimées et rien ne changera. J'ai douté d'inclure ce morceau de code dans l'article. Ce n'est pas tout à fait une erreur, et pas tout à fait avec les indices. Cependant, ce code supplémentaire est probablement apparu précisément parce que le programmeur s'est confondu dans tous ces membres de classe et index 1, 2. Donc, je pense que ce morceau de code est approprié pour démontrer à quel point il est facile de se confondre en nombre.

Projet CMake, C

Le code discuté ci-dessous n'a pas été écrit par les développeurs CMake, mais a été emprunté. A en juger par le commentaire au début du fichier, la fonction utf8_encodeIl a été écrit par Tim Kientzle en 2007. Depuis lors, cette fonction a erré de projet en projet, et il y en a beaucoup. Je n'ai pas étudié la question de la source d'origine, car ce n'est pas le sujet maintenant. Étant donné que ce code est dans le projet CMake, l'erreur s'applique à 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;
  ....
}

Comme vous pouvez le voir, il existe une sorte de confusion avec les index. Il y a deux fois un enregistrement dans l'élément de tableau p [1] . Si vous étudiez le code dans le quartier, il devient clair que le code correct devrait être comme ceci:
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;

Remarque

Veuillez noter que toutes les erreurs abordées dans ce chapitre sont liées au code C ou C ++. Pas de code C # ou Java!

C'est très intéressant, je ne m'y attendais pas. À mon avis, les fautes de frappe envisagées ne dépendent pas de la langue. Et dans les chapitres suivants, des erreurs de code dans d'autres langues apparaîtront en effet. Je pense que ce n'est qu'une coïncidence. L'analyseur PVS-Studio a commencé beaucoup plus tard à prendre en charge les langages C # / Java par rapport à C / C ++, et nous n'avons tout simplement pas réussi à accumuler les exemples d'erreur correspondants dans la base de données.

Cependant, l'observation est toujours intéressante. Apparemment, les programmeurs C et C ++ aiment utiliser les numéros 0, 1 et 2 de plus lorsqu'ils travaillent avec des tableaux :).

Fautes d'orthographe dans les noms


Ce sera la plus grande section. Il est très facile pour les gens de se confondre avec des noms comme a1 et a2 . Il semble que vous puissiez vous embrouiller ici? Pouvez. Facile. Et maintenant, le lecteur pourra le vérifier.

Projet Hive, 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;
}

La fonction de comparaison compare prend deux objets: o1 et o2 . Mais en raison d'une faute de frappe, seul o2 est utilisé plus loin .

Fait intéressant, grâce à Copy-Paste, cette erreur a migré vers une autre fonction:
@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;
}

Figure 10


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

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

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

  return l1;
}

Si quelqu'un n'a pas immédiatement remarqué une faute de frappe, vous devez regarder la ligne où la variable l2 est initialisée . Devrait utiliser exp2 .

Projet SDK du moteur source, 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;
  }
  ....
}

Correctement:
nFPSThreshold1 = 60;
nFPSThreshold2 = 50;

Projet Linux Kernel, langage C

Soit dit en passant, les fautes de frappe peuvent être non seulement dans les noms de variables, mais aussi dans les noms de macro. Il y aura maintenant plusieurs exemples de ce type.
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;
  }
  ....
}

Comme vous pouvez le voir, un masque appelé BIT1 est utilisé deux fois , ce qui rend la deuxième vérification inutile. Le corps de la deuxième instruction conditionnelle commentée ne sera jamais exécuté.

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

Un comportement aléatoire était prévu dans le jeu, mais la même constante SAY_BELNISTRASZ_AGGRO_1 est toujours sélectionnée .

Projet Vangers: One For The Road, 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;
  }
  ....
}

A en juger par le code écrit à côté, l'option correcte devrait être comme ceci:
ret = (lang)
  ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
  : iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1];

Projet RT-Thread, langage C
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 est un système d'exploitation open source en temps réel pour les appareils embarqués. Ici, nous voyons la confusion entre FIFO 0 et FIFO 1. Et quelque part, quelqu'un rencontrera un périphérique buggy.

Figure 11


L'erreur est ici:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO0 == fifo_number){

Le deuxième chèque donne toujours faux. Correctement:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO1 == fifo_number){

Projet Hive, 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")) {
    ....
}

L'analyseur PVS-Studio indique immédiatement 2 erreurs:
  1. Une chaîne stockée dans colOrScalar1 ne peut pas égaler les chaînes Col et Column;
  2. La chaîne stockée dans colOrScalar1 ne peut pas égaler les chaînes Col et Scalar en même temps.

Il y a clairement confusion sur les noms de variables.

Projet Shareaza, langage C ++
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()))
    ....
}

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

Remarque

Prenons une courte pause. On craint qu'en regardant à travers une montagne d'erreurs banales, nous oublions pourquoi nous faisons cela.

La tâche n'est pas de rire du code de quelqu'un d'autre. Tout cela n'est pas une raison pour vous caresser le doigt et dire: "Ha ha, eh bien, vous devez." Cette raison de penser!

Les publications de notre équipe sont conçues pour montrer qu'aucun de nous n'est à l'abri des erreurs. Les erreurs décrites dans l'article apparaissent dans le code beaucoup plus souvent que prévu. Il est également important que la probabilité de se perdre dans 0, 1, 2 ne dépend presque pas des qualifications du programmeur.

Il est utile de réaliser que les gens ont tendance à faire des erreurs. Sans cela, vous ne pouvez pas passer à l'étape suivante pour améliorer la qualité et la fiabilité du code. Comprenant que nous nous trompons tous, les gens commencent à essayer d'identifier les erreurs dès les premiers stades, en utilisant des normes de codage, des revues de code, des tests unitaires, des analyseurs statiques et dynamiques. C'est très bien.

Pourquoi les choses sont-elles si compréhensibles écrites? Malheureusement, en communiquant avec un grand nombre de développeurs, nous sommes obligés de déclarer que ce n'est pas toujours aussi clair pour tout le monde. Beaucoup ont une estime de soi trop élevée et ne permettent tout simplement pas de penser qu'ils sont capables de faire de simples erreurs. C'est triste.

Si vous êtes chef d'équipe / manager, je vous invite à vous familiariser avec cette note en même temps .

Projet Qt, 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())
  ....
}

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

Projet Android, langage C ++
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;
}

Deux fautes de frappe à la fois, grâce auxquelles les variables pr2.mStretchMode et pr2.mFallbackMode sont comparées entre elles.

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

À la toute fin, ils ont scellé et divisé la variable p1.z elle-même.

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

Oui, oui, l'analyseur PVS-Studio trouve des erreurs similaires dans les compilateurs. Correctement:
(Ty1->isIntegerType() &&
 Ty2->isIntegerType())

Projet Clang, 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())) {
  ....
}

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

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

NCBI Genome Workbench Project, 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 {
  .....
}

Erreur lors de la toute première vérification. Il devrait être écrit:
if (!s1.IsSet() && s2.IsSet()) {

NCBI Genome Workbench Project, 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");
  }
  ....
}

La première ligne de la condition s'est propagée, mais le programmeur s'est alors empressé et a oublié de remplacer loc1 par loc2 .

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

Projet FreeCAD, 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);
};

Quelle que soit la condition, la même action est effectuée. Cela semblerait un cas si simple. Comment pouvez-vous copier une ligne sans la réparer? Pouvez.

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

Erreur de copier-coller classique. Correctement:
const Point& GetRef2() const { return maRef2; }

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

Et une autre erreur de copier-coller classique :). À un endroit, 1 à 2 ont été corrigés et à un autre, ils ont oublié.

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

Il n'y a pas d'erreur en remplaçant 1 par 2, mais n'ajoutez tout simplement pas 2 à la deuxième condition.

Figure 12


Vous pourriez être un peu fatigué. Je propose donc de faire du thé ou du café, et nous continuerons notre connaissance du monde des nombres 0, 1 et 2.

Projet logiciel Geant4, langage C ++
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];
  }
  ....
}

J'espère que vous avez suivi les conseils et vous êtes reposé. Prêt à trouver une erreur dans ce code?

Félicitations aux lecteurs qui ont remarqué une erreur. Vous êtes formidable!

Ceux qui sont trop paresseux pour fouiller, je les comprends aussi. La révision d'un tel code est très fastidieuse et on souhaite en quelque sorte aller vite vérifier quelque chose de plus intéressant. C’est là que les analyseurs statiques aident beaucoup car ils ne se fatiguent pas.

L'erreur est que ces deux contrôles sont identiques:
if        (areacode & sC0Min1Max) {
} else if (areacode & sC0Min1Max) {

Si vous étudiez le code, il devient clair que la toute première vérification est erronée. Correctement:
if        (areacode & sC0Min1Min) {
} else if (areacode & sC0Max1Min) {
} else if (areacode & sC0Max1Max) {
} else if (areacode & sC0Min1Max) {

Projet CryEngine V, 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);
}

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

Projet logiciel Geant4, langage C ++
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;
  ....
}

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

Comme vous pouvez le voir, alors que les fragments de code vont sans explication. En fait, il n'y a rien à expliquer ici. Vous ne pouvez que soupirer.

Projet d'émulateur Dolphin, C ++
bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const
{
  return (~ComputeKnownZeroBits(Op1) &
          ~ComputeKnownZeroBits(Op1)) == 0;
}

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

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

Projet QuantLib, 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");
  ....
}

Projet Samba, 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));
  ....
}

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

Projet de système d'exploitation Haiku, 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;
}

Le projet Qt, C ++

Ok, maintenant, allons un peu plus compliqué. Pour le plaisir, essayez de trouver l'erreur ici:
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);
  ....
}

Une photo, afin de ne pas voir la réponse immédiatement, et a eu l'occasion de réfléchir.

Figure 13


C'est vrai, au lieu de orig-> y1 - orig-> y1 devrait être écrit orig-> y1 - orig-> y2 .

Projet de plateforme de compilateur .NET, langage C #
public void IndexerMemberRace()
{
  ....
  for (int i = 0; i < 20; i++)
  {
    ....
    if (i % 2 == 0)
    {
      thread1.Start();
      thread2.Start();
    }
    else
    {
      thread1.Start();
      thread2.Start();
    }
    ....
  }
  ....
}

Cas intéressant. À des fins de test, vous devez exécuter les threads dans un ordre différent. Cependant, en raison d'une faute de frappe, les threads démarrent toujours de la même manière, à la suite de quoi le test vérifie moins qu'il ne devrait.

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

Projet Samba, langage C
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;
}

La fonction de comparaison ne renverra jamais 1, car la condition i2-> pid> i2-> pid n'a pas de sens.

Naturellement, c'est une faute de frappe ordinaire, et en fait, elle devrait être écrite:
if (i1->pid > i2->pid) return 1;

Projet ChakraCore, C ++

Le dernier cas de ce chapitre. Hourra!
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))
  ....
}


Autres erreurs


Parlons maintenant des modèles d'erreur moins nombreux associés à l'utilisation des nombres 0, 1, 2.

Typos dans des conditions où la constante 0/1/2 est explicitement utilisée


Projet ROOT, 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) {
  ....
}

C'est bizarre de comparer fSummaryVrs à 0. deux fois .Projet

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

Projet FFmpeg, langage C
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(....);
  ....
}


Zip / Nom


Plus tôt, nous avons considéré les cas où l'index ou le nom est incorrect. Et voici une situation où vous ne direz pas immédiatement comment classer l'erreur. Cet exemple pourrait être attribué à l'un et à l'autre chapitre. J'ai donc décidé de l'apporter séparément.

Projet de bibliothèque graphique Mesa 3D, 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;
  ....
}

Ce code peut être corrigé comme ceci:
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())

Et vous pouvez le réparer comme ceci:
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


Parfois, 0 est redondant et nuisible. À cause de cela, le nombre peut devenir octal où il n'est pas nécessaire. Ou gâcher la chaîne de format.

Les erreurs mentionnées ne conviennent pas à cet article, mais je pense qu'il convient de les mentionner. Je ne donnerai pas de code avec ces erreurs dans l'article, mais si vous êtes intéressé, vous pouvez les consulter ici:
  • V536 Sachez que la valeur constante utilisée est représentée par une forme octale, exemples ;
  • V638 Un terminal null est présent dans une chaîne. Les caractères «\ 0xNN» ont été rencontrés. Signifiait probablement: «\ xNN», exemples .


J'ai oublié d'écrire +1


Projet de système d'exploitation Haiku, C ++
int
UserlandFS::KernelEmu::new_path(const char *path, char **copy)
{
  ....
  // append a dot, if desired
  if (appendDot) {
    copiedPath[len] = '.';
    copiedPath[len] = '\0';
  }
  ....
}

L'option correcte:
copiedPath[len] = '.';
copiedPath[len + 1] = '\0';

Remarque. La situation où ils ont oublié d'ajouter une unité n'est pas du tout rare. Je me souviens exactement que plus d'une fois j'ai rencontré de tels cas. Cependant, lorsque j'ai voulu taper des exemples similaires pour l'article, je n'ai trouvé que cet exemple. Je suis désolé de ne plus vous effrayer avec ces erreurs. Je m'excuse.

Erreurs de formatage (C #)


Le plus souvent, les fonctions de construction de chaînes fonctionnent avec un petit nombre d'arguments. Il s'avère donc que le plus souvent, les erreurs sont associées à l'utilisation de {0}, {1} ou {2}.

Projet Azure PowerShell, 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)))
  {
    ....
  }
  ....
}

Scellé et écrit {0} deux fois. Par conséquent, le nom this.Name sera inséré deux fois dans la chaîne . Mais le nom this.ResourceGroupName n'entrera pas dans la chaîne créée.

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

C'est généralement étrange. Vous devez insérer ce qui ne l'est pas. Très probablement, ce code a subi une refactorisation infructueuse et s'est avéré être cassé.

Projet Xenko, 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));
}

Le programmeur a oublié que la numérotation commence par {0}, pas par {1}. Le bon code est:
return string.Format(
                    formatProvider,
                    "Red:{0} Green:{1} Blue:{2}",
                    R.ToString(format, formatProvider),
                    G.ToString(format, formatProvider),
                    B.ToString(format, formatProvider));

Projet de plateforme de compilateur .NET, C #
private void DumpAttributes(Symbol s)
{
  ....
  Console.WriteLine("{0} {1} {2}", pa.ToString());
  ....
}

Les arguments ne sont clairement pas suffisants.

Conclusions et Recommendations


J'ai dû démontrer tant d'exemples pour montrer que les fautes de frappe liées à 0, 1 et 2 méritent une attention particulière.

Si je disais simplement: «Il est facile de confondre o1 et o2», vous seriez d'accord, mais n'y attachez pas le sens que vous attachez maintenant, après avoir lu ou au moins fait défiler l'article.

Maintenant, vous êtes averti, et c'est bien. Prévenu est avant-bras. Maintenant, vous serez plus attentif aux révisions de code et porterez une attention particulière aux variables, au nom desquelles vous verrez 0, 1, 2. Il est

difficile de donner quelques recommandations sur la façon dont le code est conçu pour éviter de telles erreurs. Comme vous l'avez vu, des erreurs se trouvent même dans un code aussi simple, où il n'y a rien à émettre.

Par conséquent, je ne vous inciterai pas à éviter 0, 1, 2 et à donner des noms longs aux variables. Si au lieu de chiffres, vous commencez à écrire First / Second / Left / Right et ainsi de suite, la tentation de copier le nom ou l'expression sera encore plus grande. Peut-être qu'une telle recommandation ne réduira finalement pas, mais augmentera le nombre d'erreurs.

Néanmoins, lorsque vous écrivez beaucoup du même type de code, la recommandation de "conception de code tabulaire" est toujours pertinente. La mise en forme tabulaire ne garantit pas l'absence de fautes de frappe, mais les rend plus faciles et plus rapides à remarquer. Voir le chapitre N13 du mini-livre " La question principale de la programmation, de la refactorisation et tout ça ".

Il y a encore une bonne nouvelle. Toutes les erreurs discutées dans cet article ont été trouvées en utilisant l'analyseur de code statique PVS-Studio .. Par conséquent, en introduisant des outils d'analyse statique dans le processus de développement, vous pouvez identifier de nombreuses fautes de frappe à un stade précoce.

Merci pour l'attention. J'espère que vous étiez intéressé et effrayé. Je vous souhaite un code fiable et moins d'erreur avec 0, 1, 2, afin que Freddy ne vienne pas vers vous.



Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Andrey Karpov. Zéro, un, deux, Freddy vient pour toi .

All Articles