صفر ، واحد ، اثنان ، فريدي سوف يقلك

الصورة 1

هنا استمرار لسلسلة من المقالات التي يمكن أن تحمل عنوان "أهوال المبرمجين". هذه المرة سنتحدث عن نمط مطبعي نموذجي مرتبط باستخدام الأرقام 0 ، 1 ، 2. لا يهم إذا كتبت في C أو C ++ أو C # أو Java. إذا كنت تستخدم ثوابت 0 ، 1 ، 2 ، أو إذا كانت هذه الأرقام موجودة في أسماء متغيرة ، فمن المرجح أن يزورك فريدي ليلًا. اقرأ ولا تقل لاحقًا أنه لم يتم تحذيرك.


المقدمة


أتابع سلسلة من المقالات المكرسة لأنماط ملحوظة في كيفية ارتكاب الناس للأخطاء. المنشورات السابقة:
  1. تأثير الخط الأخير
  2. أخطر وظيفة في عالم C / C ++
  3. يعيش الشر في وظائف المقارنة

هذه المرة لم يلاحظ هذا النمط من قبلي ، ولكن من قبل زميلي سفياتوسلاف رازميسلوف. ولفت الانتباه إلى حقيقة أنه يصف باستمرار في مقالاته المشاكل التي تظهر فيها المتغيرات التي تحتوي على الأرقام 1 و 2 باسمه. واقترح سفياتوسلاف أن أدرس هذا الموضوع بمزيد من التفصيل واتضح أنه مثمر للغاية. اتضح أن مجموعتنا من الأخطاء تحتوي على عدد كبير من أجزاء التعليمات البرمجية غير الصحيحة بسبب اختلاط الناس في المؤشرات 0 أو 1 أو 2 أو في أسماء المتغيرات التي تحتوي على هذه الأرقام. تم الكشف عن انتظام جديد مثير للاهتمام ، والذي سيتم مناقشته أدناه. أنا ممتن لسفياتوسلاف لدفعه للتحقيق في هذا الموضوع وتكريس هذه المقالة له.

الرقم 14

سفياتوسلاف Razmyslov، مدير، يقظ علة الصيد ومجرد شخص موهوب.

ما هو الغرض من هذه المقالة؟ أظهر مدى سهولة ارتكاب جميع الأخطاء والأخطاء المطبعية. إذا تم تحذير المبرمجين ، فسيكونون أكثر انتباهاً في عملية مراجعة الكود ، مع التركيز على سوء الحظ 0 ، 1 ، 2. أيضًا ، سيكون المبرمجون أكثر قدرة على الشعور بقيمة محللات الكود الثابتة التي تساعد على ملاحظة مثل هذه الأخطاء. لا يتعلق الأمر بإعلانات PVS-Studio (على الرغم من أنه أيضًا :). حتى الآن ، يجد العديد من المبرمجين أن التحليل الثابت غير ضروري ، مفضلين التركيز على الدقة الخاصة بهم ومراجعات الشفرة. لسوء الحظ ، فإن محاولة كتابة التعليمات البرمجية دون أخطاء أمر جيد ، ولكنه ليس كافياً. هذه المقالة ستوضح هذا مرة أخرى بوضوح.

لا أحد محصن ضد مثل هذه الأخطاء. فيما يلي سترى الأخطاء الفادحة في حتى المشاريع المعروفة مثل Qt و Clang و Hive و LibreOffice و Linux Kernel و .NET Compiler Platform و XNU kernel و Mozilla Firefox. وهذه ليست بعض الأخطاء النادرة النادرة ، ولكنها الأكثر تكرارًا. غير مقتنع؟ ثم دعنا نبدأ!

"الثرثرة لا قيمة لها! أرني الأخطاء! "

© اقتباس معدلة من قبل لينوس تورفالدس.

الأخطاء المطبعية في الثوابت عند فهرسة المصفوفات


كقاعدة ، نعطي تحذيرات في مقالاتنا بمساعدة الأخطاء التي تم العثور عليها. هذه المرة سأحذف هذه التحذيرات ، لأنه بدونها سيكون الخطأ واضحًا ومفهومًا بسهولة. ومع ذلك ، على الرغم من أن هذه الأخطاء واضحة على الفور في جزء قصير من التعليمات البرمجية ، إلا أنهم يعرفون كيفية الاختباء في رمز المشاريع.

لنبدأ بالأخطاء عندما يكون هناك خلط بالحروف الرقمية المستخدمة لفهرسة المصفوفات. على الرغم من تفاهة هذه الأخطاء ، فهناك الكثير منها ولا يتم الكشف عنها في العمل المختبري للطلاب.

مشروع نواة XNU ، لغة 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;
  }
  ....
}

تم نسخ السطر ، ولكن نسيت تصحيح الفهرس. كما أفهمها ، يجب كتابتها هنا:
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[1] = 0xff;

مشروع 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;
}

كما في الحالة السابقة ، تم نسخ السطر ، ولكن نسيت إصلاح 0 في 1. تم تصحيح السلسلة الحرفية فقط.

يمكن للمرء أن يسأل سؤال فلسفي ، كيف يمكن للمرء أن يرتكب مثل هذا الخطأ في دالة من أربعة أسطر؟ كل شيء ممكن. ها هي البرمجة.

مشروع الزلزال الثالث أرينا ، لغة سي
int VL_FindAdjacentSurface(....)
{
  ....
  if (fabs(dir[0]) > test->radius ||
      fabs(dir[1]) > test->radius ||
      fabs(dir[1]) > test->radius)
  {
  ....
}

في السطر المنسوخ ، نسوا استبدال dir [1] بـ dir [2] . ونتيجة لذلك ، لا يتم التحكم في القيمة على طول المحور Z.

مشروع OpenCOLLADA ، C ++
struct short2
{
  short values[2];
  short2(short s1, short s2)
  {
    values[0] = s1;
    values[2] = s2;
  }
  ....
};

نعم ، حتى في مثل هذا المنشئ القصير ، يمكنك تجاوز حدود المصفوفة عند تهيئتها.

الرقم 8


مشروع محرك Godot ، C ++
Array PhysicsDirectSpaceState::_cast_motion(....)
{
  ....
  Array ret(true);
  ret.resize(2);
  ret[0]=closest_safe;
  ret[0]=closest_unsafe;
  return ret;
}

لا تعليق مطلوب.

مشروع النجمة ، لغة 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;
  ....
}

عند كتابة نفس نوع الكتل ، يقع الخطأ ، كقاعدة ، في الكامنة الأساسية. قبل ذلك ، كانت جميع الحالات التي تم النظر فيها مجرد ذلك. هنا يقع الخطأ المطبعي في مكان غير معتاد ، أي في الكتلة الأولى. من الصعب القول لماذا حدث ذلك. ليس لدي خيار سوى إحضار صورة من هزل وحيد القرن:

الشكل 9


فتح مشروع CASCADE Technology ، C ++
inline void Prepend(const Standard_Integer theIndex)
{
  if (myIndex[1] >= 0)
    Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex....");

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

يتم نسخ قيم مختلفة مرتين في نفس خلية الصفيف. خطأ واضح ، ولكن كيفية إصلاحه لم تكن واضحة بالنسبة لي ، لأن رمز المشروع ليس مألوفًا بالنسبة لي. لذلك نظرت للتو إلى كيفية قيام المطورين بتصحيح الشفرة بعد أن أشار فريقنا إلى هذا الخطأ لهم. الخيار الصحيح:
myIndex[1] = myIndex[0];
myIndex[0] = theIndex;

مشروع خط الأنابيب العابر للبروتينات ، 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;
  }
  ....
}

أخشى أن تحتوي حزم البحث على مثل هذه الأخطاء. تم تصميم خط أنابيب Trans-Proteomic لحل المشكلات في مجال علم الأحياء. يمكن تقرير ذلك و "التحقيق". تجد هذه الحزمة عمومًا الكثير من الأشياء المثيرة للاهتمام: تحقق في عام 2012 ، تحقق في عام 2013 . ربما يمكنك المحاولة مرة أخرى للنظر في هذا المشروع.

مشروع ITK ، لغة C ++

نحن نواجه مشروعًا آخر لإجراء البحث في مجال الطب: مجموعة أدوات تقطيع البصيرة والتسجيل (ITK). المشروع مختلف ، لكن الأخطاء هي نفسها.
template< typename TCoordRepType >
void
VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize)
{
  m_VoronoiBoundaryOrigin[0] = vorsize[0];
  m_VoronoiBoundaryOrigin[0] = vorsize[1];
}

مشروع ITK ، C ++


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

تنظيف النسخ واللصق.

مشروع 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);
  ....
}

على الأرجح ، يجب كتابة ثابت crBackgnd في خلية الأعمدة [2] .

مشروع 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))) {
  ....
}

يتم فحص عنصر الصفيف الحجم [1] مرتين ، ولا يتم التحقق من عنصر الحجم [2] . هذه هي الطريقة التي تظهر بها التحف الغريبة على الصور.

مشروع OpenCV ، C ++
bool Jpeg2KDecoder::readHeader()
{
  ....
  cmptlut[0] = ....
  cmptlut[1] = ....
  cmptlut[2] = ....
  if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[0] < 0 )
    result = false;
  ....
}

هناك شعور مباشر بأن التعبير cmptlut [0] <0 تم نسخه مرتين عن طريق النسخ ، ولكن تم تصحيحه في مكان واحد فقط.

مشروع مجموعة أدوات التصور (VTK) ، C ++
void vtkImageStencilRaster::PrepareForNewData(....)
{
  ....
  if (allocateExtent &&
      allocateExtent[1] >= allocateExtent[1])
  ....
}

بعد ذلك ، لن أعلق على العديد من هذه الأخطاء. ماذا يوجد للتعليق عليه؟ الشيء الرئيسي عند النظر إلى أجزاء التعليمات البرمجية هذه هو الشعور بأنه على الرغم من أن الخطأ بسيط ، فهذا لا يعني أنه سيلاحظه المبرمج.

مشروع مجموعة أدوات التصور (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;
  ....
}

هنا ، كان المبرمج في عجلة من أمره لكتابة التعليمات البرمجية بشكل أسرع. من الصعب أن أشرح بطريقة أخرى كيف ارتكب خطأ ثلاث مرات. يتم طرح عناصر المصفوفة من نفسها. والنتيجة أن هذا الرمز مكافئ:
inZPtr +=
  (0)*inIncs[0] * data_type_size +
  (0)*inIncs[1] * data_type_size +
  (0)*inIncs[2] * data_type_size;

ومع ذلك ، يمكن تقليل هذا الرمز أكثر من ذلك:
inZPtr += 0;

بشكل فاخر. هناك تعبير طويل وجاد في الكود ، في الواقع ، لا يفعل شيئًا. أنا أحب مثل هذه الحالات.

مشروع مجموعة أدوات التصور (VTK) ، لغة C ++

حالة مماثلة من كتابة التعليمات البرمجية المتسرعة.
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);
    }
}

المقارنة newPos [2]! = OldPos [2] يتكرر مرتين .

مشروع بيئة الاتصالات المتكيفة (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);
  }
  ....
}

يجب أن تتحقق الحالة من وجود خطين مائلين بعد القولون. بمعنى آخر ، يتم البحث في السلسلة الفرعية ": //". نظرًا لخطأ مطبعي ، يكون الشيك "أعمى" وجاهزًا لاحتساب أي حرف على أنه شرطة مائلة ثانية.

مشروع عينات 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]));
  ....
}

خطأ مطبعي هنا ، في الحجج التي تم تمريرها إلى الماكرو:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])

اتضح أنه تم تحديد قيمتين متطابقتين على الأقل. في الواقع ، يجب كتابتها:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3])

بالمناسبة ، يمكن أن يثبت هذا الرمز فائدة المكتبة القياسية. إذا كتبت مثل هذا:
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));

سيصبح هذا الرمز أقصر وأقل عرضة للأخطاء. في الواقع ، كلما قل نفس نوع الرمز ، زادت احتمالية كتابته بشكل صحيح.

جرأة المشروع ، C ++
sampleCount VoiceKey::OnBackward (....) {
  ....
  int atrend = sgn(buffer[samplesleft - 2]-
                   buffer[samplesleft - 1]);
  int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                   buffer[samplesleft - WindowSizeInt-2]);
  ....
}

التعبير الصحيح هو:
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                 buffer[samplesleft - WindowSizeInt-1]);

مشروع PDFium ، لغة 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;
}

يتم تكرار سلسلة من الإجراءات لتهيئة الهيكل. يمكن حذف هذه الأسطر المميزة بالتعليق // 2 ، ولن يتغير شيء. شككت في ما إذا كان سيتم تضمين هذا الجزء من التعليمات البرمجية في المقالة. هذا ليس خطأ تمامًا ، وليس تمامًا مع المؤشرات. ومع ذلك ، فإن هذا الرمز الإضافي على الأرجح ظهر على وجه التحديد لأن المبرمج اختلط في كل أعضاء الصف والمؤشرات 1 ، 2. لذا ، أعتقد أن هذا الجزء من الكود مناسب لإظهار مدى سهولة الخلط بين الأرقام.

مشروع CMake ، C

لم يكتب مطورو CMake الكود الموضح أدناه ، ولكن تم استعارته. إذا حكمنا من خلال التعليق في بداية الملف ، فإن وظيفة utf8_encodeتمت كتابته من قبل Tim Kientzle في عام 2007. ومنذ ذلك الحين ، تنتقل هذه الوظيفة من مشروع إلى آخر ، وهناك العديد من الأماكن التي تحدث فيها. لم أدرس قضية المصدر الأصلي ، لأن هذه ليست النقطة الآن. نظرًا لأن هذا الرمز موجود في مشروع CMake ، فإن الخطأ ينطبق على 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;
  ....
}

كما ترون ، هناك نوع من الارتباك مع الفهارس. مرتين يوجد سجل في صفيف عنصر ص [1] . إذا درست الرمز في الحي ، يصبح من الواضح أن الرمز الصحيح يجب أن يكون مثل هذا:
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;

ملاحظة

يرجى ملاحظة أن جميع الأخطاء التي تمت مناقشتها في هذا الفصل تتعلق برمز C أو C ++. لا C # أو كود Java!

هذا مثير للاهتمام ، لم أتوقع هذا. في رأيي ، فإن الأخطاء المطبعية في الاعتبار لا تعتمد على اللغة. وفي الفصول التالية ، ستظهر بالفعل أخطاء في التعليمات البرمجية بلغات أخرى. أعتقد أن هذه مجرد مصادفة. بدأ محلل PVS-Studio في وقت لاحق لدعم لغات C # / Java مقارنة بـ C / C ++ ، ولم نتمكن من تجميع أمثلة الأخطاء المقابلة في قاعدة البيانات.

ومع ذلك ، لا تزال الملاحظة مثيرة للاهتمام. يبدو أن المبرمجين C و C ++ يفضلون استخدام الأرقام 0 و 1 و 2 عند العمل مع المصفوفات :).

الأخطاء الإملائية في الأسماء


سيكون هذا القسم الأكبر. من السهل جدًا الخلط بين الأشخاص باستخدام أسماء مثل a1 و a2 . يبدو أنه يمكنك الخلط هنا؟ يستطيع. سهل. والآن سيتمكن القارئ من التحقق من ذلك.

مشروع الخلية ، جافا
@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;
}

تأخذ مقارنة الدالة مقارنة كائنين: o1 و o2 . ولكن بسبب خطأ مطبعي ، يتم استخدام o2 فقط .

من المثير للاهتمام ، بفضل Copy-Paste ، تم ترحيل هذا الخطأ إلى وظيفة أخرى:
@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;
}

الرقم 10


مشروع Infer.NET ، لغة C #
private void MergeParallelTransitions()
{
  ....
  if (double.IsInfinity(transition1.Weight.Value) &&    
      double.IsInfinity(transition1.Weight.Value))
  ....
}

مشروع Doom 3 ، C ++
uint AltOp::fixedLength()
{
  uint l1 = exp1->fixedLength();
  uint l2 = exp1->fixedLength();

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

  return l1;
}

إذا لم يلاحظ أحد على الفور خطأ مطبعيًا ، فأنت بحاجة إلى إلقاء نظرة على السطر الذي تتم فيه تهيئة المتغير l2 . يجب استخدام exp2 .

مشروع SDK لمحرك المصدر ، 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;
  }
  ....
}

بشكل صحيح:
nFPSThreshold1 = 60;
nFPSThreshold2 = 50;

مشروع Linux Kernel ، لغة C

بالمناسبة ، لا يمكن أن تكون الأخطاء المطبعية في أسماء المتغيرات فقط ، ولكن أيضًا في أسماء الماكرو. الآن سيكون هناك العديد من هذه الأمثلة.
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;
  }
  ....
}

كما ترون ، يتم استخدام قناع يسمى BIT1 مرتين ، مما يجعل الفحص الثاني بلا معنى. لن يتم تنفيذ نص البيان الشرطي الثاني المعلق.

مشروع CMaNGOS ، C ++
void AttackedBy(Unit* pAttacker) override
{
  ....
  DoScriptText(urand(0, 1) ?
               SAY_BELNISTRASZ_AGGRO_1 :
               SAY_BELNISTRASZ_AGGRO_1,
               m_creature, pAttacker);
  ....
}

تم التخطيط لسلوك عشوائي في اللعبة ، ولكن يتم تحديد نفس ثابت SAY_BELNISTRASZ_AGGRO_1 دائمًا .

مشروع 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;
  }
  ....
}

إذا حكمنا من خلال الرمز المكتوب بجواره ، فيجب أن يكون الخيار الصحيح كما يلي:
ret = (lang)
  ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
  : iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1];

مشروع RT-Thread ، لغة 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 هو نظام تشغيل مفتوح المصدر في الوقت الحقيقي للأجهزة المدمجة. هنا نرى الخلط بين FIFO 0 و FIFO 1. وفي مكان ما ، سيواجه شخص ما جهاز عربات التي تجرها الدواب.

الرقم 11


الخطأ هنا:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO0 == fifo_number){

الاختيار الثاني يعطي دائما خطأ. بشكل صحيح:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO1 == fifo_number){

مشروع الخلية ، جافا
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")) {
    ....
}

يشير محلل PVS-Studio على الفور إلى خطأين:
  1. لا يمكن لسلسلة مخزنة في colOrScalar1 أن تساوي سلسلتي العمود والعمود ؛
  2. لا يمكن أن تساوي السلسلة المخزنة في colOrScalar1 سلسلتي Col و Scalar في نفس الوقت.

هناك ارتباك واضح حول الأسماء المتغيرة.

مشروع شريزا ، لغة 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()))
    ....
}

بشكل صحيح:
pAttr1->GetValue().CompareNoCase(pAttr2->GetValue())

ملاحظة

لنأخذ وقفة قصيرة. هناك خوف من أن ننظر عبر جبل من الأخطاء المبتذلة ، سوف ننسى سبب قيامنا بذلك.

المهمة ليست الضحك على رمز شخص آخر. كل هذا ليس سببًا لكزة إصبعك والقول: "هاهاها ، يجب عليك ذلك". هذا سبب التفكير!

تم تصميم منشورات فريقنا لإظهار أنه لا أحد منا في مأمن من الأخطاء. تظهر الأخطاء الموضحة في المقالة في التعليمات البرمجية في كثير من الأحيان أكثر مما تتوقع. من المهم أيضًا أن احتمال الضياع في 0 ، 1 ، 2 لا يعتمد تقريبًا على مؤهلات المبرمج.

من المفيد أن ندرك أن الناس يميلون إلى ارتكاب الأخطاء. بدون هذا ، لا يمكنك اتخاذ الخطوة التالية في تحسين جودة وموثوقية التعليمات البرمجية. إدراكًا بأننا جميعًا مخطئون ، يبدأ الناس في محاولة تحديد الأخطاء في المراحل الأولى ، باستخدام معايير التشفير ومراجعات الكود واختبارات الوحدة والتحليلات الثابتة والديناميكية. انه جيد جدا.

لماذا الأشياء مفهومة للغاية؟ لسوء الحظ ، بالتواصل مع عدد كبير من المطورين ، نحن مضطرون إلى القول أنه ليس واضحًا دائمًا للجميع. يتمتع الكثيرون باحترام عالي للغاية لأنهم ببساطة لا يسمحون للفكرة بأنهم قادرون على ارتكاب أخطاء بسيطة. أنه لأمر محزن.

إذا كنت قائد / مدير فريق ، فأنا أدعوك إلى التعرف على هذه الملاحظة في نفس الوقت .

مشروع 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())
  ....
}

بشكل صحيح:
const Numeric *const num2 = o2.as<Numeric>();

مشروع Android ، لغة 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;
}

نوعان من الأخطاء المطبعية في وقت واحد ، حيث تتم مقارنة المتغيرات pr2.mStretchMode و pr2.mFallbackMode معهما .

تعزيز المشروع ، C ++
point3D operator/(const point3D &p1, const point3D &p2)
{
  return point3D(p1.x/p2.x, p1.y/p2.y, p1.z/p1.z);
}

في النهاية ، قاموا بإغلاق المتغير p1.z نفسه وتقسيمه .

مشروع Clang ، C ++
bool haveSameType(QualType Ty1, QualType Ty2) {
  return (Context.getCanonicalType(Ty1) ==
          Context.getCanonicalType(Ty2) ||
          (Ty2->isIntegerType() &&
           Ty2->isIntegerType()));
}

نعم ، نعم ، يجد محلل PVS-Studio أخطاء مماثلة في المترجمات. بشكل صحيح:
(Ty1->isIntegerType() &&
 Ty2->isIntegerType())

مشروع 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())) {
  ....
}

بشكل صحيح:
(Op0I->hasOneUse() || Op1I->hasOneUse())

مشروع Qt ، C ++
inline bool qCompare(QImage const &t1, QImage const &t2, ....)
{
  ....
  if (t1.width() != t2.width() || t2.height() != t2.height()) {
  ....
}

مشروع NCBI Genome Workbench ، 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 {
  .....
}

خطأ في الاختيار الأول. يجب كتابتها:
if (!s1.IsSet() && s2.IsSet()) {

مشروع NCBI Genome Workbench ، 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");
  }
  ....
}

تم نشر السطر الأول من الشرط ، ولكن بعد ذلك سارع المبرمج ونسي استبدال loc1 بـ loc2 .

مشروع FlashDevelop ، C #
public void SetPrices(....)
{
  UInt32 a0 = _choice.GetPrice0();
  UInt32 a1 = _choice.GetPrice1();
  UInt32 b0 = a1 + _choice2.GetPrice0();   // <=
  UInt32 b1 = a1 + _choice2.GetPrice1();
  ....
}

مشروع 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);
};

بغض النظر عن الحالة ، يتم تنفيذ نفس الإجراء. قد تبدو مثل هذه الحالة البسيطة. كيف يمكنك نسخ خط وليس إصلاحه؟ يستطيع.

مشروع LibreOffice ، C ++
class SVX_DLLPUBLIC SdrMarkView : public SdrSnapView
{
  ....
  const Point& GetRef1() const { return maRef1; }
  const Point& GetRef2() const { return maRef1; }
  ....
};

خطأ النسخ واللصق الكلاسيكي. بشكل صحيح:
const Point& GetRef2() const { return maRef2; }

مشروع 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();
  ....
}

وخطأ نسخ ولصق كلاسيكي آخر :). في مكان واحد إلى 2 تصحيح ، وفي مكان آخر نسوا.

مشروع LibreOffice ، C ++
XMLTransformerOOoEventMap_Impl::XMLTransformerOOoEventMap_Impl(
        XMLTransformerEventMapEntry *pInit,
        XMLTransformerEventMapEntry *pInit2 )
{
  if( pInit )
    AddMap( pInit );
  if( pInit )
    AddMap( pInit2 );
}

لا يوجد خطأ في استبدال 1 بـ 2 ، ولكن فقط لم تضيف 2 إلى الشرط الثاني.

الرقم 12


قد تكون متعبًا قليلاً. لذلك ، أقترح أن أصنع الشاي أو القهوة ، وسنواصل معرفتنا بعالم الأرقام 0 و 1 و 2.

مشروع برمجيات Geant4 ، لغة 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];
  }
  ....
}

آمل أن تكون قد تلقيت النصيحة واستراحة. هل أنت جاهز للعثور على خطأ في هذا الرمز؟

مبروك للقراء الذين لاحظوا خطأ. انت عظيم!

أولئك الذين كسالى جدا للبحث ، وأنا أفهم أيضا. مراجعة هذا الرمز مملة للغاية وهناك رغبة في الذهاب بسرعة بطريقة ما للتحقق من شيء أكثر إثارة للاهتمام. هذا هو المكان الذي يساعد فيه المحللون الثابتون كثيرًا لأنهم لا يتعبون.

الخطأ هو أن هذين الاختبارين متماثلان:
if        (areacode & sC0Min1Max) {
} else if (areacode & sC0Min1Max) {

إذا قمت بدراسة الرمز ، يصبح من الواضح أن أول عملية تحقق خاطئة. بشكل صحيح:
if        (areacode & sC0Min1Min) {
} else if (areacode & sC0Max1Min) {
} else if (areacode & sC0Max1Max) {
} else if (areacode & sC0Min1Max) {

مشروع 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);
}

مشروع TortoiseGit ، C ++
void CGitStatusListCtrl::OnContextMenuList(....)
{
  ....
  if( (!this->m_Rev1.IsEmpty()) ||
      (!this->m_Rev1.IsEmpty()) )
  ....
}

مشروع برمجيات Geant4 ، لغة 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;
  ....
}

مشروع MonoDevelop ، C #
private bool MembersMatch(ISymbol member1, ISymbol member2)
{
  ....
  if (member1.DeclaredAccessibility !=
      member1.DeclaredAccessibility
   || member1.IsStatic != member1.IsStatic)
  {
    return false;
  }
  ....
}

كما ترى ، في حين أن أجزاء التعليمات البرمجية تذهب دون تفسير. في الواقع ، لا يوجد شيء لشرحه هنا. يمكنك تنهد فقط.

مشروع محاكي دولفين ، C ++
bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const
{
  return (~ComputeKnownZeroBits(Op1) &
          ~ComputeKnownZeroBits(Op1)) == 0;
}

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

مشروع IT ++ ، لغة C ++
Gold::Gold(const ivec &mseq1_connections,
           const ivec &mseq2_connections)
{
  ....
  it_assert(mseq1.get_length() == mseq1.get_length(),
            "Gold::Gold(): dimension mismatch");
}

مشروع 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");
  ....
}

مشروع سامبا ، سي ++
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 ، C ++
static PRBool IsZPositionLEQ(nsDisplayItem* aItem1,
                             nsDisplayItem* aItem2,
                             void* aClosure) {
  if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
      !aItem1->GetUnderlyingFrame()->Preserves3D()) {
    return IsContentLEQ(aItem1, aItem2, aClosure);
  }
  ....
}

مشروع نظام تشغيل هايكو ، 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;
}

مشروع Qt ، C ++

Ok ، دعنا الآن أكثر تعقيدًا. للمتعة ، حاول العثور على الخطأ هنا:
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);
  ....
}

صورة ، حتى لا ترى الجواب على الفور ، وأتيحت لها الفرصة للتفكير.

الرقم 13


هذا صحيح ، بدلاً من orig-> y1 - orig-> y1 يجب كتابته orig-> y1 - orig-> y2 .

مشروع برنامج .NET Compiler Platform ، لغة C #
public void IndexerMemberRace()
{
  ....
  for (int i = 0; i < 20; i++)
  {
    ....
    if (i % 2 == 0)
    {
      thread1.Start();
      thread2.Start();
    }
    else
    {
      thread1.Start();
      thread2.Start();
    }
    ....
  }
  ....
}

حالة مثيرة للاهتمام. لأغراض الاختبار ، تحتاج إلى تشغيل سلاسل الرسائل بترتيب مختلف. ومع ذلك ، بسبب خطأ مطبعي ، تبدأ الخيوط دائمًا بالطريقة نفسها ، ونتيجة لذلك يتحقق الاختبار أقل مما ينبغي.

بشكل صحيح:
if (i % 2 == 0)
{
  thread1.Start();
  thread2.Start();
}
else
{
  thread2.Start();
  thread1.Start();
}

مشروع سامبا ، لغة سي
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;
}

لن تقوم دالة المقارنة بإرجاع 1 أبدًا ، نظرًا لأن الشرط i2-> pid> i2-> pid لا معنى له.

بطبيعة الحال ، هذا خطأ مطبعي عادي ، وفي الواقع يجب كتابته:
if (i1->pid > i2->pid) return 1;

مشروع ChakraCore ، C ++

الحالة الأخيرة في هذا الفصل. مرحى!
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))
  ....
}


أخطاء أخرى


الآن دعونا نتحدث عن أنماط خطأ أقل عددًا مرتبطة باستخدام الأرقام 0 ، 1 ، 2.

الأخطاء المطبعية في الظروف التي يتم فيها استخدام الثابت 0/1/2 بشكل صريح


مشروع 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) {
  ....
}

غريب مقارنة fSummaryVrs بـ 0. مرتين .

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

مشروع FFmpeg ، لغة 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(....);
  ....
}


الرمز / الاسم


نظرنا سابقًا في الحالات التي يكون فيها الفهرس أو الاسم غير صحيح. وإليك موقف لن تخبر فيه على الفور عن كيفية تصنيف الخطأ. يمكن أن يُنسب هذا المثال إلى كل من الفصل الأول والآخر. لذلك ، قررت إحضاره بشكل منفصل.

مشروع Mesa 3D Library Library ، 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;
  ....
}

يمكن إصلاح هذا الرمز على النحو التالي:
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())

ويمكنك إصلاحه على النحو التالي:
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())


إضافي 0


في بعض الأحيان يكون الرقم 0 فائضًا وضارًا. بسبب ذلك ، يمكن أن يتحول الرقم إلى ثماني حيث لا حاجة إليه. أو إفساد سلسلة التنسيق.

الأخطاء المذكورة ليست مناسبة لهذه المقالة ، لكن أعتقد أنها جديرة بالذكر. لن أعطي رمزًا يحتوي على هذه الأخطاء في المقالة ، ولكن إذا كنت مهتمًا ، فيمكنك الاطلاع عليها هنا:

نسيت أن أكتب +1


مشروع نظام تشغيل هايكو ، C ++
int
UserlandFS::KernelEmu::new_path(const char *path, char **copy)
{
  ....
  // append a dot, if desired
  if (appendDot) {
    copiedPath[len] = '.';
    copiedPath[len] = '\0';
  }
  ....
}

الخيار الصحيح:
copiedPath[len] = '.';
copiedPath[len + 1] = '\0';

ملحوظة. الوضع عندما ينسون إضافة وحدة ليست نادرة على الإطلاق. أتذكر بالضبط أنني قابلت مثل هذه الحالات أكثر من مرة. ومع ذلك ، عندما أردت كتابة أمثلة مماثلة للمقالة ، وجدت هذا المثال فقط. أنا آسف لأنني لا أستطيع إخافتك بهذه الأخطاء أكثر. أنا أعتذر.

أخطاء التنسيق (C #)


في معظم الأحيان ، تعمل وظائف بناء السلاسل مع عدد قليل من الحجج. لذلك اتضح أن الأخطاء غالبًا ما ترتبط باستخدام {0} أو {1} أو {2}.

مشروع 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)))
  {
    ....
  }
  ....
}

أغلقت وكتبت {0} مرتين. ونتيجة لذلك ، سيتم إدراج الاسم this.Name في السلسلة مرتين . لكن الاسم this.ResourceGroupName لن يدخل في السلسلة التي تم إنشاؤها.

مشروع أحادي ، C #
void ReadEntropy ()
{
  if (reader.IsEmptyElement)
    throw new XmlException (
      String.Format ("WS-Trust Entropy element is empty.{2}",
                      LineInfo ()));
  ....
}

هذا غريب بشكل عام. تحتاج إلى إدراج ما هو ليس كذلك. على الأرجح ، خضع هذا الرمز لإعادة هيكلة فاشلة واتضح أنه مكسور.

مشروع 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));
}

نسي المبرمج أن الترقيم يبدأ بـ {0} ، وليس {1}. الرمز الصحيح هو:
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 ، C #
private void DumpAttributes(Symbol s)
{
  ....
  Console.WriteLine("{0} {1} {2}", pa.ToString());
  ....
}

من الواضح أن الحجج ليست كافية.

الاستنتاجات والتوصيات


كان عليّ أن أوضح العديد من الأمثلة لإظهار أن الأخطاء المطبعية المتعلقة بالأرقام 0 و 1 و 2 تستحق اهتمامًا خاصًا.

إذا قلت ببساطة: "من السهل الخلط بين o1 و o2" ، فإنك توافق ، ولكن لا تعلق عليه المعنى الذي تعلقه الآن ، بعد القراءة أو على الأقل التمرير عبر المقالة.

الآن يتم تحذيرك ، وهذا أمر جيد. أعذر من أنذر. ستصبح الآن أكثر انتباهاً لمراجعات الشفرة وستولي اهتمامًا إضافيًا للمتغيرات ، التي سترى في أسماءها 0 ، 1 ، 2. من

الصعب تقديم بعض التوصيات حول كيفية تصميم الشفرة لتجنب مثل هذه الأخطاء. كما رأيت ، تم العثور على أخطاء حتى في مثل هذا الرمز البسيط ، حيث لا يوجد شيء لمشكلته.

لذلك ، لن أحثك ​​على تجنب 0 و 1 و 2 وإعطاء أسماء طويلة للمتغيرات. إذا بدأت الكتابة بدلاً من الأرقام أولاً / ثانية / يسار / يمين وما إلى ذلك ، فإن إغراء نسخ الاسم أو التعبير سيكون أكبر. ربما لن تقلل مثل هذه التوصية في النهاية ، بل تزيد من عدد الأخطاء.

ومع ذلك ، عندما تكتب الكثير من نفس النوع من التعليمات البرمجية ، فإن توصية "تصميم التعليمات البرمجية المجدولة" لا تزال ذات صلة. لا يضمن التنسيق الجدولي عدم وجود أخطاء إملائية ، ولكنه يجعلها أسهل وأسرع في ملاحظة ذلك. انظر الفصل N13 في الكتاب المصغر " السؤال الرئيسي عن البرمجة وإعادة البناء وكل ذلك ."

هناك أخبار أخرى جيدة. تم العثور على جميع الأخطاء التي تمت مناقشتها في هذه المقالة باستخدام محلل الأكواد الثابتة PVS-Studio .. وبناءً على ذلك ، عند إدخال أدوات التحليل الثابت في عملية التطوير ، يمكنك تحديد العديد من الأخطاء المطبعية في مرحلة مبكرة.

شكرا للانتباه. اتمنى انك كنت خائفا وخائفا. أتمنى لك رمزًا موثوقًا به وأقل خطأ مع 0 ، 1 ، 2 ، حتى لا يأتي فريدي إليك.



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Andrey Karpov. صفر ، واحد ، اثنان ، فريدي قادم لك .

All Articles