你如何使错误的代码看起来不对? 你使用什么模式来避免语义错误?
自从我第一次犯了错误, if
我总是写这样的ifs:
if (CONST == variable) {
避免常见(至less对我来说)这样做的错误:
if (variable = CONST) { //WRONG, assigning 0 to variable
而且自从我读了乔尔·斯波尔斯基(Joel Spolsky)的文章“ 使错误的代码看起来错了”以来,我一直在试图将他的build议付诸实践。
那么,如果你犯了一个语义错误,你还会使用其他什么模式来使错误的代码看起来错误,或者强制语法错误?
我发现编译器错误的代码看起来很重要。 在实践中(并且只有在使用强types语言时),这意味着省略任何types的variables前缀(甚至是应用程序匈牙利语)而偏向于不同的types。 要使用Joel的示例,如果有两种不同的types来表示原始string和已消毒的string,并且两者之间没有隐式转换,则应用程序匈牙利地址甚至不能发生问题。
Word文档坐标也是一样的。 从某种意义上说,Apps Hungarian只是对没有足够严格的types检查的编译器/语言的解决方法。
我使用的一种做法(不是每个人都会同意的做法) 总是围绕着用{和}在C ++中的代码块。 所以,而不是这个:
if( true ) DoSomething(); else DoSomethingElse();
我会写这个:
if( true ) { DoSomething(); } else { DoSomethingElse(); }
这样,如果我(或其他人)稍后再回到此代码以将其他代码添加到其中一个分支,则不必担心忘记将代码括在花括号中。 我们的眼睛会直观地看到缩进是我们要做的事情的线索,但大多数语言不会。
有一件事我尽量使用,如果可能的话。 是为了避免使用布尔types作为参数的function,特别是如果有多个参数。
哪个更可读…
compare("Some text", "Some other text", true);
…要么…
compare("Some text", "Some other text", Compare.CASE_INSENSITIVE);
无可否认,这可能会有点矫枉过正,但是设置起来不难,提高了可读性,减less了作者错误地记住“真”意味着“是的,做大小写比较”还是“是的,以不区分大小写的方式进行比较“。
当然,例如…
setCaseInsenstive(true);
…简单而明显,可以独处。
如果没有理由在初始化后改变这个值,那么总是声明variables是“const”(或者在你的编程语言中是等价的)。
如果你习惯这样做,那么只要你看到一个非constvariables,你最终就会开始质疑。
@ Matt Dillard :
防御性编程的另一个措施是在每个开关代码块中总是使用一个break语句(即不要让case语句“通过”到下一个语句)。 唯一的例外是如果多个case语句被同样地处理。
有时处理案例X和案例Y意味着做几乎相同的事情,在这种情况下,下一个案例使代码更容易阅读。 尽pipe具体指出,但有一个评论:
switch( type ) { case TypeA: do some stuff specific to type A // FALL THROUGH case TypeB: do some stuff that applies to both A and B break case TypeC: ... }
如果你使用这个约定,那么所有的case语句都应该有一个break,一个return,一个continue或者一个表示它正在经历的注释。 但是,没有注释的空白案例可以:
case TypeA: case TypeB: // code for both types
与原始示例相同的是这个string字面比较技巧。 如果将string引用variables与string字面值进行比较,则可能会引发NullPointerException
。
if(variable.equals("literal")) { // NullPointerExceptionpossible ... }
你可以避免这种可能性,如果你翻转的东西,并把字面先。
if("literal".equals(variable)) { // avoids NullPointerException ... }
@Zack:
所以,你是说,而不是使用前缀命名约定来创build和总是使用两个新的类:SafeString和UnsafeString?
听起来对我来说是一个更好的select。 编译错误比运行时错误要好得多。
究竟。 布鲁斯·埃克尔(Bruce Eckel)写了一篇论文,认为静态打字是多余的,因为嘿,你正在编写testing用例,对不对? 错误。 当然,我正在编写testing用例,但编写好的testing用例很困难,而且还有很多工作要做。 尽可能多的获得帮助,编译时types检查几乎是最好的帮助。 此外,使用testing时,即使在使用自动化testing签入过程时,失败的testing也会比编译时错误晚得多,从而导致错误纠正延迟。 编译器可以给出更直接的反馈。
这并不是说我没有看到解释型语言的优点,但是dynamicinput可能是一个巨大的缺点。 我实际上对没有静态types的现代解释语言感到失望,因为正如Joel所表明的那样,这使得编写正确的代码变得更加困难,迫使我们采用像匈牙利语应用程序这样的二stream黑客。
嘿,我打了一半,想知道这是否真的有用,但是因为马特和格雷姆都已经发布了关于这个问题的答案,我会继续。
前几天,在给交换机增加一个新的情况下,我忘了结束这个情况。 一旦我发现错误,我改变了我的switch语句的缩进:
switch(var) { case CONST1: statement; statement; statement; break; case CONST2: statement; statement; statement; case CONST3: statement; statement; break; default: statement; }
(这是如何猜测大多数人通常会缩进):
switch(var) { case CONST1: statement; statement; statement; break; case CONST2: statement; statement; statement; case CONST3: statement; statement; break; default: statement; }
为了让缺失的突破脱颖而出,并让我更可能不会忘记添加一个,当我添加一个新的案例。 (当然,如果你有条件地在不止一个地方有条件的话,你不能这样做)
如果我只是做一些小事,比如设置一个variables或从case语句中调用函数,那么我经常会像这样构造它们:
switch(var) { case CONST1: func1(); break; case CONST2: func2(); break; case CONST3: func3(); break; default: statement; }
如果你想rest一下,那就太好了。 如果你的陈述不是相同的长度,加上空格直到中断alignment,以及其他任何有意义的东西:
switch(var) { case CONST1: func1("Wibble", 2); break; case CONST2: longnamedfunc2("foo" , 3); break; case CONST3: variable = 2; break; default: statement; }
虽然如果我将相同的parameter passing给每个函数,我会使用一个函数指针(下面是来自工作项目的实际代码):
short (*fnExec) ( long nCmdId , long * pnEnt , short vmhDigitise , short vmhToolpath , int *pcLines , char ***prgszNCCode , map<string, double> *pmpstrd ) = NULL; switch(nNoun) { case NOUN_PROBE_FEED: fnExec = &ExecProbeFeed; break; case NOUN_PROBE_ARC: fnExec = &ExecProbeArc; break; case NOUN_PROBE_SURFACE: fnExec = &ExecProbeSurface; break; case NOUN_PROBE_WEB_POCKET: fnExec = &ExecProbeWebPocket; break; default: ASSERT(FALSE); } nRet = (*fnExec)(nCmdId, &nEnt, vmhDigitise, vmhToolpath, &cLines, &rgszNCCode, &mpstrd);
在我的工程应用中,我将测量单位和参考框架作为variables名称的一部分。 这样我可以很容易地发现矛盾。 例子:
r1_m = r2_ft; //wrong, units are inconsistent (meters vs feet) V1_Fc = V2_Fn; //wrong, reference frames are inconsistent //(Fc=Local Cartesian frame, Fn=North East Down frame)
Konrad Rudolph写道:
应用程序匈牙利语只是对没有严格的types检查的编译器/语言的解决方法。
所以,你是说,而不是使用前缀命名约定来创build和总是使用两个新的类:SafeString和UnsafeString?
听起来对我来说是一个更好的select。 编译错误比运行时错误要好得多。
我总是在我的代码中使用大括号。
尽pipe可以写下:
while(true) print("I'm in a loop")
用圆括号来阅读是比较容易的。
while(true){ print("Obviously I'm in a loop") }
我认为这主要来自Java作为我的第一语言。
击败了一拳
防御性编程的另一个措施是在每个switch
代码块中总是使用一个break
语句(即不要让case
语句“通过”到下一个语句)。 唯一的例外是如果多个case语句被同样地处理。
switch( myValue ) { case 0: // good... break; case 1: case 2: // good... break; case 3: // oops, no break... SomeCode(); case 4: MoreCode(); // WARNING! This is executed if myValue == 3 break; }
有时候这可能是期望的行为,但是从代码可读性的angular度来看,我认为重构“共享”代码以防止这种模糊性会更好。
如果编程语言允许,只在初始化它们的地方声明variables。 (换句话说,不要在每个函数的顶部声明它们)。如果你在同一个地方一致地声明和初始化,那么你有更less的机会有未初始化的variables。
避免嵌套循环,避免缩进代码超过几个级别。
通常可以通过将嵌套代码提取到函数/方法来重构嵌套循环或深度嵌套代码。 这通常会使代码更容易推理。
我书中最好的东西就是减less噪音。 这就是为什么我喜欢把exception分开的原因,这是一个很好的例子(确保处理错误的情况不是内联的)。
如果噪声降低了,那么您所查看的代码只是执行方法/函数名称所表示的特定目的,这使得识别错误代码变得更容易。
我玩过(0 ==variables)技巧,但是可读性有所损失 – 你必须在脑海中将事物转换为“如果variables等于零”。
我第二次马特·迪拉德(Matt Dillard)推荐围绕单线条件加上括号。 (如果可以的话,我会把它投票!)
当性能不重要时我使用另一个技巧:我将定义
void MyClass::DoNothing() { }
并用它来代替空的语句。 一个简单的分号很容易丢失。 可以像这样添加数字1到10(并将其存储在总和中):
for (i = 1; i <= 10; sum += i++) ; //empty loop body
但这是更可读和自我loggingIMO:
for (i = 1; i <= 10; sum += i++) { DoNothing(); }
不要使用只有1或2个字符的variables名称。 不要使用非常短的variables名称。 (除了循环。)
尽可能less使用全局variables。 当我把“int i”当成全局的时候真的是一个大问题(是的,我在一个真实的项目中看到过类似的东西:))
总是使用括号。
以下是来自Juval Lowy关于如何设置代码样式(C#)的非常好的阅读。 你可以在这里find它: http : //www.idesign.net/在右侧的“资源”
或者这里是一个直接的链接(这是一个压缩的PDF): http : //www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip
IDesign C#编码标准,用于Juval Lowy的开发指南和最佳实践
目录:
前言
1.命名约定和风格
2.编码实践
3.项目设置和项目结构
4.框架具体指导原则
– 4.1数据访问
4.2 ASP.NET和Web服务
– 4.3multithreading
– 4.4序列化
– 4.5远程
– 4.6安全
4.7 System.Transactions
– 4.8企业服务
5.资源
使用一点匈牙利语
标记variables可能会有所帮助。 例如,如果您正在清理用户input,您可能会这样做:
$username = santize($rawusername);
这样,如果你要说echo $rawusername
,它会看起来不对,因为它是。
当在JS中定义数组或对象(和其他语言中的模拟)时,我把值放在值的前面。 这是来自多次修改数组/对象,并由于缺less语法错误,
– 如果行很长很容易忘记。
oPerson = { nAge: 18 ,sFirstName: "Daniel" ,sAddress: "..." ,... }
类似于长线的串接,我把+
放在前面而不是末端“
sLongString = "..........................................................." + ".........................................................." + ".............." + "";
如果你正在玩HTML的土地,尝试获得validation的代码 – 有几次来自HTMLvalidation器插件的红色小x给了我一个方便的捷径,以解决我还没有注意到的问题。
获得有效的HTML可能并不总是可能的,但通常值得一试。