我在一些遗留代码中偶然发现了这个错误:
class MyAPIHandler
{
private:
int handle;
public:
void MyApiHandler() // default constructor
{
handle = 42;
};
};
编译很好,没有任何警告 - 但行为不是我想要的,因为构造函数名称是拼写错误。这本身会产生关于“函数不返回值”的警告,但我想我是在自动驾驶上并添加了一个“无效”返回类型来“修复”这个。
现在,错误修复很容易,但我的问题是: -
我可以使用哪些技术来防止此类错误重复出现?
某些语言需要明确的“构造函数”关键字,这应该会使这个问题变得明显。单元测试,显然也应该抓住它。我还能做什么?
答案 0 :(得分:19)
如果您始终在构造函数中使用初始化列表:
MyApiHandler() // default constructor
: handle(42)
{
}
错误的构造函数错误甚至更不可能,反正它更好style。
修改:感谢评论者提供的链接
答案 1 :(得分:2)
代码审核?单元测试,如你所提到的那样也很好。代码覆盖率。许多常见的查找错误的工具都可以找到它。
答案 2 :(得分:2)
除了小心或有代码审查之外,你可以做的事情并不多。
您可以制作一个清单,用于编写包含以下主题的新课程:
这些是与构造函数相关的点,整个清单可能更长。过了一会儿,你会自动开始做这样的事情。
就我个人而言,我认为单元测试是避免特定问题的最佳答案,正如您已经提到的那样。
编辑:从评论中添加了想法:
在某些开发环境中,您可以使用代码模板或宏为您生成正确的类骨架。这是一个真正的“程序员解决方案” - 自动化所有可以自动化以避免错误的内容。
答案 3 :(得分:2)
这应该不是什么大问题。构造函数没有返回值(甚至没有void),因此编译器应该能够判断某些东西是构造函数还是成员函数,并告诉您是否存在问题。显然有人看到编译器的错误并选择了错误的解决方案(添加返回类型而不是纠正拼写)。 (编辑:对不起,没意识到你自己就是这么做的。)
就个人而言,我总是将构造函数放在类的公共部分的开头附近(在任何成员函数之前,但是在公共类型定义和常量之后),所以这样的问题对我来说非常明显。 - 阅读代码。但惯例有所不同。
答案 4 :(得分:1)
我很确定静态代码分析工具PC Lint会发现这个错误。它不是免费的,但非常非常好。值得一看:
答案 5 :(得分:1)
它适用于构造函数,但也适用于任何其他方法,甚至函数。例如:
class MyBase
{
// etc.
virtual void doSomething() ;
} ;
class MyDerived : public MyBase
{
// etc.
virtual void DoSomething() ;
} ;
或者:
bool isOk(int value) { /* etc. */ }
bool isOK(double value) { /* etc. */ }
void doSomething(int value)
{
if(isOK(value)) // isOK(double) will be called
{
// Etc.
}
}
这不仅仅是个性问题。发生这种错误。像自动完成这样的IDE帮助程序可以有所帮助,就像一个好的单元测试代码(覆盖类的所有方法的东西)一样,但我不相信单独的编译器可以防止这种开发人员的错误输入,即使使用其他关键字也是如此。
至于在我之前提到的CONSTRUCTOR定义,这是一个坏主意恕我直言:
它会像注释一样多(所以,为什么不使用/ * CONSTRUCTOR * /代替?),如果有人想到使用CONSTRUCTOR这个词作为定义符号,那么你可以打赌别人会有你包含但不拥有的某些标题中的相同想法,它会破坏你的代码...
答案 6 :(得分:1)
如果成员变量不会改变,则将其声明为const,编译器将强制您使用初始化列表,在这种情况下会强制您检测错误(因为它不会编译)。
答案 7 :(得分:0)
也许你可以做一个
#define CONSTRUCTOR
然后在你的代码中
class MyAPIHandler
{
public:
CONSTRUCTOR MyAPIHandler()
{
// Deep magic
}
};
现在,它本身不会做任何事情,但如果你习惯这样写,你应该能够更容易地发现错误。
虽然我真的认为这是一次罕见的事情,但不值得这么麻烦。
答案 8 :(得分:0)
我想跟随TDD会有所帮助,虽然不是你想要的解决方案......
正如您所说的遗留代码,我建议您阅读“使用遗留代码 - Kent Beck”。这可能有所帮助。
回归测试会因错误修复而选择问题。
即使我期待开箱即用的建议:)
答案 9 :(得分:0)
单元测试。
MyAPIHandler mah;
BOOST_CHECK_EQUAL(mah.handle, 42);
答案 10 :(得分:0)
我有一套肉鸡板/模板(在Vim中......但我想你可以在任何现代编辑器中使用它们),这可以避免这种性质的拼写错误。
答案 11 :(得分:0)
大多数IDE允许某种宏。只需使用宏来创建类定义。这就是我设置它的方式。我的宏打印输出:
class CHANGETHIS
{
public:
CHANGETHIS();
~CHANGETHIS();
}
#error "Finish this class definition"
#error行只是一个安全网,如果我在工作中被打断并且在我回来时忘记它,那么确保它不会构建。
我喜欢这个,它可以在任何编辑器中移植,所以当我切换到不同的IDE时,我感到不适应。希望这会有所帮助。
此外,使用初始化列表。它们更快,导致更少的错误。
答案 12 :(得分:0)
我认为避免这个问题的最简单方法是以编码风格指南的形式规定命名法则。在此,您可以设置如何命名实体,并坚持下去。
特别是在工作中,我们强制要求缩写仅在第一个字母上大写,因此这将更容易被捕获。