thing_15

https://97-things-every-x-should-know.gitbooks.io/97-things-every-programmer-should-know/content/en/thing_15/

Coding with Reason
Trying to reason about software correctness by hand results in a formal proof that is longer than the code and is more likely to contain errors than the code. Automated tools are preferable, but not always possible. What follows describes a middle path: reasoning semi-formally about correctness.

The underlying approach is to divide all the code under consideration into short sections — from a single line, such as a function call, to blocks of less than ten lines — and arguing about their correctness. The arguments need only be strong enough to convince your devil’s advocate peer programmer.

A section should be chosen so that at each endpoint the state of the program (namely, the program counter and the values of all “living” objects) satisfies an easily described property, and that the functionality of that section (state transformation) is easy to describe as a single task — these will make reasoning simpler. Such endpoint properties generalize concepts like precondition and postcondition for functions, and invariant for loops and classes (with respect to their instances). Striving for sections to be as independent of one another as possible simplifies reasoning and is indispensable when these sections are to be modified.

Many of the coding practices that are well known (although perhaps less well followed) and considered ‘good’ make reasoning easier. Hence, just by intending to reason about your code, you already start thinking toward a better style and structure. Unsurprisingly, most of these practices can be checked by static code analyzers:

Avoid using goto statements, as they make remote sections highly interdependent.
Avoid using modifiable global variables, as they make all sections that use them dependent.
Each variable should have the smallest possible scope. For example, a local object can be declared right before its first usage.
Make objects immutable whenever relevant.
Make the code readable by using spacing, both horizontal and vertical. For example, aligning related structures and using an empty line to separate two sections.
Make the code self-documenting by choosing descriptive (but relatively short) names for objects, types, functions, etc.
If you need a nested section, make it a function.
Make your functions short and focused on a single task. The old 24-line limit still applies. Although screen size and resolution have changed, nothing has changed in human cognition since the 1960s.
Functions should have few parameters (four is a good upper bound). This does not restrict the data communicated to functions: Grouping related parameters into a single object benefits from object invariants and saves reasoning, such as their coherence and consistency.
More generally, each unit of code, from a block to a library, should have a narrow interface. Less communication reduces the reasoning required. This means that getters that return internal state are a liability — don’t ask an object for information to work with. Instead, ask the object to do the work with the information it already has. In other words, encapsulation is all — and only — about narrow interfaces.
In order to preserve class invariants, usage of setters should be discouraged, as setters tend to allow invariants that govern an object’s state to be broken.
As well as reasoning about its correctness, arguing about your code gives you understanding of it. Communicate the insights you gain for everyone’s benefit.

By Yechiel Kimchi

试图手动推理软件的正确性会导致比代码更长的形式证明,并且比代码更可能包含错误。自动化工具是可取的,但并非总是可行的。下面描述了一条中间道路:关于正确性的半形式推理。
基本方法是将所有正在考虑的代码分成小段——从一行代码(如函数调用)到不到十行的代码块——并争论它们的正确性。这些论点只需要足够有力就可以说服你的同行程序员。
应该选择一个部分,以便在每个端点上程序的状态(即程序计数器和所有“活动”对象的值)满足一个易于描述的属性,并且该部分的功能(状态转换)易于作为一个单独的任务来描述-这将使推理更简单。这样的端点属性概括了函数的前置条件和后置条件等概念,以及循环和类(相对于它们的实例)的不变量。努力使各部分尽可能相互独立简化了推理,并且在修改这些部分时是必不可少的。
许多众所周知(尽管可能不太受欢迎)并被认为“好”的编码实践使推理变得更容易。因此,只要打算对代码进行推理,您就已经开始考虑更好的样式和结构了。不出所料,这些实践中的大多数都可以通过静态代码分析器进行检查:
避免使用goto语句,因为它们使远程部分高度相互依赖。
避免使用可修改的全局变量,因为它们使所有使用它们的部分都是依赖的。
每个变量都应该具有尽可能小的范围。例如,本地对象可以在第一次使用之前声明。
使对象在相关时不可变。
通过使用水平和垂直间距使代码可读。例如,对齐相关结构,并使用空行分隔两个部分。
通过为对象、类型、函数等选择描述性的(但相对较短的)名称,使代码自我文档化。
如果您需要一个嵌套部分,请将其作为一个函数。
使你的职能简短,专注于一项任务。旧的24行限制仍然适用。尽管屏幕大小和分辨率都发生了变化,但自20世纪60年代以来,人类的认知没有任何变化。
函数应该有几个参数(四个是一个很好的上限)。这并不限制传递给函数的数据:将相关参数分组到一个对象中有利于对象不变量,并节省推理,例如它们的一致性和一致性。
更一般地说,从一个块到一个库,每个代码单元都应该有一个狭窄的接口。更少的沟通减少了所需的推理。这意味着返回内部状态的getter是一种负担——不要向对象询问要使用的信息。相反,要求对象使用它已经掌握的信息来完成工作。换言之,封装只是关于狭窄的接口。
为了保留类不变量,应该不鼓励使用setter,因为setter倾向于允许控制对象状态的不变量被破坏。
除了对其正确性进行推理外,对代码的争论还能让你理解它。为了每个人的利益,交流你所获得的见解。
作者:Yechiel Kimchi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值