I'm a fan of the techniques described in "The Pragmatic Programmer". I also use TDD, rather than DBC as I find it more flexible and productive. For example some of the techniqes described in 'pragprog' include:
Test often. Test early. Test automatically
Don't repeat yourself
Use saboteurs to test your testing
Use exceptions for exceptional problems
Don't live with broken windows
Don't use manual procedures
They all seem like common sense, but its amazing how quickly teams deviate from these underpinning principles when faced with deadlines.
I'm fond of the second pair of eyes method: after I've written and tested some critical code, I'll sometimes ask a coworker to review it specifically with the intent of looking for ways to break it.
It's fun to bring out people's creativity this way. :-)
These three probably serve me better than any other:
Avoid state whenever possible. Create and use immutable objects- they are easier to test and less likely to betray you.
Don't write unnecessary code. This one is hard. Check out the recent bloom of articles relating "The Elements of Style" by Strunk and White to programming.
Ask yourself every 10 minutes or so: "Is this dumb?" Be honest. This one is harder.
I implement a variety of methods to prevent and recover from errors:
1) Handle all exceptions. As you stated, "handle every error". If a user clicks a button on a form, there should be no possibility of the application just disappearing ("poof") from an unhandled exception. For that reason, I wrap event handlers with a generic try catch.
2) Log errors with full stack trace. When I rethrow an exception I always create a new one and add the caught exception as an inner exception. My logging code unwraps the messages recursively which gives a little more detail than I'd have otherwise.
3) Decide whether your classes are going to be reusable or not. If not document it. Consider implementing an interface in your code, something like IRestartable or IReusable. Any object not implementing it must be thrown away after it's used once.
4) Never assume thread safety. I've learned the hard way that .NET is extremely multi-threaded. Many events are handled on arbitrary threads. A given app written in .NET could have many simultaneous threads executing and not have a single line of code explicitly creating one.
5) Keep variable scope as narrow as possible. Instantiate objects near where they are used instead of in a large block at the beginning of a method. You'll potentially shorten the life of the objects and you won't forget about unneeded or reused variables sitting in a huge block at the top of the class or method.
5) Simple, but I still see it happening. Avoid globals like the plague. I've seen code with hundreds of unused/reused variables. It was a mess to figure out and refactor.
I'm a fan of not initializing local variables. They should be set when needed. Otherwise, programmers reading your code could be confused as in "hmm why is this 0 at the beginning...". If you don't initialize it, it's clear it's not used yet.
That way, when my code is used (or when I used my own code), I know what I can expect. Simple recommendation, but so rarely implemented ;)
That documentation also include a clear separation of static and runtime exceptions. That way, if the program must fail as soon as possible in order to improve robustness, I know if it fails because of a foreseen exception (static, must be dealt with at coding time trough a catch or a re-throw), or if it is because of incorrect parameters (runtime exceptions, only detected during the application lifetime).
If both types of exception are clearly documented (especially when it come to limit values for parameters), the overall robustness is easier to enforce.
When it's possible, I ask the opinion of someone who specializes in something else. That often uncovers one or more entirely new ways of breaking things.
The systems I write (in C) have high requirements as to performance and reliability. Designing for fail is fine. When it comes to design for safe things become more complicated.
Hardware can always fail and apart from that you have the issue of invalid data entering the system from outside. My solutions in those areas will usually be elaborate (design for safe) and in practically all other areas design for fail by essentially having extremely simple code and no data validation at all.
So you could say that I design for fail when the risks are small and for safe when they are high.
For debugging I sometimes write conditional code which should never be entered which contains a divide by zero or some such so that when processing enters there the debugger will be invoked immediately.
I usually don't initialize local stack variables since the compiler tells me which need to be initialized and which can be omitted completely.
Code reviews are great but not fool-proof. I once spent several hours looking at a small (infinite) loop (embarassing, isn't it?) and couldn't see that the index wasn't incremented. My colleague didn't see it either. When I finally looked at the generated code listing I saw that the loop testing had been optimized to a single unconditional jump and only then did I understand what the problem was.
发布评论
评论(10)
我是“实用程序员”中描述的技术的粉丝”。 我还使用 TDD,而不是 DBC,因为我发现它更灵活、更高效。 例如,“pragprog”中描述的一些技术包括:
它们看起来都是常识,但令人惊讶的是,当团队在以下情况下偏离这些基本原则时,速度会如此之快:面临最后期限。
I'm a fan of the techniques described in "The Pragmatic Programmer". I also use TDD, rather than DBC as I find it more flexible and productive. For example some of the techniqes described in 'pragprog' include:
They all seem like common sense, but its amazing how quickly teams deviate from these underpinning principles when faced with deadlines.
我喜欢第二双眼睛方法:在我编写并测试了一些关键代码之后,我有时会要求同事专门审查它,目的是寻找突破的方法它。
通过这种方式发挥人们的创造力很有趣。 :-)
I'm fond of the second pair of eyes method: after I've written and tested some critical code, I'll sometimes ask a coworker to review it specifically with the intent of looking for ways to break it.
It's fun to bring out people's creativity this way. :-)
听起来你已经把这两个放下了:
快速失败。 http://en.wikipedia.org/wiki/Fail-fast
故障安全。 http://en.wikipedia.org/wiki/Fail-safe
这三个可能比其他任何一个都更适合我:
尽可能避免状态。 创建和使用不可变对象 - 它们更容易测试并且不太可能背叛您。
不要编写不必要的代码。 这个很难。 查看最近大量有关 Strunk 和 White 的“风格的元素”与编程的文章。
每隔 10 分钟左右问问自己:“这很蠢吗?” 说实话。 这个比较难。
-杰森
Sounds like you already have these two down:
Fail Fast. http://en.wikipedia.org/wiki/Fail-fast
Fail Safe. http://en.wikipedia.org/wiki/Fail-safe
These three probably serve me better than any other:
Avoid state whenever possible. Create and use immutable objects- they are easier to test and less likely to betray you.
Don't write unnecessary code. This one is hard. Check out the recent bloom of articles relating "The Elements of Style" by Strunk and White to programming.
Ask yourself every 10 minutes or so: "Is this dumb?" Be honest. This one is harder.
-Jason
我实现了多种方法来防止错误并从错误中恢复:
1)处理所有异常。 正如您所说,“处理每个错误”。 如果用户单击表单上的按钮,应用程序应该不会因未处理的异常而消失(“噗”)。 出于这个原因,我用通用的 try catch 包装事件处理程序。
2) 使用完整的堆栈跟踪记录错误。 当我重新抛出异常时,我总是创建一个新异常并将捕获的异常添加为内部异常。 我的日志记录代码递归地解开消息,这比其他方式提供了更多的细节。
3) 决定你的类是否可重用。 如果没有记录下来。 考虑在代码中实现一个接口,例如
IRestartable
或IReusable
。 任何没有实现它的对象在使用一次后必须被丢弃。4)永远不要假设线程安全。 我经历了惨痛的教训才知道.NET 是极其多线程的。 许多事件是在任意线程上处理的。 用 .NET 编写的给定应用程序可能有许多同时执行的线程,并且没有一行代码显式创建线程。
5) 保持变量范围尽可能窄。 在使用对象的地方实例化对象,而不是在方法开头的大块中实例化对象。 您可能会缩短对象的寿命,并且您不会忘记位于类或方法顶部的巨大块中不需要或重用的变量。
5)很简单,但我仍然看到它发生。 像躲避瘟疫一样避免全局变量。 我见过带有数百个未使用/重用变量的代码。 弄清楚和重构是一团糟。
I implement a variety of methods to prevent and recover from errors:
1) Handle all exceptions. As you stated, "handle every error". If a user clicks a button on a form, there should be no possibility of the application just disappearing ("poof") from an unhandled exception. For that reason, I wrap event handlers with a generic try catch.
2) Log errors with full stack trace. When I rethrow an exception I always create a new one and add the caught exception as an inner exception. My logging code unwraps the messages recursively which gives a little more detail than I'd have otherwise.
3) Decide whether your classes are going to be reusable or not. If not document it. Consider implementing an interface in your code, something like
IRestartable
orIReusable
. Any object not implementing it must be thrown away after it's used once.4) Never assume thread safety. I've learned the hard way that .NET is extremely multi-threaded. Many events are handled on arbitrary threads. A given app written in .NET could have many simultaneous threads executing and not have a single line of code explicitly creating one.
5) Keep variable scope as narrow as possible. Instantiate objects near where they are used instead of in a large block at the beginning of a method. You'll potentially shorten the life of the objects and you won't forget about unneeded or reused variables sitting in a huge block at the top of the class or method.
5) Simple, but I still see it happening. Avoid globals like the plague. I've seen code with hundreds of unused/reused variables. It was a mess to figure out and refactor.
我尝试尽可能使用按合同设计。 但我发现这在我的工作领域很少实用。
I try to use Design by contract as much as possible. But I find that it's rarely practical in my field of work.
我喜欢不初始化局部变量。 应在需要时设置它们。 否则,阅读代码的程序员可能会感到困惑,就像
“嗯,为什么开头是 0...”
。 如果你不初始化它,很明显它还没有被使用。I'm a fan of not initializing local variables. They should be set when needed. Otherwise, programmers reading your code could be confused as in
"hmm why is this 0 at the beginning..."
. If you don't initialize it, it's clear it's not used yet.我喜欢...记录限制 (java)doc 中的值。 (参数可以为空吗?可以为 null 吗?)
这样,当使用我的代码时(或者当我使用我自己的代码时),我知道我可以期待什么。 简单的建议,但很少实现;)
该文档还明确区分了静态异常和运行时异常。 这样,如果程序必须尽快失败以提高鲁棒性,我知道它是否由于预见的异常而失败(静态,必须在编码时通过 catch 或重新抛出来处理),或者如果这是因为参数不正确(运行时异常,仅在应用程序生命周期内检测到)。
如果两种类型的异常都被清楚地记录下来(特别是当涉及到参数的限制值时),则整体稳健性更容易实施。
I like to... document limit values in (java)doc. (can a parameter be empty ? be null ?)
That way, when my code is used (or when I used my own code), I know what I can expect. Simple recommendation, but so rarely implemented ;)
That documentation also include a clear separation of static and runtime exceptions. That way, if the program must fail as soon as possible in order to improve robustness, I know if it fails because of a foreseen exception (static, must be dealt with at coding time trough a catch or a re-throw), or if it is because of incorrect parameters (runtime exceptions, only detected during the application lifetime).
If both types of exception are clearly documented (especially when it come to limit values for parameters), the overall robustness is easier to enforce.
如果可能的话,我会询问专门从事其他事情的人的意见。 这通常会揭示一种或多种全新的破坏事物的方法。
When it's possible, I ask the opinion of someone who specializes in something else. That often uncovers one or more entirely new ways of breaking things.
偏执是程序员的生存特质。 不断地问自己,这怎么会失败呢? 然后尝试找出如何防止这种失败。
Being paranoid is a survival trait for programmers. Constantly ask yourself how can this fail? Then try to figure out how to prevent that failure.
我编写的系统(用 C 语言)对性能和可靠性有很高的要求。 为失败而设计是很好的。 当谈到安全设计时,事情变得更加复杂。
硬件总是会出现故障,除此之外,您还会遇到从外部进入系统的无效数据的问题。 我在这些领域的解决方案通常会很复杂(为安全而设计),而在几乎所有其他领域,我的解决方案都因基本上具有极其简单的代码且根本没有数据验证而设计为失败。
所以你可以说,当风险很小时,我的设计是为了失败,当风险很高时,我的设计是为了安全。
为了调试,我有时会编写永远不应该输入的条件代码,其中包含除以零或一些这样的内容,以便当处理进入那里时,将立即调用调试器。
我通常不初始化局部堆栈变量,因为编译器告诉我哪些需要初始化,哪些可以完全省略。
代码审查很棒,但并非万无一失。 我曾经花了几个小时查看一个小(无限)循环(令人尴尬,不是吗?)并且看不到索引没有增加。 我同事也没看到。 当我最终查看生成的代码清单时,我发现循环测试已优化为单个无条件跳转,直到那时我才明白问题是什么。
The systems I write (in C) have high requirements as to performance and reliability. Designing for fail is fine. When it comes to design for safe things become more complicated.
Hardware can always fail and apart from that you have the issue of invalid data entering the system from outside. My solutions in those areas will usually be elaborate (design for safe) and in practically all other areas design for fail by essentially having extremely simple code and no data validation at all.
So you could say that I design for fail when the risks are small and for safe when they are high.
For debugging I sometimes write conditional code which should never be entered which contains a divide by zero or some such so that when processing enters there the debugger will be invoked immediately.
I usually don't initialize local stack variables since the compiler tells me which need to be initialized and which can be omitted completely.
Code reviews are great but not fool-proof. I once spent several hours looking at a small (infinite) loop (embarassing, isn't it?) and couldn't see that the index wasn't incremented. My colleague didn't see it either. When I finally looked at the generated code listing I saw that the loop testing had been optimized to a single unconditional jump and only then did I understand what the problem was.