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

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

换句话说,试着想想 没意思以外的例子:

  • 避免意外指派

下面是一些 很有意思最佳防御性编程实践的例子(特定于语言的例子在 Java 中) :

- 锁定你的变量,直到你知道你需要改变他们

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

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

当不好的事情发生的时候,留下一串证据

当出现异常时,您可以做很多事情: 显然,记录异常并执行一些清除操作是很少的。但是你也可以留下一系列的证据(例如,设置变量为前哨值,比如“ UNABLE TO LOAD FILE”或者99999在调试器中会很有用,以防你碰巧越过了一个异常 catch-block)。

说到一致性,细节决定成败

与您正在使用的其他库保持一致。例如,在 Java 中,如果您正在创建一个提取一系列值的方法,那么可以使用下界 包容性和上界 独家新闻。这将使它与以相同方式操作的 String.substring(start, end)等方法保持一致。在 Sun JDK,你会发现所有这些类型的方法都是这样运行的,因为它执行各种操作,包括迭代与数组一致的元素,其中索引从0(包容性)到数组长度(独家新闻)。

那么你最喜欢的防御方法是什么呢?

更新: 如果你还没有,欢迎随时加入。在我选择 正式的答案之前,我给了更多的回答机会。

21101 次浏览

在 C # 中,使用 as关键字进行强制转换。

string a = (string)obj

如果 obj 不是字符串,将引发异常

string a = obj as string

如果 obj 不是字符串,那么将保留一个空值

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

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

使用 Java,即使在运行关闭断言的生产代码时,也可以方便地使用断言关键字:

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


return blahBlah(param);
}

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

当从数据集中获取表时

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


//use the row;
}

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

readonly var prodSVC = new ProductService();

SQL

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

select *
--delete
From mytable
Where ...

当我运行它时,我将知道我是否忘记了 where 子句。我有保险。如果一切正常,我将在“——”注释标记之后突出显示所有内容,并运行它。

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

如果(一些非常糟糕的情况)
抛出新的异常(“特别糟糕的事情发生了”)
如果结束

通常是这样的

新公共小巴(以键作引导)
调暗返回作为返回包 = Services.TableBackedObjectServices.GetData (key)
如果 oReturn. ds.tables (0) . Rows. Count = 0则 抛出新异常(“从键加载的 TableBackedObject 未在数据库中找到”)
如果结束

由于只有在从搜索过程的结果中选择一个特定的对象后,才会调用这个特定的构造函数,因此不能找到它要么是一个 bug,要么是一个竞争条件(这意味着另一个用户按键删除了该对象)。

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

这可以用来防止出现严重问题,并要求您终止服务。

预先分配这些内存为您提供了一个安全网,因为您可以释放这些内存,然后使用可用内存执行以下操作:

  • 保存所有持久数据
  • 关闭所有适当的文件
  • 将错误消息写入日志文件
  • 向用户显示一个有意义的错误

C + +

当我输入 new 时,我必须立即输入 delete,特别是对于数组。

C #

在访问属性之前检查 null,特别是在使用 Mediator 模式时。对象被传递(然后应该按照已经注意到的方式进行强制转换) ,然后检查 null。即使你认为它不会为空,检查无论如何。我很惊讶。

在 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";
...
}

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

目前,我更倾向于避免防御性编程而选择 测试驱动开发。如果您能够快速地从外部捕获错误,那么您就不需要用防御策略来混淆代码,您的代码是 干的-er,因此您需要防御的错误会更少。

维基知识写道:

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

防御性编程 意味着编写代码的习惯 试图弥补 数据中的错误,编写代码的错误 假设来电者可能 提供不符合 呼叫者和被呼叫者之间的合同 子程序和子程序 必须想办法应付。

在佩尔,每个人都有

use warnings;

我喜欢

use warnings FATAL => 'all';

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

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

我在 PHP 中做的一些事情(错误很容易发生,而且往往是灾难性的) :

  • 打开 Vim 里所有的语法突显。有很多默认关闭(使用 :help php查看它们)。我正在考虑添加一些我自己的错误标示..。
  • 在 git 中使用预提交钩子来语法检查(php -l)每个更改的文件。它只能防止基本错误的发生,但总比什么都没有强。
  • 编写数据库类周围的包装器,使参数化准备语句与输入普通查询相比简单得要命—— $db->q1($sql, $param1, $param2)获取第一行的单个列,等等。
  • 通过配置它(通过 Xdebug 扩展)为即使是微不足道的警告消息提供大量的调试信息的 HTML 表,因此不可能忽略它们。也就是说,在开发服务器上。在生产时,它们会被静默地记录下来。
  • 简明扼要。我花了 很多的时间只是重构的东西,为了使较小的文件。
  • 使用 显式控制结构语法避免有几个“}”在接近。
  • 在登记之前校对代码。我已经养成了最大化窗口的习惯,然后设置一个荒谬的大字体大小。如果我只能在屏幕上看到132C x 50R 的小字体时才能理解它,那么它就太长了。

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”之后错误地使用它,您将得到“访问冲突”错误。这比随机内存损坏更容易修复。

当处理枚举(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:,可怕的崩溃停止... 直到我添加另一个帐户类型,忘记更新的情况下在这里。

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

C # :

string myString = null;


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


}


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


}

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

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

通常是这样写的

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

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

我使用的解决方案是

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

现在,如果您添加更多的字符串’我的弦’数组,for 循环将自动调整。

适用于所有语言:

将变量 的范围减少到所需的最小值。提供的 回避变量将它们带入下一个语句。不存在的变量是你不需要理解的变量,你不需要为此负责。出于同样的原因,尽可能使用 Lambdas。

在 Java 中,当发生了一些我不知道为什么的事情时,我有时会这样使用 Log4J:

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

通过这种方式,我得到了一个堆栈跟踪,它告诉我如何进入意想不到的情况,比如一个从未解锁的锁,一个不能为 null 的 null,等等。显然,如果抛出了真正的 Exception,则不需要执行此操作。这就是我需要在不影响其他任何事情的情况下查看生产代码中发生了什么的时候。我 不要想抛出一个异常,但我没有接住一个。我只是想要一个堆栈跟踪记录一个适当的消息,标记我在发生什么事情。

我在 Java 中学到了 几乎没有无限期地等待一个锁被解锁,除非我真的认为它可能需要无限期地长时间。如果实际上,锁应该在几秒钟内解锁,那么我将只等待一段时间。如果锁没有解锁,那么我会抱怨并将堆栈转储到日志中,根据对系统稳定性最有利的方式,要么继续,就像锁没有解锁一样继续,要么继续,就像锁从未解锁一样继续。

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

C #

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

在 Java 和 C # 中,给 每个线程一个有意义的名称。这包括线程池线程。它使堆栈转储更有意义。甚至给线程池线程起一个有意义的名字也需要花费一些精力,但是如果一个线程池在长时间运行的应用程序中出现问题,我可以导致堆栈转储发生(您知道 SendSignal.exe,对吗?),获取日志,而不必中断正在运行的系统,我可以告诉哪些线程是... 无论什么。僵持不下,泄露,成长,不管是什么问题。

准备好 任何输入,以及任何意外的输入,转储到日志中。(在合理范围内。如果您正在从用户那里读取密码,请不要将其转储到日志中!并且不要每秒钟将成千上万的这类消息记录到日志中。在你记录之前,要对内容、可能性和频率进行推理。)

我说的不仅仅是用户输入验证。例如,如果正在读取希望包含 XML 的 HTTP 请求,请为其他数据格式做好准备。我惊讶地发现 HTML 响应中只有 XML ——直到我查看并看到我的请求是通过一个我不知道的透明代理进行的,而客户声称对此不知情——并且代理超时试图完成请求。因此,代理向我的客户机返回了一个 HTML 错误页面,这使得只需要 XML 数据的客户机非常困惑。

因此,即使您认为自己控制了线路的两端,也可以获得意想不到的数据格式,而不会涉及任何恶行。做好准备,以防御的方式编写代码,并在出现意外输入时提供诊断输出。

不需要与语言的局限性作斗争是我能在程序逻辑中使用的最好的防御。有时更容易说明什么时候事情应该 别说了

例如,你有这样的循环:

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 派生语言上没有 until 构造,所以只需要执行上述操作,否则就这样做(在 C/C + + 中可以使用 # Definition; -)

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

在每个没有默认大小写的 switch 语句中,我添加了一个用错误消息中止程序的大小写。

#define INVALID_SWITCH_VALUE 0


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

使用具有特定接口的 OOP 模式的前哨类,而不是 null

例如,当使用

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;

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

额外的好处是: 你不再需要用一个 if (var!=null) ...来封装每一个 IFileReader变量,因为它们不会是 null

如果您正在使用 Visual C + + ,那么在重写基类的方法时,请使用 重写关键字。这样,如果有人碰巧更改了基类签名,它将抛出编译器错误,而不是默默地调用错误的方法。如果它早点存在的话,我可以省好几次。

例如:

class Foo
{
virtual void DoSomething();
}


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

如果有疑问,轰炸应用程序!

每一个方法的开头检查 每一个参数(无论是自己显式编码,还是使用基于契约的编程在这里都无关紧要) ,如果代码的任何前提条件没有得到满足,就用正确的异常和/或有意义的错误消息轰炸。

我们都知道这些隐式的先决条件 当我们写代码的时候,但是如果它们没有被明确检查,我们就是在为我们自己创建迷宫,当后来出了问题,并且堆积了几十个方法调用来分离症状的发生和没有满足先决条件的实际位置(= 问题/bug 的实际位置)。

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

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

不要让任何你不需要的东西逃离你的课堂!

设计您的日志记录策略,以便当生产中出现错误时,相应的支持人员或开发人员会自动收到电子邮件。这使您能够主动地发现 bug,而不是等待用户抱怨。

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

我多次忘记用 PHP 编写 echo:

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

我花了很长时间才弄明白为什么-> 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的示例现在将抛出一个异常..。

在 c # 检查 string.IsNullOrEmpty 时,在对字符串执行任何操作(如 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)

使用 VB.NET,在默认情况下为整个 Visual Studio 打开 OptionExplect 和 OptionStrict。

爪哇咖啡

Java api 没有不可变对象的概念,这很糟糕!这种情况下期末考试可以帮助你。用 final 标记每个不可变的类,并准备类 因此

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

在读取器中使用 辩方复制。除非返回基本类型或不可变物件,否则请确保复制对象不会违反封装。

不要使用克隆,使用 复制建构子

学习 equals 和 hashCode 之间的契约。这是经常被侵犯的。问题是在99% 的情况下它不会影响您的代码。人们覆盖 equals,但不关心 hashCode。有些实例中,你的代码可能会中断或者行为异常,例如使用可变对象作为映射中的键。

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

  • 总是线头,见鬼,使它成为你的 makefile 的一部分。更好的是,如果可能的话使用遮盖物。
  • 不要使用 C + + 异常。
  • 不要在 C + + 构造函数上放太多东西。使用 init ()方法代替。在构造函数中发出错误信号的唯一方法是异常,即 PITA。
  • 除非必要,不要超载操作员。
  • 如果构造函数只有一个参数,则始终使用显式关键字。
  • 避免全局对象。它们的执行顺序不能得到保证。
  • 在类分配内存时定义复制建构子。但是如果您不希望类被复制,并且您懒得定义一个类,那么请保护它不被调用。

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

而不是在 java 中使用 var.equals (“ whatever”) ,我使用“ whatever”。等于(var)。这样,如果 var 为 null,我就不必担心 nullpoint 异常。这在处理诸如 URL 参数等事情时非常有效。

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

25个最危险的编程错误中,“不正确的输入验证”是“组件之间不安全的交互”部分中最危险的错误。

在方法的开头添加 先决条件断言是确保参数一致性的好方法。在方法的末尾,我编写 后置条件,检查输出是否符合要求。

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

我在评估 代码合同库

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

另外,linux 上的‘ strace-p [ pid ]’将显示进程(或 linux 线程)正在进行的系统调用。一开始可能看起来很奇怪,但是一旦您习惯了通常由 libc 调用进行的系统调用,您将发现这对于现场诊断非常有价值。

用 C + +

我将断言分散在函数的各个部分,特别是在函数的开始和结束部分,以捕捉任何意外的输入/输出。当我稍后在函数中添加更多功能时,断言将帮助我记住。它还可以帮助其他人了解该函数的意图,并且只在调试模式下处于活动状态。

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

我还尽可能多地在声明和函数/方法参数中使用 const这个词。

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

在对值类型使用 尝试解析而不是 解析时,为了避免像 FormatException、 Overflow Exception 等异常,当然也为了避免为同样的异常编写 try 块。

坏代码

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

您可以对几乎所有的值类型执行相同的操作,例如: 布尔值。尝试解析,Int16。尝试解析,十进制。尝试解析等

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

不是完全的“防守”,但我从很多比赛中看到这一点。

我想有一个完整的控制台,我的所有应用程序,让我可以:

  1. 定义要从控制台调用的简单命令(如切换到调试模式、设置一些运行时变量、检查内部配置参数等)。
  2. 在应用程序运行时,每次我想从应用程序访问日志。
  3. 如果需要,将日志保存到文件中
  4. 在将每个未处理的异常引发给用户之前(如果适当)将其记录到控制台。这样,每个异常都会被视为某种级别的异常。如果你巧妙地结合调试信息或映射文件,你可以得到很好的结果。

在 C # 中,如果您使用条件属性标记 Console 方法,那么它们将自动从发布版本中剥离。在其他语言中,同样可以通过预处理器指令来实现。

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

此外:

  • 永远不要仅仅为了登录而捕获异常。
  • 永远不要捕获一般的异常(异常 E)
  • 永远不要隐藏例外
  • 将编译器警告视为错误,只接受经过仔细研究的警告。
  • 始终检查来自库外的每个输入。
  • 在“调试”中检查来自库内的输入,不要检查发布。
  • 永远不要提出一个通用的异常。如果存在一个描述问题的异常,使用它,如果没有,创建自己的异常。

回到那些内存不是免费的日子,大多数计算机是非常有限的,“没有足够的内存!”是一个很常见的错误信息。

那么,大多数应用程序能够崩溃与“优雅”: 用户(几乎)从来没有失去他们的作品。

(差一点,我说! ^ ^)。

怎么做到的?非常简单: 当您启动应用程序时,分配一个内存气球(比如,一个庞大的20KB!).然后,当调用 malloc ()失败时:

  1. 请说“记忆力不够”(这条信息是强制性的)。
  2. 加上“你最好保存你所有的工作。现在!”
  3. 释放庞大的20KB 内存气球。
  4. 简历。

就是这样,你的应用程序在用户面前缓慢崩溃,大多数时候,可以保存它的工作。

在 C + + assert()是一个非常方便的工具。我不仅提供了评估的条件,而且还提供了一个信息,说明什么是错误的:

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

当没有实际的变量需要检查,或者你发现自己处于一个本不应该发生的情况下(switch ()的“默认”处理程序) ,这也可以起作用:

assert( 0 && "Invalid parameter" );

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

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

C # : 而不是这样:

if( str==null )

这样做:

if( String.IsNullOrEmpty(str) )

在 Python 中,如果我存根(或者更改一个方法) ,然后当天没有时间测试它,我就会填入一个“断言 False”,这样一旦方法运行,代码就会崩溃,造成第二天我就会注意到的令人尴尬的错误。有意识的语法错误也会有所帮助。

例如:

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

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 和 ROLLBACK TRANSACTION之间添加 SELECT语句,以确保一切正常。

当您完全确定它达到了预期的效果时,将 ROLLBACK改为 COMMIT,然后真正地运行。

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

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

包括高级异常处理,如此处详细描述的

Windows 窗体应用程序中的顶级异常处理

我的程序就会变成这样

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

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

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

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

算不上聪明,但也许是个不错的做法。在 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;
}

我见过很多为什么这不重要的论点,但对我来说最好的论点是简单的经验。我经常抓耳挠腮,问“为什么靠近这个函数底部的断点没有被击中?”结果却发现除了我以外,有人在上面的某个地方放了一个返回值(并且通常改变了一些本应该被放在一边的条件)。

我还发现,拥有这样非常简单的规则可以使我成为一个更加一致的编码者。我从来没有特别违反过这条规则,所以有时候我不得不考虑其他处理事情的方法(比如清理内存等等)。到目前为止,情况总是朝着好的方向发展。

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

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

%s周围缺少引号真的很糟糕,因为 filename 是一个空字符串或者只是空格之类的。打印出来的信息当然是:

ERROR: Could not open file

因此,总是更好的做法:

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

那么至少用户可以看到:

ERROR: Could not open file ''

我发现这对最终用户提交的 bug 报告的质量有很大的影响。如果有一个像这样有趣的错误消息,而不是一些通用的声音,那么他们更有可能复制/粘贴它,而不是只写“它不会打开我的文件”。

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

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

  • 在开发过程中,要使打开诊断输出变得容易,但要使生产过程中关闭诊断输出变得容易。

发出错误消息时,至少尝试提供与程序决定抛出错误时相同的信息。

“拒绝许可”告诉您存在许可问题,但您不知道问题发生的原因和位置。“无法写入事务日志/my/file: 只读文件系统”至少可以让您知道作出决定的依据,即使这个决定是错误的——特别是如果它是错误的: 错误的文件名?开错了?其他意想不到的错误?并让你知道你在哪里,当你有问题。

C + + :

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

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

我使用的两种防御策略是:

  • 防止除以零, 如果(x! = 0){ Z = y/x; } 否则{ /* 给 z 一个可识别的假号码,继续程序 */ }

  • 不要将零或负数传递给对数计算。这在增益、共模抑制比(CMRR)和电源抑制比(PSRR)的计算中很常见。 如(x > 0){ Psrr = 20 * log (x) ; { Psrr = -999;/* 假号码 */ }

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

Mike

JavaScript:

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

= = : 类型转换相等比较

= = : 严格的相等比较

例如,‘1’= = 1为真,但‘1’= = = 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; }
}
}

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

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

  • 很小的可以理解的课程,很多都是。
  • 极小可理解的方法。
  • 只要有可能就不会改变。
  • 最小化范围——没有可以打包的公共包,没有可以私有的包。
  • 从来没有任何借口公开变量。

而且,当你的课程很小而且通常是期末课程的时候,防御真的很便宜——不管你是否相信它,最好还是把它扔进去。测试传递给您的构造函数和设置器的值(如果您确实必须拥有它们)。

不要传递裸集合,即使是泛型。它们不能被保护,也不能附加逻辑。

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

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

相反,把它包装起来,给自己一个地方放一些业务逻辑。你会发现一些不错的重构一旦你这样做。

通常,您会发现添加一些变量或者第二个集合是有意义的——然后您会意识到这个类一直都没有出现!

在进行多线程 C/C + + 编程时,创建一系列宏来断言您的函数正在您认为正在被调用的线程上被调用。那就好好利用。

  • ASSERT _ ON _ UI _ THREAD
  • ASSERT _ ON _ WORKER _ THREAD
  • ASSERT _ ON _ THREADPOOL _ THREAD
  • 下载线程
  • 等等。

初始化线程时,在 Windows 上使用 GetCurrentThreadId () ,在 Posix 上使用 pthread _ self () ,然后存储在全局变量中。断言与存储的值进行比较。

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

  • 在从代码执行 SQL 查询时,始终使用占位符
  • MySQL 对 DELETE语句有一个有用的非标准扩展: DELETE FROM sometable WHERE name IS LIKE 'foo%' LIMIT 1。这样你就不会擦掉整张桌子,以防万一。

语言不可知论: 问题: 报告和处理整体的某些部分。无论何时显示计算结果和百分比,我总是保留一个运行总数,对于最后一个条目,它的值不像其他条目那样计算,而是从100.00减去运行总数。按照这种方式,如果某个感兴趣的方面选择将所有组件百分比相加,那么他们将正好相加到100.00

始终在最高警告级别进行编译,并将警告视为错误(生成断开器)。

即使代码是“正确的”,也要尽可能在不禁用警告的情况下修复警告的原因。例如,您的 C + + 编译器可能会对合法代码给出如下警告:

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

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

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

稍微显式一点不仅可以消除警告,而且还可以帮助下一个必须理解代码的程序员。

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

那么,我们当中有谁在编写递归方法时没有意外地将 VisualStudio 锁定一段时间呢?

  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 的速度相当快(在我的电脑上不到一秒钟)。在确定方法正常工作之后,您可以删除这个故障保护程序(或者只是注释掉它,留待以后调试)。

语言不可知性: 不要依赖编译器、虚拟机等进行初始化。始终明确地将变量初始化为有用的值。

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

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

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

记住,异常是程序员最好的朋友——永远不要吃它们!