我们如何使错误的代码看起来不正确?我们使用什么模式来避免语义错误?

时间:2020-03-05 18:42:50  来源:igfitidea点击:

自从我第一次犯了在if中进行赋值的错误以来,我就总是这样写ifs:

if (CONST == variable) {

避免这样做的常见错误(至少对我而言):

if (variable = CONST) { //WRONG, assigning 0 to variable

自从我读了Joel Spolsky的论文《使错误的代码看起来错误》以来,我一直在尝试将他的建议付诸实践。

那么,我们还使用什么其他模式来使错误的代码看起来错误,或者如果我们犯了语义错误,则可以强制语法错误?

解决方案

回答

不要使用仅相差1或者2个字符的变量名。不要使用非常短的变量名。 (除了循环。)

尽可能少使用全局变量。当我们将" int i"作为一个全局对象时,这确实是一个大问题(是的,我在现实生活项目中已经看到过类似的内容:))

始终使用括号。

回答

我使用的一种做法(并不是所有人都不会同意的一种做法)总是在代码块(在C ++中)中使用{和}。所以代替这个:

if( true )
    DoSomething();
else
    DoSomethingElse();

我会这样写:

if( true ) {
    DoSomething();
} else {
    DoSomethingElse();
}

这样,如果我(或者其他人)稍后返回此代码以将更多代码添加到分支之一,则不必担心忘记将代码括在花括号中。我们的眼睛会在视觉上看到缩进作为我们正在尝试做的线索,但大多数语言不会。

回答

我总是在代码中使用花括号。

虽然可以这样写:

while(true)
   print("I'm in a loop")

括号清晰易读。

while(true){
   print("Obviously I'm in a loop")
}

我认为这主要是因为Java是我的第一语言。

打败

回答

与原始示例相同的地方是此字符串文字比较技巧。如果将字符串引用变量与字符串文字进行比较,则有可能抛出NullPointerException

if(variable.equals("literal")) { // NullPointerExceptionpossible
    ...
}

如果我们将内容翻转过来并放在原先的位置,则可以避免这种可能性。

if("literal".equals(variable)) { // avoids NullPointerException
    ...
}

回答

我发现使错误的代码在编译器中看起来很错误很重要。在实践中(仅在使用强类型语言时),这意味着将省略任何类型的变量前缀(甚至是Apps Hungarian),而应使用不同的类型。以Joel的示例为例,如果有两种不同的类型来表示原始字符串和经过净化的字符串,并且两者之间没有隐式转换,那么Apps Hungarian地址甚至不会发生。

Word文档坐标也是如此。在某种程度上,Apps Hungarian只是对没有足够严格的类型检查的编译器/语言的一种解决方法。

回答

以下是Juval Lowy撰写的有关如何设置代码样式(C#)的很好的读物。我们可以在这里找到它:http://www.idesign.net/在"资源"下的右侧

或者直接链接(压缩的PDF):http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip

The IDesign C# Coding Standard, for development guidelines and best practices by Juval Lowy
  
  Table of Contents:
  
  Preface

  1. Naming Conventions and Style

  2. Coding Practices

  3. Project Settings and Project Structure

  4. Framework Specific Guidelines

  - 4.1 Data Access

  - 4.2 ASP.NET and Web Services

  - 4.3 Multithreading

  - 4.4 Serialization

  - 4.5 Remoting

  - 4.6 Security

  - 4.7 System.Transactions

  - 4.8 Enterprise Services

  5. Resources

回答

Konrad Rudolph wrote:
  
  Apps Hungarian is only a workaround
  for compilers/languages that have no
  strict enough type checking.

因此,我们是在说而不是使用前缀命名约定来创建并始终使用两个新类:SafeString和UnsafeString?

听起来对我来说是一个更好的选择。编译错误比运行时错误要好得多。

回答

如果我们在HTML领域玩游戏,请尝试获取验证代码,HTML验证程序插件中的小红色x几次给了我一个方便的快捷方式来解决我什至没有注意到的问题。

不一定总能获得有效的HTML,但通常值得一试。

回答

防御性编程的另一种措施是始终在每个" 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;
}

有时候这可能是理想的行为,但是从代码可读性的角度来看,我认为最好重构"共享"代码以防止这种歧义。

回答

@Zack:

So, you're saying that rather than using a prefix naming convention to instead create and always use two new classes: SafeString and UnsafeString?
  
  Sounds like a much better choice to me. Compile errors are much better than run-time errors.

确切地。布鲁斯·埃克尔(Bruce Eckel)撰写了一篇文章,指出静态类型是多余的,因为,嘿,我们还是在编写测​​试用例,对吗?错误的。当然,我在写测试用例,但是编写好的测试用例很辛苦并且需要很多工作。最好获得所有帮助,并且编译时类型检查几乎是我们可以获得的最佳帮助。此外,在使用测试时,即使使用自动签入测试过程,也要比编译时错误晚得多地通知失败的测试,从而导致错误纠正延迟。编译器可以给出更直接的反馈。

这并不是说我看不到解释语言的优点吗?我知道,但是动态类型可能是一个巨大的缺点。我实际上很失望,因为没有静态类型的现代解释语言,因为正如Joel所展示的那样,这使得编写正确的代码变得更加困难,并迫使我们诉诸于诸如Apps Hungarian之类的二等黑客。

回答

@马特·迪拉德(Matt Dillard):

One other measure of defensive programming is always to use a break statement in each switch code block (i.e. do not let a case statement "fall through" to the next one). The only exception is if multiple case statements are to be handled identically.

有时处理案例X和案例Y意味着做几乎相同的事情,在这种情况下,进入下一个案例会使代码更易于阅读。不过,最好在注释中特别指出:

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

回答

如果编程语言允许,请仅在初始化变量时声明变量。 (换句话说,不要在每个函数的顶部都声明它们。)如果在同一位置一致地声明和初始化,则拥有未初始化变量的机会要少得多。

回答

如果初始化后没有理由更改变量,则始终将变量声明为" const"(或者编程语言中的等效变量)。

如果我们养成这种习惯,那么每当看到非常量变量时​​,我们最终都会开始质疑。

回答

避免嵌套循环,并避免缩进代码超过两个层次。

通常可以通过将嵌套代码提取到函数/方法中来重构嵌套循环或者深度嵌套的代码。这通常使代码更易于推理。

回答

嘿,我已经输入了一半,想知道它是否真的有用,但是由于Matt和Graeme都已经发布了有关此问题的答案,因此我将继续。

几天前,在向交换机添加新案例时,我忘了以休息结束案例。找到错误后,我将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;
}

为了使丢失的中断更加突出,并使我更有可能在添加新案件时不要忘记添加一个。 (当然,如果我们有条件地在一个以上的位置中断,我们将无法执行此操作,而我有时会这样做)

如果我只是做一些琐碎的事情,例如设置变量或者从case语句中调用函数,那么我通常会像这样构造它们:

switch(var) {
   case CONST1: func1();  break;  
   case CONST2: func2();  break;  
   case CONST3: func3();  break;  
   default: statement;
}

如果我们错过休息,那将变得非常明显。如果语句长度不一样,请添加空格,直到断行对齐为止,并添加其他有意义的内容:

switch(var) {
   case CONST1:          func1("Wibble", 2);  break;  
   case CONST2: longnamedfunc2("foo"   , 3);  break;  
   case CONST3: variable = 2;                 break;  
   default: statement;
}

尽管如果我将相同的参数传递给每个函数,我将使用函数指针(以下是工作项目中的实际代码):

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);

回答

我书中最好的事情就是减少噪音。这就是为什么我喜欢将关注点分离,例外是一个很好的例子(确保错误情况的处理不是内联的)。

如果降低了噪音,则我们正在查看的代码仅在此处执行方法/函数名称所建议的特定目的,这使发现不良代码变得更加容易。

回答

我玩过(0 ==变量)技巧,但是可读性有所下降-我们必须在思维上进行切换,才能将其读取为"如果变量等于零"。

我赞同马特·迪拉德(Matt Dillard)关于在单行条件句中加括号的建议。 (如果可以的话,我会投票赞成!)

当性能不是很关键时,我将使用另一种技巧:我将定义

void MyClass::DoNothing()
{

}

并使用它代替null语句。裸分号很容易丢失。可以像这样将数字1加到10(并将其总和存储):

for (i = 1; i <= 10; sum += i++)
    ; //empty loop body

但这是更具可读性和自我记录的IMO:

for (i = 1; i <= 10; sum += i++)
{
    DoNothing();
}

回答

如果可能,我尝试使用一件事。是避免使用布尔类型作为函数的参数,尤其是在有多个参数的情况下。

哪个更易读...

compare("Some text", "Some other text", true);

...或者...

compare("Some text", "Some other text", Compare.CASE_INSENSITIVE);

诚然,有时候这可能有点过大,但设置起来并不难,提高了可读性,并减少了作者错误地记住" true"是"是的,以区分大小写的方式进行比较"还是"是的,以不区分大小写的方式进行比较'。

当然,例如...

setCaseInsenstive(true);

...简单明了,足以让他一个人呆着。

回答

在我的工程应用程序中,我使测量单位和参考框架成为变量名称的一部分。这样,我可以轻松发现不一致之处。例子:

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)

回答

用一点匈牙利语

标记变量可能会有所帮助。例如,如果要清理用户输入,则可以执行以下操作:

$username = santize($rawusername);

这样,如果我们要说echo $ rawusername,它将看起来是错误的,因为是这样。