最喜欢的(聪明的)防守编程最佳实践

时间:2009-01-29 03:48:51

标签: language-agnostic

如果您必须选择收藏(聪明)技术进行防御性编码,它们会是什么?虽然我目前的语言是Java和Objective-C(有C ++背景),但可以随意用任何语言回答。这里强调的是聪明的防御技术,而不是我们这里70%以上的人已经知道的那些。所以现在是时候深入挖掘你的技巧了。

换句话说,试着想到除了无趣的示例之外的其他内容:

  • if(5 == x) 而不是 if(x == 5):以避免意外分配

以下是一些有趣的最佳防御性编程实践的示例(特定于语言的示例在Java中):

- 锁定变量,直到您知道需要更改它们

也就是说,您可以声明所有变量final,直到您知道需要更改它为止,此时您可以删除final。一个常见的未知事实是,这对方法参数也有效:

public void foo(final int arg) { /* Stuff Here */ }

- 当发生不好的事情时,留下一些证据

当你遇到异常时,你可以做很多事情:明显记录它并进行一些清理会有一​​些。但是你也可以留下一些证据(比如将变量设置为“UNABLE TO LOAD FILE”等哨兵值,或者99999在调试器中有用,以防你碰巧遇到异常catch - 阻止)。

- 谈到一致性:魔鬼在细节

与您正在使用的其他库一致。例如,在Java中,如果要创建一个提取一系列值的方法,则使下限包含和上限独占。这将使其与String.substring(start, end)等以相同方式运行的方法保持一致。您可以在Sun JDK中找到所有这些类型的方法,因为它使各种操作包括元素的迭代与数组一致,其中索引从零(包含)到长度数组(独占)。

那么你最喜欢的防守做法是什么?

更新:如果您还没有,请随意加入。在我选择官方答案之前,我有机会获得更多回复。

67 个答案:

答案 0 :(得分:103)

在c ++中,我曾经喜欢重新定义new,以便它提供一些额外的内存来捕获fence-post错误。

目前,我更倾向于避免采用防御性编程而采用Test Driven Development。如果你快速和外部地发现错误,你不需要用防御性操作来捣乱你的代码,你的代码是DRY - 呃,你最终会减少你必须防御的错误。

As WikiKnowledge Wrote

  

避免防御性编程,快速失败。

     

通过防御性编程我   意味着编写代码的习惯   试图弥补一些   编写代码时数据失败   假设呼叫者可能   提供不符合的数据   来电者和来电者之间的合同   子程序和那个子程序   必须以某种方式应对它。

答案 1 :(得分:76)

<强> SQL

当我必须删除数据时,我写

select *    
--delete    
From mytable    
Where ...

当我运行它时,我会知道我是否忘记或拙劣的where子句。我有安全感。如果一切正常,我会在' - '评论标记之后突出显示所有内容,然后运行它。

编辑:如果我删除大量数据,我将使用count(*)而不是*

答案 2 :(得分:48)

在应用程序启动时分配合理的内存块 - 我认为Steve McConnell在代码完成中将其称为内存降落伞

如果出现严重问题并且您需要终止,则可以使用此选项。

预先分配此内存可为您提供安全网,因为您可以将其释放,然后使用可用内存执行以下操作:

  • 保存所有持久数据
  • 关闭所有相应的文件
  • 将错误消息写入日志文件
  • 向用户提出有意义的错误

答案 3 :(得分:42)

在每个没有默认情况的switch语句中,我添加了一个使用错误消息中止程序的案例。

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}

答案 4 :(得分:41)

当你处理枚举的各种状态时(C#):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

然后,在一些例程中...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

在某些时候,我会在此枚举中添加其他帐户类型。当我这样做时,我会忘记修复这个switch语句。所以Debug.Fail可怕地崩溃(在调试模式下)引起我对这个事实的注意。当我添加case AccountType.MyNewAccountType:时,可怕的崩溃就会停止......直到我添加另一个帐户类型并忘记更新这些案例。

(是的,多态性在这里可能更好,但这只是我头脑中的一个例子。)

答案 5 :(得分:35)

使用字符串打印错误消息时(特别是依赖于用户输入的消息),我总是使用单引号''。例如:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

%s周围缺少引号非常糟糕,因为说文件名是空字符串或只是空格或其他内容。打印出的信息当然是:

ERROR: Could not open file

所以,总是做得更好:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

然后至少用户会看到:

ERROR: Could not open file ''

我发现这在最终用户提交的错误报告的质量方面产生了巨大的差异。如果有这样一个看起来很滑稽的错误信息,而不是一般的声音,那么他们更有可能复制/粘贴它而不只是写“它不会打开我的文件”。

答案 6 :(得分:28)

SQL安全

在编写任何将修改数据的SQL之前,我将整个事务包装在回滚事务中:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

这可以防止您永久执行错误的删除/更新。而且,您可以执行整个操作并验证合理的记录计数,或在SQL和SELECT之间添加ROLLBACK TRANSACTION语句,以确保一切正常。

如果您完全确定它符合预期,请将ROLLBACK更改为COMMIT并运行为真实。

答案 7 :(得分:26)

适用于所有语言:

将变量范围降至最低要求。 Eschew variables 仅用于将它们带入下一个语句。不存在的变量是您不需要理解的变量,您不能对此负责。出于同样的原因,尽可能使用Lambdas。

答案 8 :(得分:19)

在Java中,特别是对于集合,请使用API​​,因此如果您的方法返回类型List(例如),请尝试以下操作:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

不要让任何事情逃脱你不需要的课程!

答案 9 :(得分:19)

如有疑问,请点击申请表!

每个方法的开头检查每个参数(无论是自己明确编码,还是使用基于合同的编程都没关系)和炸弹如果不满足代码的任何先决条件,则使用正确的异常和/或有意义的错误消息。

当我们编写代码时,我们都知道这些隐含的前置条件,但如果没有明确检查它们,我们就会在以后出现问题时为自己创建迷宫并堆叠几十个方法调用将症状的出现与不满足前提条件的实际位置分开(=问题/错误实际存在的位置)。

答案 10 :(得分:17)

在Perl中,每个人都

use warnings;

我喜欢

use warnings FATAL => 'all';

这导致代码死于任何编译器/运行时警告。这在捕获未初始化的字符串时非常有用。

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here

答案 11 :(得分:16)

C#:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}

答案 12 :(得分:15)

在对字符串执行任何操作之前对string.IsNullOrEmpty进行c#检查,如length,indexOf,mid等

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

<强> [编辑]
现在我也可以在.NET 4.0中执行以下操作,另外检查值是否只是空格

string.IsNullOrWhiteSpace(myString)

答案 13 :(得分:14)

在Java和C#中,为每个线程提供一个有意义的名称。这包括线程池线程。它使堆栈转储更有意义。为线程池线程提供一个有意义的名称需要花费更多的精力,但是如果一个线程池在长时间运行的应用程序中出现问题,我可能会导致堆栈转储(你知道SendSignal.exe,对吗?),抓住日志,而不必打断正在运行的系统我可以告诉哪些线程......无论如何。无论遇到什么问题,都会陷入僵局,泄漏,成长。

答案 14 :(得分:12)

使用VB.NET,默认情况下为整个Visual Studio启用Option Explicit和Option Strict。

答案 15 :(得分:11)

使用Java,即使您关闭断言运行生产代码,使用assert关键字也很方便:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

即使断言断言,至少代码记录了param预计会设置在某处的事实。请注意,这是一个私有帮助函数,而不是公共API的成员。此方法只能由您调用,因此可以对如何使用它进行某些假设。对于公共方法,最好为无效输入抛出一个真正的异常。

答案 16 :(得分:10)

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

然后用 SAFE_DELETE(pPtr) SAFE_DELETE_ARRAY替换所有'删除pPtr '和'删除[] pPtr '调用(PPTR)

现在错误的是,如果你在删除指针后使用指针'pPtr',则会出现'访问冲突'错误。修复比随机内存损坏容易得多。

答案 17 :(得分:10)

在Java中,当某些事情发生并且我不知道为什么时,我有时会像这样使用Log4J:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

这样我得到一个堆栈跟踪,向我展示我是如何进入意外情况的,说一个永远不会解锁的锁,一个不能为null的null,依此类推。显然,如果抛出一个真正的异常,我不需要这样做。这时我需要查看生产代码中发生的事情而不会实际干扰其他任何事情。我想要抛出一个Exception而我没有抓到一个。我只是希望用适当的消息记录堆栈跟踪,以便将我标记为正在发生的事情。

答案 18 :(得分:10)

在找到ReSharper之前我没有找到readonly关键字,但我现在本能地使用它,特别是对于服务类。

readonly var prodSVC = new ProductService();

答案 19 :(得分:9)

如果您使用的是Visual C ++,请在覆盖基类的方法时使用override keyword。这样,如果有人碰巧更改了基类签名,它将抛出编译器错误,而不是静默调用错误的方法。如果它早先存在,这本可以节省我几次。

示例:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}

答案 20 :(得分:9)

C#

  • 在公共方法中验证引用类型参数的非空值。
  • 我对类使用sealed以避免在我不想要的地方引入依赖项。允许继承应该明确而不是偶然。

答案 21 :(得分:8)

我已经在Java中学到了几乎从未无限期地等待锁定解锁,除非我真的希望它可能需要无限期的长时间。如果实际上,锁定应在几秒钟内解锁,那么我将只等待一段时间。如果锁没有解锁,那么我会抱怨并将堆栈转储到日志中,并且根据系统稳定性的最佳状态,继续执行,就像锁解锁一样,或者继续,就像锁从未解锁一样。

在我开始这样做之前,这有助于隔离一些神秘的竞争条件和伪死锁条件。

答案 22 :(得分:8)

当您发出错误消息时,至少尝试提供程序在决定抛出错误时所具有的相同信息。

“Permission denied”告诉您存在权限问题,但您不知道问题发生的原因或位置。 “无法写入事务日志/我的/文件:只读文件系统”至少让你知道做出决定的基础,即使它是错的 - 特别是如果它是错的:错误的文件名?打错了?其他意外错误? - 当你遇到问题时,让你知道你在哪里。

答案 23 :(得分:7)

在C#中,使用as关键字进行投射。

string a = (string)obj
如果obj不是字符串

将抛出异常

string a = obj as string
如果obj不是字符串

将保留为null

您仍然需要考虑null,但这通常更直接,然后寻找强制转换异常。有时您需要“强制转换”或“爆炸”类型行为,在这种情况下,首选(string)obj语法。

在我自己的代码中,我发现大约75%的时间使用as语法,(cast)语法大约占25%。

答案 24 :(得分:7)

爪哇

java api没有不可变对象的概念,这很糟糕!在这种情况下,决赛可以帮助你。使用final标记每个不可变的类,并准备类accordingly

有时在局部变量上使用final是有用的,以确保它们永远不会改变它们的值。我发现这在丑陋但必要的循环结构中很有用。它只是为了容易意外地重用一个变量,即使它是一个常数。

在getter中使用defense copying。除非返回基本类型或不可变对象,否则请确保将对象复制为不违反封装。

永远不要使用克隆,请使用copy constructor

了解equals和hashCode之间的契约。这经常被违反。问题是它在99%的情况下不会影响您的代码。人们覆盖等于,但不关心hashCode。有些情况下你的代码会破坏或表现得很奇怪,例如:使用可变对象作为地图中的键。

答案 25 :(得分:6)

任何输入做好准备,并且您获得的任何输入都是意外的,转储到日志。 (在合理范围内。如果您正在读取用户的密码,请不要将其转储到日志中!并且不要每秒将数千种这类消息记录到日志中。在记录之前有关内容,可能性和频率的原因。)

我不只是谈论用户输入验证。例如,如果您正在阅读希望包含XML的HTTP请求,请为其他数据格式做好准备。我很惊讶地看到HTML响应,我只期望XML - 直到我看到并看到我的请求是通过透明代理我不知道并且客户声称无知 - 并且代理超时试图完成请求。因此,代理向我的客户端返回了一个HTML错误页面,使得客户端只关注XML数据而感到困惑。

因此,即使您认为自己控制了电线的两端,也可以获得意想不到的数据格式而不涉及任何恶意。做好准备,防御性地编码,并在意外输入的情况下提供诊断输出。

答案 26 :(得分:6)

我尝试使用契约式设计方法。 它可以通过任何语言模拟运行时间。每种语言都支持“断言”,但是编写一个更好的实现可以让您更容易,更方便地以更有用的方式管理错误。

Top 25 Most Dangerous Programming Errors中,“不正确的输入验证”是“组件之间的不安全交互”部分中最危险的错误。

在方法开头添加前提条件断言是确保参数一致的好方法。在方法结束时,我写了后置条件,检查输出是什么意思。

为了实现不变量,我在任何检查“类一致性”的类中编写一个方法,该类应该由前置条件和后置条件宏自动调用。

我正在评估Code Contract Library

答案 27 :(得分:5)

我忘记在PHP中写echo次太多次了:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

我需要永远尝试找出原因 - &gt; baz()没有返回任何东西,而实际上我并没有回应它! :-S所以我创建了一个EchoMe类,它可以包含任何应该回显的值:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

然后在开发环境中,我使用EchoMe来包装应该打印的内容:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

使用该技术,错过echo的第一个示例现在将抛出异常......

答案 28 :(得分:4)

使用带有某些基于接口的OOP模式的sentinel类,而不是null

E.g。当使用像

这样的东西时
public interface IFileReader {
  List<Record> Read(string file);
}

使用像

这样的哨兵类
public class NoReader : IFileReader {
  List<Record> Read(string file) {
    // Depending on your functional requirements in this case
    // you will use one or more of any of the following:
    // - log to your debug window, and/or
    // - throw meaningful exception, and/or
    return new List<Record>(); // - graceful fall back, and/or
    // - whatever makes sense to you here...
  }
}

并使用它来初始化任何IFileReader变量

IFileReader reader = new NoReader();

而不是将它们留给null(隐式或显式)

IFileReader reader; /* or */
IFileReader reader = null;

确保不会出现意外的空指针异常。

额外奖励:您不必再将IFileReader变量用于if (var!=null) ...,因为它们不会null

答案 29 :(得分:4)

C ++

当我输入新内容时,我必须立即键入delete。特别是阵列。

C#

在访问属性之前检查null,尤其是在使用Mediator模式时。对象被传递(然后应该使用as,如已经注意到的那样),然后检查null。即使您认为它不会为空,请检查。我很惊讶。

答案 30 :(得分:4)

始终在最高警告级别进行编译,并将警告视为错误(构建破坏程序)。

即使代码是“正确的”,也可以在不禁用警告的情况下修复警告的原因。例如,您的C ++编译器可能会向您发出类似法律代码的警告:

while (ch = GetNextChar()) { ... }

看起来您可能输入了=而不是==。如果添加显式检查,大多数提供此(有用)警告的编译器将会关闭。

while ((ch = GetNextChar()) != 0) { ... }

略微更明确不仅会使警告无声,还会帮助下一位必须理解代码的程序员。

如果您必须禁用警告,请在代码中使用#pragma,这样您就可以(1)限制禁用警告的代码范围,以及(2)使用注释解释警告必须的原因被禁用命令行或makefile中禁用的警告是等待发生的灾难。

答案 31 :(得分:4)

使用允许动态运行时日志级别调整的日志记录系统。通常,如果您必须停止程序以启用日志记录,您将丢失发生错误的罕见状态。您需要能够在不停止进程的情况下打开更多日志记录信息。

此外,linux上的'strace -p [pid]'将显示您希望系统调用进程(或Linux线程)。一开始看起来可能很奇怪,但是一旦习惯了libc调用的系统调用,你会发现这对于现场诊断来说是非常宝贵的。

答案 32 :(得分:3)

请记住,例外是程序员最好的朋友 - 永远不要吃它们!

答案 33 :(得分:3)

  • 微小可理解的课程。许多人。
  • 微小可理解的方法。
  • 尽可能不变。
  • 最小化范围 - 没有任何公开可以是包,没有任何可以私有的包。
  • 从来没有任何公开变量变量的借口。

此外,当你的课程很小并且通常是最终的时候,防守是非常便宜的 - 无论你是否相信它,都可以把它扔进去。测试传递给构造函数的值和(如果你真的必须有)setter。

答案 34 :(得分:3)

在Python中,如果我存根(或更改方法)然后没有时间在那天进行测试,我会填写“断言错误”,以便在运行方法时代码会崩溃,从而创建令人尴尬的错误我会在第二天注意到。有意的语法错误也很有用。

示例:

def somefunction(*args,**kwargs):
    ''' <description of function and args> '''
    # finish this in the morning
    assert False, "Gregg finish this up"

答案 35 :(得分:3)

在C ++中assert()是一个非常方便的工具。我不仅要为它提供评估条件,还要提供一条说明错误的信息:

assert( isConditionValid && "ABC might have failed because XYZ is wrong." );

当没有要检查的实际变量或者你发现自己处于一个永远不会发生的情况时(switch()的'默认'处理程序),这也有效:

assert( 0 && "Invalid parameter" );

它不仅在调试模式下断言,而且还告诉你同时出了什么问题。

如果我没记错的话,我从“C ++编码标准”中得到了这个。

答案 36 :(得分:3)

使用控制台,就像在游戏中一样;

并非完全“防守”,但我在很多游戏中看到了它。

我希望我的所有应用程序都有一个完整的控制台,允许我:

  1. 定义从控制台调用的简单命令(如切换到调试模式,设置一些运行时变量,检查内部配置参数等)。
  2. 在应用程序运行时,每次从应用程序访问日志。
  3. 如果需要,将日志保存到文件
  4. 将每个未处理的异常记录到控制台,然后再将其提交给用户(如果适用)。这样每个异常都会被捕获到某种程度。如果巧妙地将其与调试信息或地图文件相结合,您可以获得非常好的结果。
  5. 在C#中,如果使用条件属性标记Console方法,则它们将自动从发布版本中删除。在其他语言中,可以通过预处理器指令实现相同的目的。

    我发现它在测试阶段特别有价值,因为它允许开发人员看到正在发生的事情,并且测试人员可以向开发人员提供更好的反馈。

    另外:

    • 永远不会仅为登录捕获异常。
    • 永远不会捕获一般例外(例外E)
    • 永不隐藏异常
    • 将编译器警告视为错误,只接受警告,并仔细研究。
    • 始终检查来自图书馆外的所有输入。
    • 在“debug”中检查来自库内部的输入,不要检查发行版。
    • 永远不要提出一般性异常。如果存在描述问题的异常,则使用它,如果不存在,则创建自己的。

答案 37 :(得分:3)

从数据集中获取表格时

if(  ds != null &&
     ds.tables != null &&
     dt.tables.Count > 0 &&
     ds.tables[0] != null &&
     ds.tables[0].Rows > 0 )
{

    //use the row;
}

答案 38 :(得分:3)

我的C ++指南,但我认为这不聪明:

  • 总是lint,哎呀,让它成为makefile的一部分。更好的是,尽可能使用覆盖率。
  • 不要使用C ++例外。
  • 不要在C ++构造函数上放太多东西。请改用init()方法。在构造函数中发出错误信号的唯一方法是异常,即PITA。
  • 除非必要,否则不要超载操作员。
  • 如果您的构造函数有一个参数,请始终使用explicit keyword。
  • 避免使用全局对象。他们的执行令无法保证。
  • 在类分配内存时定义复制构造函数。但是如果你不希望复制这个类,并且你懒得定义它,那就防止它被调用。

class NonCopied {
private:
    NonCopied(const NonCopied&);
    NonCopied& operator=(const NonCopied&);
}
  • 停止使用sprintf(),strcpy(),strcat()。请改用他们的替代品,例如。 snprintf,strncpy()等

答案 39 :(得分:3)

对于C ++:自动检测数组的大小

char* mystrings[] = { "abc", "xyz" , "pqr" }

通常然后写为

for (int i=0; i< 3; i++)
{
    str= mystrings[i]
    // somecode
}

但是,稍后您可以为“ mystrings ”添加更多新字符串。在这种情况下,上面的for循环可能会在代码中引入细微的错误。

我使用的解决方案是

int mystringsize = sizeof(mystrings)/sizeof(char*)
for (int i=0; i< mystringsize; i++)
{
    str= mystrings[i]
    // somecode
}

现在,如果您向' mystrings '数组添加更多字符串,for循环将自动调整。

答案 40 :(得分:2)

在进行多线程C / C ++编程时,创建一系列宏,在您认为调用它的线程上调用您的函数。然后大量使用它们。

  • ASSERT_ON_UI_THREAD
  • ASSERT_ON_WORKER_THREAD
  • ASSERT_ON_THREADPOOL_THREAD
  • ASSERT_ON_DOWNLOAD_THREAD

在初始化线程时,在Windows上使用GetCurrentThreadId()或在Posix上使用pthread_self(),然后存储在globals中。断言与存储的值进行比较。

为我节省了大量痛苦的调试,特别是当其他人重构现有的多线程代码时。

答案 41 :(得分:2)

在C ++中

我在我的函数中展开断言,特别是在函数的开始和结束时捕获任何意外的输入/输出。当我稍后在函数中添加更多功能时,断言将帮助我记住。它还有助于其他人看到该功能的意图,并且仅在调试模式下有效。

我尽可能地避免使用指针,而是使用引用,这样我就不需要在代码中添加混乱的if (NULL!=p)语句。

我在声明和函数/方法参数中也经常使用const这个词。

我也避免使用POD,而是尽可能使用 STL / Boost 来避免内存泄漏和其他令人讨厌的事情。但是我确实避免使用过多的自定义模板,因为我发现它们很难调试,特别是对于那些没有编写代码的人。

答案 42 :(得分:2)

  • 使代码尽可能可读,特别是使用尽可能明显的函数和变量名。如果这意味着某些名称有点长,那就这样吧。

  • 尽可能使用静态分析仪。你很快就养成了编写符合其规则的代码的习惯。

  • 在开发过程中,可以轻松打开诊断输出 - 但可以轻松将其关闭以进行生产。

答案 43 :(得分:2)

在Perl中,当子例程没有传递足够的参数时,die()。这可以防止您在通过堆栈追踪10级时遇到故障。

sub foo {
    my $param0 = shift or confess "param0 is a required param";
    my $param1 = shift or confess "param1 is a required param";
    my $param2 = shift or confess "param2 is a required param";
    ...
}

答案 44 :(得分:2)

不要传递裸体集合,甚至是泛型。它们无法得到保护,也无法附加逻辑。

一个好的并行将是一个公共变量而不是setter / getter。 setter / getter允许您在不影响外部世界的情况下更改底层实现。

如果要传递集合,如何在不影响外部世界的情况下更改数据结构?您的收藏的所有访问权限都分发在您的所有代码中!!

相反,将它包装起来,给自己一个放置一点商业逻辑的地方。一旦你这样做,你会发现一些很好的重构。

通常你会发现添加一些变量和第二个集合是有意义的 - 那么你会发现这个课程一直都没有!

答案 45 :(得分:2)

尽量不要在几周内制作任何你设计的东西。在事情被锁定之前,通常会遇到其他情况。

答案 46 :(得分:2)

设计您的日志记录策略,以便在生产中发生错误时,会自动通过电子邮件发送相应的支持人员或开发人员。这使您可以主动发现错误,而不是等待用户抱怨。

请注意,这应该谨慎行事。我的一个例子是开发人员在循环中编写了一些日志代码。几个月后,系统中的错误触发了此代码。不幸的是,应用程序处于该循环中,一遍又一遍地记录相同的错误。我们在早上到达办公室时被告知我们的邮件服务器在我们的日志记录框架在凌晨4点到早上8点之间发送了40,000封电子邮件时崩溃了!

答案 47 :(得分:2)

回到RAM没有免费的那些日子,大多数电脑都非常有限并且“没有足够的记忆!”是一个非常常见的错误消息......

那么,大多数应用程序都能够以'优雅'崩溃:用户(几乎)从未丢失过他们的作品。

(差不多,我说了!^^)。

怎么做的?非常简单:当你的应用程序启动时,分配一个RAM气球(比如,高达20 KB!)。然后,当对malloc()的调用失败时:

  1. 请说“没有足够的记忆”(此消息是强制性的)。
  2. 添加“你最好保存所有工作。现在!”
  3. 释放高达20 KB的RAM气球。
  4. 恢复。
  5. Etvoilà。你的应用程序在用户缓慢崩溃,大部分时间都可以保存它的工作。

答案 48 :(得分:1)

我在PHP中做的一些事情(错误很容易且经常是灾难性的):

  • 在Vim中打开所有语法高亮提示。默认情况下有很多关闭(请:help php查看)。我正在考虑添加一些错误突出的东西......
  • 使用git中的预提交钩子对每个更改的文件进行语法检查(php -l)。它只能防止基本错误进入,但它总比没有好。
  • 围绕数据库类编写包装器,使得参数化预处理语句与输入普通查询相比变得简单易行 - $db->q1($sql, $param1, $param2)来获取第一行的单个列,依此类推。
  • 配置它(通过Xdebug扩展)为即使是微不足道的警告消息吐出巨大的调试信息HTML表,因此不可能忽略它们。在开发服务器上,即。在制作时,他们会默默地记录下来。
  • 简短,简单明了。为了制作更小的文件,我只花了很多次来重构一些东西。
  • 使用explicit control structure syntax避免几个“}”靠近。
  • 在签入之前校对代码。我养成了最大化窗口的习惯,然后设置了一个荒谬的大字体。如果我只能用微小的字体在屏幕上看到132C x 50R时才能理解它,那么开始时间太长了。

答案 49 :(得分:1)

If (some really bad condition) Then
Throw New Exception("particular bad thing happened")
End If

通常采用

形式

     Public SUb New(关键为Guid)
        Dim oReturn as returnpacket = Services.TableBackedObjectServices.GetData(key)
        如果oReturn.ds.tables(0).Rows.Count = 0则             抛出新异常(“在数据库中找不到从密钥加载的TableBackedObject。”)         结束如果

由于该特定构造函数仅在从搜索过程的结果中选择后加载特定对象时才被调用,因此找不到它是错误或竞争条件(这意味着另一个用户删除了该对象)键)。

答案 50 :(得分:1)

Crash-Only software,简而言之,不需要一些关机程序以及一些很少使用(因此可能是错误的)恢复代码,总是通过“崩溃”来停止程序,因此在启动时始终运行恢复代码。

这不适用于所有事情,但在某些情况下,这是一个非常巧妙的想法。

答案 51 :(得分:1)

那么,在编写递归方法时,我们中谁还没有意外地锁定Visual Studio?

  int DoSomething(int value)  // stupid example for illustrative purposes
  {
      if (value < 0)
          return value;
      value++;  // oops
      return DoSomething(value);
  }

为了避免不得不等待,有时可能不得不使用任务管理器终止你的IDE,请在调试时将其包含在递归方法中:

  int DoSomething(int value)
  {
>>    if (new StackTrace().FrameCount > 1000)  // some appropriately large number
>>        Debug.Fail("Stack overflow headed your way.");

      if (value < 0)
          // some buggy code that never fires
      return DoSomething(value);
  }

这看起来似乎很慢,但实际上检查FrameCount非常快(在我的电脑上不到一秒钟)。在确定方法正常工作后,您可以将此故障保护输出(或者只是将其注释掉,但留待以后调试)。

答案 52 :(得分:1)

语言无关:永远不要依赖编译器,虚拟机等进行初始化。始终将变量显式初始化为有用的值。

断言是你最好的朋友,虽然在某些情况下单元测试可能更合适。

C / C ++:尽可能使用基于堆栈的对象。

在条件中,明确检查您期望或不期望的值。例如,如果您有一个名为activated的布尔变量,而不是写if (activated),请写if (true == activated)。原因是activated可能包含足以使条件成功的垃圾。

答案 53 :(得分:1)

如果某个值类型对其值有某些约束,请创建一个由代码强制执行这些约束的类。一些例子:

public class SanitizedHtmlString
{
private string val;

public SanitizedHtmlString(string val)
{
  this.val = Sanitize(val);
}

public string Val
{
  get { return val; }
}

//TODO write Sanitize method...
}


public class CarSpeed
{
private int speedInMilesPerHour; 

public CarSpeed(int speedInMilesPerHour)
{
  if (speedInMilesPerHour > 1000 || speedInMilesPerHour < 0)
  {
    throw new ArgumentException("Invalid speed.");
  }
  this.speedInMilesPerHour = speedInMilesPerHour; 
}

public int SpeedInMilesPerHour
{
  get { return speedInMilesPerHour; }
}
}

答案 54 :(得分:1)

JavaScript的:

我们应该适当地使用“==”和“===”。

  

==:类型转换相等比较

     

===:严格的平等比较

例如,'1'== 1为真,但'1'=== 1为假。

很多人在无意识中使用“==”而不是“===”。

答案 55 :(得分:1)

我在Teradyne(c,vba),Advantest(c ++ .net)等自动测试设备上测试混合信号半导体的工作中做了很多数学计算。

我使用的两种防守动作是:

  • 防止被零除,    if(x!= 0){              Z = Y / X;              }              其他{              / *给z一个可识别的假号,继续程序* /              }

  • 不传递零或负数来记录计算。这对于增益,CMRR和PSRR的计算是常见的。    if(x> 0){     psrr = 20 * log(x);     } else {     psrr = -999; / *假号* /     }

有些人可能反对使用假数字,但这些程序用于大量半导体制造。如果在测试坏部件时发生错误,最好继续测试并保持数据格式的完整性。在测试数据的后处理过程中,假数字很容易被分离为异常值。

- 迈克

答案 56 :(得分:1)

C ++:

避免使用原始指针,始终使用Boost智能指针包(例如,shared_ptr)。

答案 57 :(得分:1)

不要将单字符变量用于循环索引。例如:

for (int ii = 0 ; ii < someValue ; ii++)
    // loop body

这是一个简单的习惯,但如果必须使用标准文本编辑器来查找循环变量的引用,这将非常有用。当然,索引循环通常不应该太长,以至于您需要搜索索引引用...

答案 58 :(得分:1)

包括高级异常处理,详见

Top-level Exception Handling in Windows Forms Applications

我的Program.cs看起来像这样

    static class Program
    {
    [STAThread]
    static void Main()
    {
        Application.ThreadException += 
            new ThreadExceptionEventHandler(new ThreadExceptionHandler().ApplicationThreadException);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

    public class ThreadExceptionHandler
    {
        public void ApplicationThreadException(object sender, ThreadExceptionEventArgs e)
        {
            MessageBox.Show(e.Exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

答案 59 :(得分:1)

在C#中,使用'using'确保对象在超出范围时被处理掉。即。

        using(IDataReader results = DbManager.ExecuteQuery(dbCommand, transaction))
        {
            while (results.Read())
            {
                //do something
            }
        }

此外,在投射

后检查空值
        MyObject obj = this.bindingSource.Current as MyObject;
        if (MyObject != null)
        {
           // do something
        }

另外,我尽可能使用枚举来避免硬编码,拼写错误,并在需要时提供简单的重命名,即

    private enum MyTableColumns
{ 
    UserID,
    UserName
}

private enum StoredProcedures
{
    usp_getMyUser,
    usp_doSomething
}

public static MyUser GetMyUser(int userID)
{
    List<SqlParameter> spParameters = new List<SqlParameter>();

    spParameters.Add(new SqlParameter(MyTableColumns.UserID.ToString(), userID));


    return MyDB.GetEntity(StoredProcedures.usp_getMyUser.ToString(), spParameters, CommandType.StoredProcedure);
}

答案 60 :(得分:1)

在C#中: 而不是:

if( str==null )

这样做:

if( String.IsNullOrEmpty(str) )

答案 61 :(得分:1)

在c#中使用 TryParse 而不是 Parse 用于值类型以避免像FormatException,OverflowException等异常,当然要避免为同一个写入try块。 / p>

错误代码

string numberText = "123"; // or any other invalid value

public int GetNumber(string numberText)
  {
  try
  {
     int myInt = int.Parse(numberText);
     return myInt;
  }
  catch (FormatException)
  {
    //log the error if required
     return 0;
   }
  catch (OverflowException)
  {
     return 0;
  }
}

好的代码(如果你不想处理错误)

string numberText = "123"; // or any other invalid value
public int GetNumber(string numberText, int defaultReturnValue)
  {
    int myInt;
    return ( int.TryParse(numberText, out myInt) ) ?  myInt : defaultReturnValue;
}

您可以对几乎所有的值类型执行相同的操作,例如: Boolean.TryParse,Int16.TryParse,decimal.TryParse等

答案 62 :(得分:0)

虽然很聪明,但也许是一种体面的做法。在C / C ++中:

始终从底部的功能返回,从不在中间。唯一的例外是检查所需参数的null;总是第一次立即返回(否则我只是在顶部写一个看起来很傻的“if”条件)。

int MyIntReturningFuction(char *importantPointer)
{
    int intToReturn = FAILURE;
    if (NULL == importantPointer)
    {
        return FAILURE;
    }
    // Do code that will set intToReturn to SUCCESS (or not).
    return intToReturn;
}

我已经看到很多关于为什么它并不重要的论据,但对我来说最好的论据就是经验。我常常挠挠脑袋,问道:“为什么我的断点不能靠近这个功能的底部?”只是发现除了我之外的其他人已经在上面的地方放了一个回报(并且通常会改变一些原本应该保留的条件)。

我还发现,拥有这样非常简单的规则使我成为一个更加一致的编码器。我从来没有特别违反这个规则,所以有时我必须考虑处理事物的其他方法(例如清理内存等)。到目前为止,它一直都是好的。

答案 63 :(得分:0)

  • 从代码执行SQL查询时,请始终使用占位符
  • MySQL具有DELETE语句的有用的非标准扩展名:DELETE FROM sometable WHERE name IS LIKE 'foo%' LIMIT 1。这样你就不会在出错的情况下擦拭整个表格。

答案 64 :(得分:0)

语言不可知:问题:报告和处理整体的部分内容。每当显示计算和百分比时,我总是保持运行总计,对于最后一个条目,其值不像其余条目那样计算,而是从100.00减去运行总计。以这种方式,如果一些感兴趣的人选择将所有组件百分比加起来,他们将完全添加到100.00

答案 65 :(得分:0)

不需要与语言的限制抗争是我在程序逻辑中可以采用的最佳防御。有时,当事情停止时,更容易说明。

例如,你有这种循环:

while(1)
{
  // some codes here

  if(keypress == escape_key || keypress == alt_f4_key 
     || keypress == ctrl_w_key || keypress == ctrl_q_key) break;

  // some codes here
}

如果你想把条件放在循环标题上,而不是与没有直到结构的语言作斗争,只需逐字复制条件并加上感叹号:

while(! (keypress == escape_key || keypress == alt_f4_key 
     || keypress == ctrl_w_key || keypress == ctrl_q_key) )
{ 
    // some codes here
}

除了C语言的构造之前没有,所以只需执行上述操作,否则执行此操作(可能在C / C ++中使用#define ;-)

until(keypress == escape_key || keypress == alt_f4_key 
     || keypress == ctrl_w_key || keypress == ctrl_q_key)
{ 
    // some codes here
}

答案 66 :(得分:0)

而不是var.equals(“whatever”)在java中我做什么“无论什么”.equals(var)。这样,如果var为null,我不必担心nullpointer异常。在处理URL参数等事情时,这很有用。