关于编程、重构等 42条建议 下

上一篇:关于编程、重构等 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条建议可以很快过的,这估计有很多都是老外的习惯。。。

29. 在迭代器中使用++i,不要用后缀i++

来自虚幻4引擎项目,如果itr是迭代器,那使用++itr会更好。

void FSlateNotificationManager::GetWindows(....) const
{
  for( auto Iter(NotificationLists.CreateConstIterator());
       Iter; Iter++ )
  {
    TSharedPtr<SNotificationList> NotificationList = *Iter;
    ....
  }
}

说明
首先,译者认为++i的效率要比i++高,因为在看重载时你会尤为深刻的认识到这一点。

MyOwnClass& operator++()
{
  ++meOwnField;
  return (*this);
}

MyOwnClass operator++(int)
{
  MyOWnCLass tmp = *this;
  ++(*this);
  return tmp;
}

如今编译器可以优化,在release版本可能差异不大,但debug调试时,这种性能问题会稍显明显。

30. VC++和sprintf函数

此来自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);

这样就正确了,分析器知道这个宏的意思,并考虑所有代码。当然译者看到着,也就呵呵了,一脸懵逼继续。

31. C/C++中,数组参数传递问题

取自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。。。你可以继续参考 不要将单个指针作为数组传递

32. 危险的printf

取自TortoiseSVN项目
BOOL CPOFile::ParseFile(….)
{
….
printf(File.getloc().name().c_str());
….
}
说明
通常打印或写文件,程序员会这么干

printf(str);
fprintf(file,str);

这是一个非常不安全的写法,你没有格式化说明符,可能导致一些后果。译者表示,在实际开发中,确实遇到过,明明都是字符数组,却打印混乱。
需要额外注意的是,这是程序本身的一个真正的漏洞,它可以受黑客控制,以此来做其他的动作,所以要小心。
正确的代码

printf(“%s”,File.getloc()。name()。c_str());

建议
类似输出,流等需要格式化的东西,一定要做格式化操作,以免导致潜在的危险。

33. 永远不要引用空指针

此来自GIT源代码中

void mark_tree_uninteresting(struct tree *tree)
{
  struct object *obj = &tree->object;
  if (!tree)
    return;
  ....
}

说明
应用空指针是会造成错误的。所以不要试图偷懒。这里作者到时又说了一堆话,译者表示不翻了。有个更详细的解释:http : //www.viva64.com/en/b/0306/
建议
写好代码,关爱你我。

34. 保证类型的区间大小可用

这里作者没有说哪个项目,但主要说,如果你定义了一个int型的变量,在做循环时尤要保证循环的大小范围,一旦超出int范围,就会产生错误。

35. 添加枚举记得修改switch

来自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
{
  ....
}

好吧,就是注释而已,搜代码,看来并没有特别好的方法。

36. 如果电脑发神经,请检查内存

这个问题译者也遇到过,当然作者遇到的可能是真正的问题。作者旨在说明,如果你的程序发生异常,奇怪的问题,不要怪编译器,不要怪pc,你应该试图去找到代码中的问题,因为99.99%的bug都出自代码,当然,也不排除其他可能,但那是很少见的。作者截了两个图。
这里写图片描述
这里写图片描述
在调试中,作者试图修改内存中的值,可敲入回车时,却发现变成了2.并且立刻去找内存测试程序进行确认。memtest86
后来作者替换了内存库表示一切正常了。。。虽然这没有什么技巧性的建议,但旨在告诉你,检查内存,确保无误。

37. 注意在do while中的continue

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

建议
作者的建议又是主观情感。。。

38. 从现在开始使用nullptr而不是null

好吧,译者表示这个已经知道了。。。

39. 代码怎么又不正确了

来自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);
  ....
}

说明
仍然是优先级问题 ?:优先级要低于|操作。作者建议尽量避免复杂的表达式。。。译者表示重复的凌乱。。。

40. 使用静态代码分析

来自Haiku项目

int compareTypeAndID(....)
{
  ....
  if (lJack && rJack)
  {
    if (lJack->m_jackType < lJack->m_jackType)
    {
      return -1;
    }
    ....
}

说明
作者又强调谨慎,细致,但这不是慢慢习惯养成的么,关键中国这么多做外包的,你让外包情何以堪。。。
自己找错误。。。
作者还建议一些单元测试之类的。。。

41. 避免向项目中添加新库

作者意思表示,如果有这么一个功能,你是用了外部库,或者其他的第三方库,那么你就要做好同时维护的工作,你可以会发现 2017年的程序,还在用2002年的库,并且2002年的库存在重大网络漏洞,当升级后,又不兼容2017年的程序,这是可怕的一件事,可惜,在译者身上发生了,好在只是用了一小段功能。。。
不要过分使用库,作者提出几点想法。

  • 添加新库,如果很重,则增加项目的大小。
  • 即使使用1%的功能,他都包含了整体,负担很重。
  • 一起编译很复杂,开发的体验并不是很好。
  • 如果你关心安全漏洞,你就知道这是致命的。
  • 你用的那个库如果更改了许可咋办。。。
  • 升级也是很麻烦的事
  • 不同编译器,移植,呵呵呵。。。
  • 不同的平台也会有问题。
  • 如果太多,我都不知道类叫什么名字,因为太多重名。。。
  • 添加很多库没事,但很乱。
  • 新库有很多bug,你可能都不知道。
    但不是说我们不能使用库,还是合理使用,不能滥用。
    建议
    不要急于添加库到项目中,你应该多思考,他的轻重优劣。

42. 不要使用empty命名函数

来自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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值