Я регулярно пишу статьи о программировании и выступаю на конференциях. В ходе этого я просматриваю большое количество кода и ошибок. У меня был случай, когда в процессе написания статей, я выявил интересную закономерность, которую назвал "
Эффект последней строки". Сейчас у меня дежавю. Я готов выделить ещё один паттерн ошибки, но с ним не всё так понятно, и мне интересно услышать мнение сообщества программистов.
Не так давно я делал две презентации (одну для 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);
}