我参与了开发非常精细的编码标准.我自己的经验是,如果你没有适当的流程来维护它和维护它的策略,就很难执行.
现在,我正在努力并领导一个环境,甚至可能在很长一段时间内拥有流程和后续策略.我仍然希望维持一些最低级别的可敬代码.所以我认为我会在这里得到很好的建议,我们可以共同制作一个合理的轻量级子集,其中最重要的编码标准实践供其他人用作参考.
所以,要强调这里的本质:
每个答案1个候选人,最好有短暂的动机.
投票选出专注于风格和主观格式指南的候选人.这并不是说它们不重要,只是说它们在这种背景下不太相关.
投票给关注如何评论/记录代码的候选人.这是一个更大的主题,甚至可能值得拥有自己的职位.
投票选出明显有助于更安全代码的候选人,从而最大限度地降低神秘错误的风险,从而提高可维护性等.
不要向你不确定的候选人投票.即使它们听起来合理而聪明,或相反"肯定没有人会使用",你的投票应该基于清晰的理解和经验.
Aardvark.. 68
喜欢RAII.
STL的auto(以及boost和C++ 0x中的共享)指针可能有所帮助.
喜欢RAII.
STL的auto(以及boost和C++ 0x中的共享)指针可能有所帮助.
const
默认使用标识符.它们为读者/维护者提供了保证,并且比以后插入更容易构建.
将声明成员变量和方法const
,以及函数参数. const
成员变量强制正确使用初始化列表.
这条规则的副作用:避免有副作用的方法.
使用:
static_cast
const_cast
reinterpret_cast
dynamic_cast
但从来没有C风格的演员.
它如何明确地促进更安全的代码,最大限度地减少了神秘错误的风险,从而提高了可维护性等.
每个演员阵容都有限.例如,如果你想删除一个const(无论出于何种原因),const_cast
不会同时改变类型(这可能是一个很难找到的bug).
此外,这使得审阅者能够搜索它们,然后编码器在需要时为其辩护.
尽可能使用引用而不是指针.这可以防止持续的防御性NULL检查.
确保编译器的警告级别设置得足够高(最好是/ Wall),以便它可以捕获如下的愚蠢错误:
if (p = 0)
当你真正的意思
if (p == 0)
所以你不需要采取甚至更愚蠢的技巧:
if (0 == p)
这会降低代码的可读性.
无论何时需要创建数据缓冲区,都要使用std :: vector,即使大小是固定的.
每当需要字符串时,请使用std :: string.
它如何明确地促进更安全的代码,最大限度地减少神秘错误的风险,从而提高可维护性等?
std :: vector:向量的用户总能找到它的大小,如果需要,可以调整向量的大小.它甚至可以(通过(&(myVector [0]))表示法给出一个C API.当然,矢量会自行清理.
std :: string:几乎与上面相同的原因.事实上它将始终被正确初始化,它不能被溢出,它将优雅地处理修改,如连接,赋值等,并以自然的方式(使用运营商而不是功能)
将功能保持在合理的大小.就个人而言,我喜欢将功能保持在25行以下.当您可以将功能作为一个单元进行操作而不必上下扫描时,可读性得到增强,并试图弄清楚它是如何工作的.如果你必须滚动阅读它,那就更糟了.
断言所有假设,包括临时假设,如未实现的行为.如果不重要,则断言函数进入和退出条件.断言所有重要的中间状态.如果没有断言失败,你的程序永远不会崩溃.您可以自定义断言机制以忽略未来的发生.
对预期发生的情况使用错误处理代码; 将断言用于永不发生的条件.错误处理通常会检查错误的输入数据; 断言检查代码中的错误.
如果使用错误处理代码来解决异常情况,则错误处理将使程序能够正常响应错误.如果针对异常情况触发了断言,则纠正措施不仅仅是优雅地处理错误 - 纠正措施是更改程序的源代码,重新编译和发布新版本的软件.思考断言的好方法是作为可执行文档 - 您不能依赖它们来使代码工作,但它们可以比程序语言注释更积极地记录假设[1].
麦康奈尔,史蒂夫.代码完成,第二版.Microsoft Press©2004.第8章 - 防御性编程
尽可能在堆栈上创建对象(没用无用的新东西)
除非确实需要,否则不要转让所有权
使用RAII和智能指针
如果要求转让所有权(没有智能指针),那么,清楚地记录代码(函数应该有一个非模糊的名称,总是使用相同的名称模式,如"char*allocateMyString()"和"void deallocateMyString(char)*p)".
它如何明确地促进更安全的代码,最大限度地减少神秘错误的风险,从而提高可维护性等?
没有明确的内存所有权原则导致有趣的错误或内存泄漏,并且时间丢失,想知道此函数返回的char*是否应由用户释放,或者是否返回特殊的释放函数等.
分配内存的函数/对象必须尽可能地取消分配它的函数/对象.
附注:不要施加SESE(单入口单出口)(即不要禁止多于一个return
,使用break
/ continue
/ ......)
在C++中,这是一个乌托邦,throw
也是另一个回归点.SESE在C和无异常语言方面有两个优点:
现在由C++中的RAII习语巧妙地处理资源的确定性释放,
使功能更容易维护,这不应该是一个问题,因为功能必须保持简短(由"一个功能,一个责任"的规则指定)
首先编写安全且正确的代码.
然后,如果您遇到性能问题,并且如果您的探查器告诉您代码很慢,您可以尝试对其进行优化.
永远不要相信你会比编译器更好地优化代码片段.
在寻找优化时,研究所使用的算法,以及可能更好的替代方案.
它如何明确地促进更安全的代码,最大限度地减少神秘错误的风险,从而提高可维护性等?
通常,"优化"(或假设优化)的代码不那么清晰,并且倾向于通过原始的,近乎机器的方式表达自己,而不是更加面向业务的方式.一些优化依赖于切换,if等等,然后由于多个代码路径而更难以测试.
当然,在分析之前进行优化通常会导致零性能提升.
任何控制语句的大括号.(感谢自己的经验并通过阅读Code Complete v2加强):
// bad example - what the writer wrote if( i < 0 ) printf( "%d\n", i ); ++i; // this error is _very_ easy to overlook! // good example - what the writer meant if( i < 0 ) { printf( "%d\n", i ); ++i; }
首选符合标准的代码.更喜欢使用标准库.
只是琐碎的使用?:运营商,即
float x = (y > 3) ? 1.0f : -1.0f;
没问题,但这不是:
float x = foo(2 * ((y > 3) ? a : b) - 1);
使用棉绒工具 - 即PC-Lint.这将捕获许多"结构"编码指南问题.意思是读取实际错误而不是样式/可读性问题的东西.(并不是说可读性不重要,但它不如实际错误那么重要).
例如,而不是要求这种风格:
if (5 == variable)
作为一种防止"意外分配"错误的方法,让lint找到它.
结构体是合法的C++结构,用于将数据聚合在一起.但是,数据应始终正确初始化.
所有C++结构应该至少有一个默认构造函数,它将聚合数据设置为默认值.
struct MyStruct // BAD { int i ; bool j ; char * k ; } struct MyStruct // GOOD { MyStruct() : i(0), j(true), k(NULL) : {} int i ; bool j ; char * k ; }
如果它们通常以某种方式初始化,则提供构造函数以使用户能够避免C样式的结构初始化:
MyStruct oMyStruct = { 25, true, "Hello" } ; // BAD MyStruct oMyStruct(25, true, "Hello") ; // GOOD
它如何明确地促进更安全的代码,最大限度地减少神秘错误的风险,从而提高可维护性等?
拥有没有适当构造函数的struct会让这个struct的用户完成初始化它的任务.因此,以下代码将从函数复制粘贴到函数:
void doSomething() { MyStruct s = { 25, true, "Hello" } ; // Etc. } void doSomethingElse() { MyStruct s = { 25, true, "Hello" } ; // Etc. } // Etc.
这意味着,在C++中,如果需要在结构中添加字段或更改内部数据的顺序,则必须完成所有这些初始化以验证每个字段仍然正确.使用适当的构造函数,修改结构的内部与其使用分离.
不要向全局命名空间添加类型或函数.
最不惊讶的原则.
也许这不是你正在寻找的规则的"味道",但我绝对会把它放在首位.
它不仅是对格式化和评论指南等所有无聊内容的根本原因和理智检查,而且 - 对我来说更重要的是 - 强调对代码的读取和理解,而不仅仅是编译.
它还涵盖了我遇到的唯一合理的代码质量测量 - 每分钟WTF.
我将使用第一点强调清晰,一致的代码的重要性和价值,并激励编码标准中的以下项目.
禁止t[i]=i++;
f(i++,i);
,等等,因为没有(便携式)保证首先执行什么.
始终,始终始终在对象构造上进行正确的数据成员初始化.
我遇到了一个问题,其中一个对象构造函数依赖于其数据成员的某些"默认"初始化.在两个平台(Windows/Linux)下构建代码会产生不同的结果和难以发现的内存错误.结果是数据成员未在构造函数中初始化,并在初始化之前使用.在一个平台(Linux)上,编译器将其初始化为代码编写者认为合适的默认值.在Windows上,值被初始化为某些东西 - 但是垃圾.在使用数据成员时,一切都变得混乱.一旦初始化得到修复 - 没有更多问题.