Почему люди часто ошибаются при написании простых функций сравнения?
От: Analytic2007 Россия https://www.viva64.com/ru/pvs-studio/
Дата: 21.04.16 13:27
Оценка: 22 (1) -1
Я регулярно пишу статьи о программировании и выступаю на конференциях. В ходе этого я просматриваю большое количество кода и ошибок. У меня был случай, когда в процессе написания статей, я выявил интересную закономерность, которую назвал "Эффект последней строки". Сейчас у меня дежавю. Я готов выделить ещё один паттерн ошибки, но с ним не всё так понятно, и мне интересно услышать мнение сообщества программистов.

Не так давно я делал две презентации (одну для C++, другую для C# программистов). Вдруг я понял, что в обоих случаях мне часто встречаются проблемы в небольших функциях сравнения, носящих такие имена как Compare, Cmp, Equal и так далее. При чем этот паттерн не зависит от языка. Я начал просматривать имеющуюся у меня коллекцию ошибок, и мои подозрения подтвердились. Теперь я могу заявить следующее:

Программисты часто допускают глупые ошибки в простых функциях, сравнивающих два объекта.

Однако, с объяснением этого эффекта не всё так прозрачно как в случае с "Эффектом последней строки". Тот эффект я могу объяснить тем, что ослабляется внимание. Почему же так много ошибок в функциях сравнения? Моё предположение: программисты считают такие функции простыми и однотипными и соответственно пишут их очень быстро, не удосуживаясь проверить. Они уверены, что функции просты, а значит программистам и в голову не приходит их проверять.

Я не знаю, прав я или нет. Мне очень интересно услышать, что думаю другие разработчики о замеченном эффекте. Думаю, это поможет написать мне интересную статью.

Чтобы не быть голословным, я приведу несколько примеров ошибок. Начнем с самых показательных:

C++ (проект Samba):

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


C# (проект IronPython and IronRuby):

public static int Compare(SourceLocation left,
                          SourceLocation right) {
  if (left < right) return -1;
  if (right > left) return 1;
  return 0;
}


У меня много накопилось примеров, и в статье будет бОльшее количество. Сейчас приведу только некоторые, чтобы вопрос не был слишком длинный. Поверьте, раз я обратил на такие функции внимание, в этом во всём действительно есть какая-то закономерность.

С++ (MySQL) Длинный, но однотипный код.

static int rr_cmp(uchar *a,uchar *b)
{
  if (a[0] != b[0])
    return (int) a[0] - (int) b[0];
  if (a[1] != b[1])
    return (int) a[1] - (int) b[1];
  if (a[2] != b[2])
    return (int) a[2] - (int) b[2];
  if (a[3] != b[3])
    return (int) a[3] - (int) b[3];
  if (a[4] != b[4])
    return (int) a[4] - (int) b[4];
  if (a[5] != b[5])
    return (int) a[1] - (int) b[5];        /// <<<
  if (a[6] != b[6])
    return (int) a[6] - (int) b[6];
  return (int) a[7] - (int) b[7];
}


С++ (Expat)

static int
nsattcmp(const void *p1, const void *p2)
{
  const XML_Char *att1 = *(const XML_Char **)p1;
  const XML_Char *att2 = *(const XML_Char **)p2;
  int sep1 = (tcsrchr(att1, NSSEP) != 0);
  int sep2 = (tcsrchr(att1, NSSEP) != 0);     /// <<<
  if (sep1 != sep2)
    return sep1 - sep2;
  return tcscmp(att1, att2);
}


C++ (CryEngine 3 SDK)

inline bool operator != (const SEfResTexture &m) const
{
  if (stricmp(m_Name.c_str(), m_Name.c_str()) != 0 ||       /// <<<
      m_TexFlags != m.m_TexFlags || 
      m_bUTile != m.m_bUTile ||
      m_bVTile != m.m_bVTile ||
      m_Filter != m.m_Filter ||
      m_Ext != m.m_Ext ||
      m_Sampler != m.m_Sampler)
    return true;
  return false;
}


C++ (G3D Content Pak). Подсказка: не там поставлена закрывающаяся круглая скоба.

bool Matrix4::operator==(const Matrix4& other) const {
  if (memcmp(this, &other, sizeof(Matrix4) == 0)) {
    return true;
  }
  ...
}


С++ (RunAsAdmin Explorer Shim)

bool IsLuidsEqual(LUID luid1, LUID luid2)
{
  return (luid1.LowPart == luid2.LowPart) &&
         (luid2.HighPart == luid2.HighPart);
}


С++ (Serious Engine)

  inline BOOL IsEqual( CTexParams tp) {
    return tp_iFilter     == tp.tp_iFilter &&
           tp_iAnisotropy == tp_iAnisotropy &&       /// <<<
           tp_eWrapU      == tp.tp_eWrapU &&
           tp_eWrapV      == tp.tp_eWrapV; };


С# (MonoDevelop) Вновь длинная функция, но код весьма прост.

private bool MembersMatch(ISymbol member1, ISymbol member2)
{
  if (member1.Kind != member2.Kind)
  {
    return false;
  }

  if (member1.DeclaredAccessibility != 
      member1.DeclaredAccessibility 
   || member1.IsStatic != member1.IsStatic)      /// <<<
  {
    return false;
  }

  if (member1.ExplicitInterfaceImplementations().Any() ||  
      member2.ExplicitInterfaceImplementations().Any())
  {
    return false;
  }

  return SignatureComparer
    .HaveSameSignatureAndConstraintsAndReturnTypeAndAccessors(
       member1, member2, this.IsCaseSensitive);
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.