上一篇:关于编程、重构等 42条建议 中
原文来自:
The Ultimate Question of Programming, Refactoring, and Everything
https://software.intel.com/en-us/articles/the-ultimate-question-of-programming-refactoring-and-everything
译文来自:http://blog.csdn.net/huanglong8/article/details/56668577
其实这42条建议可以很快过的,这估计有很多都是老外的习惯。。。
来自虚幻4引擎项目,如果itr是迭代器,那使用++itr会更好。
void FSlateNotificationManager::GetWindows(....) const
{
for( auto Iter(NotificationLists.CreateConstIterator());
Iter; Iter++ )
{
TSharedPtr NotificationList = *Iter;
....
}
}
说明
首先,译者认为++i的效率要比i++高,因为在看重载时你会尤为深刻的认识到这一点。
MyOwnClass& operator++()
{
++meOwnField;
return (*this);
}
MyOwnClass operator++(int)
{
MyOWnCLass tmp = *this;
++(*this);
return tmp;
}
如今编译器可以优化,在release版本可能差异不大,但debug调试时,这种性能问题会稍显明显。
此来自Energy Checker SDK
int main(void) {
...
char *p = NULL;
...
wprintf(
_T("Using power link directory: %s\n"),
p
);
...
}
说明
第一个错误是使用_T以宽字节构建字符串。使用L才是这里正确的方法。如果我们不使用宽字节格式,_T将不会扩展任何内容,代码也不会被编译。如果你使用wprintf打印,你应该使用格式%S。
问题很奇怪,但这是微软干的。
正确的代码
char *p = NULL;
...
#ifdef defined(_WIN32)
wprintf(L"Using power link directory: %S\n"), p);
#else
wprintf(L"Using power link directory: %s\n"), p);
#endif
建议
没啥好建议的,只是警告你这里会有问题,在VS2015中,有了可移植的兼容解决方案,就是给预处理器 _CRT_STDIO_ISO_WIDE_SPECIFIERS宏。
这时候代码变成
const wchar_t * p = L“abcdef”;
const char * x =“xyz”;
wprintf(L“%S%s”,p,x);
这样就正确了,分析器知道这个宏的意思,并考虑所有代码。当然译者看到着,也就呵呵了,一脸懵逼继续。
取自Wolf项目。sizeof要清除返回的大小是否是你想要的大小。
ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
memcpy( mat, src, sizeof( src ) );
}
返回的是指针的大小,而不是数组的大小。那这里永远都是4或8。。。
正确的代码
ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
memcpy(mat, src, sizeof(float) * 3 * 3);
}
建议
你可以通过引用数组来传递
ID_INLINE mat3_t::mat3_t( float (&src)[3][3] )
{
memcpy( mat, src, sizeof( src ) );
}
嗯,这样是可以的。另一种方案就是使用array类等或stl。。。你可以继续参考 不要将单个指针作为数组传递
取自TortoiseSVN项目
BOOL CPOFile::ParseFile(….)
{
….
printf(File.getloc().name().c_str());
….
}
说明
通常打印或写文件,程序员会这么干
printf(str);
fprintf(file,str);
这是一个非常不安全的写法,你没有格式化说明符,可能导致一些后果。译者表示,在实际开发中,确实遇到过,明明都是字符数组,却打印混乱。
需要额外注意的是,这是程序本身的一个真正的漏洞,它可以受黑客控制,以此来做其他的动作,所以要小心。
正确的代码
printf(“%s”,File.getloc()。name()。c_str());
建议
类似输出,流等需要格式化的东西,一定要做格式化操作,以免导致潜在的危险。
此来自GIT源代码中
void mark_tree_uninteresting(struct tree *tree)
{
struct object *obj = &tree->object;
if (!tree)
return;
....
}
说明
应用空指针是会造成错误的。所以不要试图偷懒。这里作者到时又说了一堆话,译者表示不翻了。有个更详细的解释:http : //www.viva64.com/en/b/0306/
建议
写好代码,关爱你我。
这里作者没有说哪个项目,但主要说,如果你定义了一个int型的变量,在做循环时尤要保证循环的大小范围,一旦超出int范围,就会产生错误。
来自Appleseed项目
enum InputFormat
{
InputFormatScalar,
InputFormatSpectralReflectance,
InputFormatSpectralIlluminance,
InputFormatSpectralReflectanceWithAlpha,
InputFormatSpectralIlluminanceWithAlpha,
InputFormatEntity
};
switch (m_format)
{
case InputFormatScalar:
....
case InputFormatSpectralReflectance:
case InputFormatSpectralIlluminance:
....
case InputFormatSpectralReflectanceWithAlpha:
case InputFormatSpectralIlluminanceWithAlpha:
....
}
说明
这个也是由于程序员疏忽造成的,但时刻应该记得,switch中是有default的,这样可以避免你出错的机会。
修改
switch (m_format)
{
case InputFormatScalar:
....
case InputFormatSpectralReflectance:
case InputFormatSpectralIlluminance:
....
case InputFormatSpectralReflectanceWithAlpha:
case InputFormatSpectralIlluminanceWithAlpha:
....
case InputFormatEntity:
....
default:
assert(false);
throw "Not all variants are considered"
}
如果有未定义的枚举,就会抛出异常,这有两个缺点。如果测试没测到,到了用户,那就麻烦了。如果考虑默认其他作为错误,那必须将所有枚举都写一个值,这不方便。作者再次建议这么写。
enum InputFormat
{
InputFormatScalar,
....
InputFormatEntity
//If you want to add a new constant, find all ENUM:InputFormat.
};
switch (m_format) //ENUM:InputFormat
{
....
}
好吧,就是注释而已,搜代码,看来并没有特别好的方法。
这个问题译者也遇到过,当然作者遇到的可能是真正的问题。作者旨在说明,如果你的程序发生异常,奇怪的问题,不要怪编译器,不要怪pc,你应该试图去找到代码中的问题,因为99.99%的bug都出自代码,当然,也不排除其他可能,但那是很少见的。作者截了两个图。
在调试中,作者试图修改内存中的值,可敲入回车时,却发现变成了2.并且立刻去找内存测试程序进行确认。memtest86
后来作者替换了内存库表示一切正常了。。。虽然这没有什么技巧性的建议,但旨在告诉你,检查内存,确保无误。
来自Haiku项目
do {
....
if (appType.InitCheck() == B_OK
&& appType.GetAppHint(&hintRef) == B_OK
&& appRef == hintRef)
{
appType.SetAppHint(NULL);
// try again
continue;
}
....
} while (false);
这段代码看似很傻,其实疏忽也会带来错误的根本。
说明
continue是循环跳出,并没有达到预想的效果。例如下面程序
for (int i = 0; i < n; i++)
{
if (blabla(i))
continue;
foo();
}
Or like this:
while (i < n)
{
if (blabla(i++))
continue;
foo();
}
这是没有问题的,可能程序员就是这么想的,但如果这样
do
{
if (blabla(i++))
continue;
foo();
} while (i < n);
直觉上往往是错误的。事实证明do while(false)中,用continue相当于break。
正确的代码
for (;;) {
....
if (appType.InitCheck() == B_OK
&& appType.GetAppHint(&hintRef) == B_OK
&& appRef == hintRef)
{
appType.SetAppHint(NULL);
// try again
continue;
}
....
break;
};
建议
作者的建议又是主观情感。。。
好吧,译者表示这个已经知道了。。。
来自Miranda NG’s
#define MF_BYCOMMAND 0x00000000L
void CMenuBar::updateState(const HMENU hMenu) const
{
....
::CheckMenuItem(hMenu, ID_VIEW_SHOWAVATAR,
MF_BYCOMMAND | dat->bShowAvatar ? MF_CHECKED : MF_UNCHECKED);
....
}
说明
仍然是优先级问题 ?:优先级要低于|操作。作者建议尽量避免复杂的表达式。。。译者表示重复的凌乱。。。
来自Haiku项目
int compareTypeAndID(....)
{
....
if (lJack && rJack)
{
if (lJack->m_jackType < lJack->m_jackType)
{
return -1;
}
....
}
说明
作者又强调谨慎,细致,但这不是慢慢习惯养成的么,关键中国这么多做外包的,你让外包情何以堪。。。
自己找错误。。。
作者还建议一些单元测试之类的。。。
作者意思表示,如果有这么一个功能,你是用了外部库,或者其他的第三方库,那么你就要做好同时维护的工作,你可以会发现 2017年的程序,还在用2002年的库,并且2002年的库存在重大网络漏洞,当升级后,又不兼容2017年的程序,这是可怕的一件事,可惜,在译者身上发生了,好在只是用了一小段功能。。。
不要过分使用库,作者提出几点想法。
来自WinMerge项目
void CDirView::GetItemFileNames(
int sel, String& strLeft, String& strRight) const
{
UINT_PTR diffpos = GetItemKey(sel);
if (diffpos == (UINT_PTR)SPECIAL_ITEM_POS)
{
strLeft.empty();
strRight.empty();
}
....
}
说明
作者表示,不好的命名会造成其他程序员阅读的误导。例如clear erase empty我们应该尽可能按其本身进行编码。就像empty我们应该认为它是返回是否为空的信息,而并非清空。。。译者表示,我的习惯还是很好的。
这篇文章,或许帮助不是特别大,但至少会强调一些内容。我的目的也是时刻警告程序员,你的粗心可能会导致整个项目在找一个奇怪问题,而耗费了大量的时间,避免这些错误,上点心,这是我的建议。为此,祝大家可以进行无bug编码!
我也邀请大家关注Twitter @Code_Analysis