JAN*_*JAN 313 c c++ defensive-programming coding-style curly-braces
我正在阅读我的C++讲师的一些讲义,他写了以下内容:
- 使用缩进//确定
- 永远不要依赖运算符优先级 - 始终使用括号//确定
- 总是使用{}块 - 即使是单行// 不行,为什么???
- 比较左侧的Const对象// OK
- 对于> = 0 //好玩法的变量使用无符号
- 删除后将指针设置为NULL - 双删除保护//不错
第三种技术对我来说并不清楚:通过在一条线中放置一条线可以获得{ ... }
什么?
例如,拿这个奇怪的代码:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
}
Run Code Online (Sandbox Code Playgroud)
并替换为:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
Run Code Online (Sandbox Code Playgroud)
使用第一个版本有什么好处?
Luc*_*ore 505
让i
我们在增加时尝试修改j
:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++;
Run Code Online (Sandbox Code Playgroud)
不好了!来自Python,这看起来不错,但实际上并非如此,因为它相当于:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++;
Run Code Online (Sandbox Code Playgroud)
当然,这是一个愚蠢的错误,但即使是有经验的程序员也可以做出错误.
在ta.speot.is的回答中指出了另一个很好的理由.
我能想到的第三个是嵌套if
的:
if (cond1)
if (cond2)
doSomething();
Run Code Online (Sandbox Code Playgroud)
现在,假设你现在想要doSomethingElse()
什么时候cond1
不满足(新功能).所以:
if (cond1)
if (cond2)
doSomething();
else
doSomethingElse();
Run Code Online (Sandbox Code Playgroud)
这显然是错误的,因为else
与内在联系在一起if
.
编辑:由于这引起了一些关注,我将澄清我的观点.我回答的问题是:
使用第一个版本有什么好处?
我已经描述过了.有一些好处.但是,IMO,"永远"规则并不总是适用.所以我不完全支持
总是使用{}块 - 即使是单行//不行,为什么???
我不是说总是使用一个{}
块.如果这是一个简单的条件和行为,不要.如果您怀疑有人可能会稍后进入并更改您的代码以添加功能,请执行此操作.
ta.*_*.is 320
如果你不使用{
和,很容易用注释意外改变控制流}
.例如:
if (condition)
do_something();
else
do_something_else();
must_always_do_this();
Run Code Online (Sandbox Code Playgroud)
如果您do_something_else()
使用单行评论进行评论,则最终会得到以下结果:
if (condition)
do_something();
else
//do_something_else();
must_always_do_this();
Run Code Online (Sandbox Code Playgroud)
它编译,但must_always_do_this()
并不总是被调用.
我们在代码库中遇到了这个问题,有人在发布之前很快就禁用了某些功能.幸运的是,我们在代码审查中发现了它.
Jam*_*nze 59
我对讲师的能力表示怀疑.考虑他的观点:
(b*b) - ((4*a)*c)
?一些优先级是显而易见的(或应该是),而额外的括号只会增加混乱.(另一方面,即使您知道不需要它们,您也应该在不太明显的情况下使用括号.)
if ( cond ) { code; }和:
if ( cond ) { code; }首先,我同意他的看法.开口
{
不是那么明显,所以最好假设它始终在那里.然而,在第二个中,我(和我一起工作的大多数人)在单个语句中省略大括号没有问题.(当然,前提是缩进是系统化的,并且你一直使用这种风格.(和许多非常优秀的程序员一样,编写非常易读的代码,即使在格式化第一种方式时也省略了大括号.)
if ( NULL == ptr )
丑陋的东西足以阻碍可读性.直观地写下比较.(在许多情况下导致右边的常数.)他的4是不好的建议; 任何使代码不自然的东西都会降低其可读性.
int
保留特殊情况.对于有经验的C和C++程序员,使用unsigned
信号位运算符.C++没有真正的基数类型(或任何其他有效的子类型); unsigned
由于促销规则,对数值不起作用.没有算术运算有意义的数值,如序列号,可能是unsigned
.但是,我反对它,因为它发出了错误的信息:按位操作也没有意义.基本规则是整数类型int
,_unless_使用其他类型的重要原因.
delete this;
往往是最常见的情况(而不能设置this
到NULL
),否则,大多数delete
都在析构函数,所以你不能访问指针后反正.并将其设置为对NULL
浮动的任何其他指针没有任何作用.系统地设置指针NULL
给出了一种虚假的安全感,并没有真正为你买任何东西.
查看任何典型参考文献中的代码.例如,Stroustrup违反了你给出的所有规则,除了第一个规则.
我建议你找另一位讲师.一个谁知道他在说什么.
Kon*_*lph 46
所有其他答案都捍卫了你的讲师规则3.
让我说我同意你的观点:这条规则是多余的,我不会建议.确实,如果你总是添加花括号,它理论上可以防止错误.另一方面,我从未在现实生活中遇到过这个问题:与其他答案所暗示的相反,我不会忘记在必要时添加大括号.如果你使用适当的缩进,很明显你需要在多个语句缩进时添加大括号.
"组件10"的答案实际上突出了唯一可以想象的情况,这可能真的导致错误.但另一方面,通过正则表达式替换代码总是需要非常小心.
现在让我们来看看奖牌的另一面:总是使用花括号是否有缺点?其他答案完全忽略了这一点.但是,是一个缺点:它占用了大量的垂直屏幕空间,而这反过来又可以让你的代码不可读,因为这意味着你必须滚动超过必要的.
考虑一个开头有很多保护条款的函数(是的,以下是糟糕的C++代码,但在其他语言中,这将是一种非常常见的情况):
void some_method(obj* a, obj* b)
{
if (a == nullptr)
{
throw null_ptr_error("a");
}
if (b == nullptr)
{
throw null_ptr_error("b");
}
if (a == b)
{
throw logic_error("Cannot do method on identical objects");
}
if (not a->precondition_met())
{
throw logic_error("Precondition for a not met");
}
a->do_something_with(b);
}
Run Code Online (Sandbox Code Playgroud)
这是一个可怕的代码,我坚决认为以下内容更具可读性:
void some_method(obj* a, obj* b)
{
if (a == nullptr)
throw null_ptr_error("a");
if (b == nullptr)
throw null_ptr_error("b");
if (a == b)
throw logic_error("Cannot do method on identical objects");
if (not a->precondition_met())
throw logic_error("Precondition for a not met");
a->do_something_with(b);
}
Run Code Online (Sandbox Code Playgroud)
类似地,短嵌套循环可以从省略大括号中获益:
matrix operator +(matrix const& a, matrix const& b) {
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
for (auto j = 0; j < a.h(); ++j)
c(i, j) = a(i, j) + b(i, j);
return c;
}
Run Code Online (Sandbox Code Playgroud)
与之比较:
matrix operator +(matrix const& a, matrix const& b) {
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
{
for (auto j = 0; j < a.h(); ++j)
{
c(i, j) = a(i, j) + b(i, j);
}
}
return c;
}
Run Code Online (Sandbox Code Playgroud)
第一个代码简洁; 第二个代码是膨胀的.
是的,通过将开口支撑放在前一行上可以在一定程度上减轻这种情况.但是,与没有任何大括号的代码相比,这仍然不太可读.
简而言之:不要编写占用屏幕空间的不必要代码.
jam*_*jam 39
我正在研究的代码库是由对病毒有病态厌恶的人分散的代码,而对于后来出现的人来说,它实际上可以对可维护性产生影响.
我遇到的最常见的问题是:
if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
this_looks_like_a_then-statement_but_isn't;
Run Code Online (Sandbox Code Playgroud)
因此,当我出现并希望添加一个声明时,如果我不小心,我可以很容易地结束这个:
if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
this_looks_like_a_then-statement_but_isn't;
i_want_this_to_be_a_then-statement_but_it's_not;
}
Run Code Online (Sandbox Code Playgroud)
鉴于添加大括号需要大约1秒,并且可以在最少的几分钟调试时节省您的时间,为什么你不会使用简化模糊选项?对我来说似乎是虚假的经济.
Nem*_*vic 19
我的2c:
使用缩进
明显
永远不要依赖运算符优先级 - 始终使用括号
我不会使用"never and"always"这个词,但总的来说,我认为这个规则很有用.在某些语言中(Lisp,Smalltalk),这不是问题.
始终使用{}块 - 即使是一行
我从来没有这样做,从来没有遇到任何问题,但我可以看到它对学生有什么好处,尤其是.如果他们之前研究过Python.
比较左侧的Const对象
尤达的条件?不谢谢.它伤害了可读性.只需在编译代码时使用最大警告级别.
对于> = 0的变量使用无符号
好.有趣的是,我听说Stroustrup不同意.
删除后将指针设置为NULL - 双删除保护
不好的建议!永远不会有指向已删除或不存在的对象的指针.
Com*_* 10 13
为了增加上面非常合理的建议,我在重构一些代码时遇到的一个例子如下:我正在改变一个非常大的代码库来从一个API转换到另一个API.第一个API调用了如下设置公司ID:
setCompIds( const std::string& compId, const std::string& compSubId );
Run Code Online (Sandbox Code Playgroud)
而替换需要两个电话:
setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );
Run Code Online (Sandbox Code Playgroud)
我开始使用非常成功的正则表达式来改变它.我们还通过astyle传递了代码,这使得它更具可读性.然后,在审查过程的一部分,我发现在某些条件情况下它改变了这个:
if ( condition )
setCompIds( compId, compSubId );
Run Code Online (Sandbox Code Playgroud)
对此:
if ( condition )
setCompId( compId );
setCompSubId( compSubId );
Run Code Online (Sandbox Code Playgroud)
这显然不是什么要求.我不得不回到开头再做一次,将替换完全视为一个块,然后手动修改最终看起来很傻的东西(至少它不会是错误的.)
我注意到astyle现在有一个选项--add-brackets
,允许你在没有括号的地方添加括号,如果你发现自己处于和我相同的位置,我强烈推荐这个.
{}
除了少数显而易见的情况外,我到处都在使用.单行是其中一种情况:
if(condition) return; // OK
if(condition) //
return; // and this is not a one-liner
Run Code Online (Sandbox Code Playgroud)
在返回之前添加一些方法可能会对您造成伤害.缩进表示当满足条件时返回正在执行,但它将始终返回.
C#中使用statment的其他示例
using (D d = new D()) // OK
using (C c = new C(d))
{
c.UseLimitedResource();
}
Run Code Online (Sandbox Code Playgroud)
这相当于
using (D d = new D())
{
using (C c = new C(d))
{
c.UseLimitedResource();
}
}
Run Code Online (Sandbox Code Playgroud)
我能想到的最相关的例子:
if(someCondition)
if(someOtherCondition)
DoSomething();
else
DoSomethingElse();
Run Code Online (Sandbox Code Playgroud)
哪个if
会else
搭配?缩进意味着外部if
获取else
,但实际上并不是编译器将如何看到它; 该内 if
将得到else
和外if
没有.你必须知道(或者在调试模式下看到它的行为)通过检查来确定为什么这段代码可能会失败你的期望.如果你了解Python,它会变得更加混乱; 在这种情况下,您知道缩进定义了代码块,因此您希望它根据缩进进行评估.然而,C#并没有给出关于空白的飞行翻转.
现在,那就是说,我并不特别同意这个"总是使用括号"的规则.它使代码非常垂直嘈杂,降低了快速读取代码的能力.如果声明是:
if(someCondition)
DoSomething();
Run Code Online (Sandbox Code Playgroud)
......然后应该像这样写.声明"总是使用括号"听起来像"总是用括号括起数学运算".这会把非常简单的陈述a * b + c / d
变成((a * b) + (c / d))
,引入错过密码的可能性(许多编码员的祸根),以及为什么?操作顺序众所周知且执行得很好,因此括号是多余的.您只能使用括号来强制执行与通常应用的操作不同的操作顺序:a * (b+c) / d
例如.块状括号类似; 使用它们来定义你想要做的事情,如果它与默认值不同,并且不是"明显的"(主观的,但通常很常识).
通过回答没有人明确说明我习惯的那种练习,讲述你的代码的故事:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
}
Run Code Online (Sandbox Code Playgroud)
变为:
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0) j++;
}
Run Code Online (Sandbox Code Playgroud)
把j++
在同一行的,如果要发信号给别人,"我只希望这一块永远增加j
".coursethis是唯一有价值的,如果该行是尽可能简单,因为把一个断点在这里,因为周边提到,不会是非常有用的.
事实上,我刚刚遇到了部分Twitter Storm API,它在java中有这种"类型"代码,这里是执行代码的relvant片段,在本幻灯片的第43页上:
...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...
Run Code Online (Sandbox Code Playgroud)
for循环块有两个东西,所以我不会内联该代码.即从不:
int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;
Run Code Online (Sandbox Code Playgroud)
这很可怕,我甚至不知道它是否有效(按预期); 不要这样做.新的线条和大括号有助于区分单独但相关的代码片段,就像散文中的逗号或分号一样.上面的块是一个很长的句子,有一些条款和一些其他语句从不打破或暂停来区分不同的部分.
如果你真的想要给别人发电报它是一个单线工作,使用三元运算符或?:
形式:
for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;
Run Code Online (Sandbox Code Playgroud)
但这是代码高尔夫,我认为不是很好的练习(我不清楚我是否应该把j ++放在:
或不是一边).NB我之前没有在C++中运行三元运算符,我不知道这是否有效,但确实存在.
想象一下你的读者(即维护代码的人)如何解释你的故事(代码).让它们尽可能清楚.如果你知道新手编码器/学生正在维护这个,甚至{}
可能尽可能多地留下,这样他们就不会感到困惑.
因为当你没有两个陈述时{}
,很容易错过一个问题.我们假设代码看起来像这样.
int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();
if ((err = prepare_hash(hash, &hash_result))) != 0)
goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
goto fail;
goto fail;
if ((err = hash_finish(hash)) != 0)
goto fail;
error = do_important_stuff_with(hash);
fail:
hash_free(hash);
return error;
Run Code Online (Sandbox Code Playgroud)
看起来很好.它的问题很容易被遗漏,特别是当包含代码的函数更大时.问题是goto fail
无条件地运行.你很容易想象这是多么令人沮丧(让你问为什么最后hash_update
总是失败,毕竟一切看起来都很好hash_update
).
然而,这并不意味着我要在{}
任何地方添加(在我看来,{}
到处看都很烦人).虽然它可能会导致问题,但它从来没有为我自己的项目做过,因为我的个人编码风格禁止条件而{}
不是它们不在同一条线上(是的,我同意我的编码风格是非常规的,但我喜欢它,而我在为其他项目做贡献时使用项目的代码样式).这使得以下代码很好.
if (something) goto fail;
Run Code Online (Sandbox Code Playgroud)
但不是以下一个.
if (something)
goto fail;
Run Code Online (Sandbox Code Playgroud)