面向条件的编程

面向条件的编程(COP)是面向合约编程的一个子域,作为一种面向函数和命令式编程的混合模式。正确的使用它,作为你武器库中的工具,方便编写安全的合约。它有助于你的合约代码完全可审计-不严格的讲-非正式的的证明有更正确的运行时操作。

Condition-Orientated Programming (COP) is subdomain of contract-orientated programming, and sits as a hybrid approach between functional and imperative programming. Done properly it is a tool in your arsenal for writing safe, secure contracts. It helps make your contract code comprehensively auditable and — loosely speaking — informally provable to have correct run-time operation.

COP不特定于某语言;它更多的是一种不严格的方法论,而不是特定的语法。尽管如此,因为有modifier和events,它特别适用于Solidity语言。
简单的说,COP只有一个目的:
函数体不包含有条件分支。
或者换一种说法:
永远不要混合状态变化和条件。

COP is not language specific; it is more of a loose methodology than particular syntax. However, with its function modifiers and events, it is particularly well-suited to the Solidity language.
Simply put, COP has just one main aim:
Function bodies should have no conditional paths.
Or alternatively:
Never mix transitions with conditions.

这听起来是一个困难的目标对于命令式语言,因为条件分支是使你达到丰富的状态变化,它使程序运行更动态化。为了实现它,我们试着分割所有的条件和它们守护的状态变化。我们单独为它们命名,然后将它们结合起来形成真正的函数。
条件分支和状态变化逻辑混合在一起的问题是,它们增加了非线性的概念到状态语义。潜在的bug隐藏起来了,当程序员认为条件(因此状态展现出来如此)是这样,但是实际上它可能有微妙的差别。

This may seem like a difficult goal to achieve for an imperative language, since conditional paths are how you achieve the rich state-transitions which allow interesting operational dynamics. To achieve it, we try to split all conditions apart from the state-transitions that they guard. We name each independently and combine to form real functions.
The problem with such conditional paths within transition logic is that they add conceptual non-linearity over state semantics. Potential bugs hide when the programmer believes a conditional (and thus the state it projects onto) means one thing when in fact it means something subtly different.

一个单层的条件已经很不好了,但是当多层条件被引入时,复杂度(也就是说程序员必须考虑现实世界中所有的状态分支)迅速增长,然后它变得很难推断整个合约的状态变化,在没有正式工具普遍使用时。
COP解决了这个问题,通过需要程序员显示地枚举所有的条件。逻辑变得扁平,没有条件的状态变化。条件片段可以被正确的文档化,复用,可以根据需求和实现来推断。重要的是,COP在编程中把预先条件当作为一等公民。

A single-level conditional is bad enough, but when multi-level conditionals are introduced, the complexity (i.e. the paths which the programmer must consider in all states of the world) increases exponentially and it quickly becomes impossible to reason about the entire contract’s state transitions without formal tools not generally available.
COP addresses this by requiring the programmer to explicitly enumerate all such conditionals. Logic becomes flattened into non-conditional state-transactions. The condition fragments can then be properly documented, reused, reasoned-about and attributed with requirements and implications. Essentially, COP uses pre-conditions as a first-class citizen in programming.

使用方法

如果你已经在使用Solidity,你已经在不经意的情况下开始和COP打交道了。让我么来看一个简单的Token合约。

How it works
If you have already used Solidity, the chances are that you inadvertently flirted with COP already. Let’s look at a simple token contract:

contract Token {
    // The balance of everyone
    mapping (address => uint) public balances;
    // Constructor - we're a millionaire!
    function Token() {
        balances[msg.sender] = 1000000;
    }
    // Transfer `_amount` tokens of ours to `_dest`.
    function transfer(uint _amount, address _dest) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
    }
}

聪明的读者可能注意到这里有一个bug:transfer 函数不能保证 sender有足够的ether在他的账户里。一般的命令式编程语言解决这个问题的方式通常是引入一个条件判断到函数体:

The astute reader will realise there is a bug here: the transfer function doesn’t ensure that the sender has enough in their account. The normal imperative-language fix for this would be to introduce a conditional into the body:

function transfer(uint _amount, address _dest) {
    if (balances[msg.sender] >= _amount) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
    }
}

Or perhaps:
或者:

function transfer(uint _amount, address _dest) {
    if (balances[msg.sender] < _amount)
        return;
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

尽管如此,这两个解决方案都没有实现COP;我们的实现方式比较混乱(无论我们选哪种),意思是,这两个例子条件判断依然在函数主体中:执行账户 msg.sender,如果有至少 _amount 的余额。作为一个COP程序员,我们清楚地明白这个问题,因为两种解决方案都破坏了我们的基本原则:
函数主体不应该包含条件分支
所以在COP中,我么应该抽象条件(balances[msg.sender] >= _amount),创建一个modifier函数:

However both of these solutions rather miss the point of COP; we’re muddling the implementation (which ever one we choose) with the meaning, which remains the same in both cases: that the executing account msg.sender, should have a balance of at least _amount. As a COP coder, we understand this problem perfectly since both solutions break our fundamental rule:
Function bodies should have no conditional paths.
So in COP, we rather abstract the condition (balances[msg.sender] >= _amount) and create a function modifier:

modifier only_with_at_least(uint x) {
    if (balances[msg.sender] >= x) _
}

这段代码本质上抽象了“执行账户必须至少有某个特定余额”的概念。当它在合适的位置时,我么不需要考虑条件方面的判断,更重要的是,我们不需要混合前置条件逻辑和状态变化的逻辑。这大大提高了人们对状态转移的可读性和可理解性。
以下是新的transfer函数:

This piece of code fundamentally abstracts the notion of “executing account has a balance of at least some particular amount”. With it in place, we no longer need to think in terms of conditionals, and most importantly, we don’t need to mix pre-condition logic with state-transition logic. This allows a far greater scope for human-understandable analysis of state-transitions.
Here’s the new transfer function:

function transfer(uint _amount, address _dest)
only_with_at_least(_amount) {
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

抽象和复用

假设我们有另外一个函数,允许任何人超过1000 ether 余额就可以对某件事投票。我们假设现在投票只是,对一个以address为key的mapping进行赋值操作。
在我们这些规定下,我们会有像这样的函数:

Abstraction and Reuse
Suppose we have another function, which allows anyone with a balance more than 1000 to vote on some issue. We’ll assume for now that voting is just a case of setting the value of an address-indexed mapping.
In our old scheme of things, we’d have a function like this:

function vote(uint _opinion) {
    if (balances[msg.sender] >= 1000) {
        votes[msg.sender] = _opinion;
    }
}

加到我们原来的代码中,我么现在有两个意思相近的条件。原则上,我们只需要一个这样的条件函数,方便审计和文档化,但是可以使用两次。使用COP:

Added to our old codebase, we would now have two similar-meaning conditionals. In principle, we would like to have only one such conditional, audited and documented once but used twice. With COP that’s exactly what we do:

function vote(uint _opinion) only_when_at_least(1000) {
    votes[msg.sender] = _opinion;
}

这使我们的投票函数有更强的可读性,而且允许我们复用守护逻辑,减少了一些潜在的表面攻击。

This makes our vote function substantially more readable and allows us to reuse important guard-logic, minimising our potential attack surface.

更复杂的交易

通过反对条件分支混在状态变化中,我们限制了状态变化的复杂度。这大大方便了审计,因为它允许我们对代码逻辑分而治之,单独的检查状态变化的逻辑,和守护这些逻辑的条件逻辑。但是有时候这些状态变化有自己的内部守护逻辑。
下面的投票例子,假设我们扩展了transfer 函数,那样我们可以确保,任何余额减少的账户没有投票权。
在传统的命令式编程语言中,我么可以简单的放置条件判断在投票减少的位置:

More complex transitions
By discouraging conditional paths from our state-transitions, we limit the complexity of our state-transitions. This hugely helps with auditing since it allows us apply the divide and conquer strategy to program logic analysis and independently check the logic of state transitions from the conditional logic on which they are gated. However sometimes the state transition itself includes gated logic internally.
Following on the voting example, suppose we extend the transfer function so that we ensure that any new, reduced, balance has no vote.
In the traditional, imperative, way we would simply place a conditional near the balance reduction code:

function transfer(uint _amount, address _dest) {
    if (balances[msg.sender] >= _amount) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
        if (balances[msg.sender] < 1000) {
            votes[msg.sender] = 0;    // Clear their vote.
        }
    }
}

这完全违背了COP的理念。尽管如此,我们不能直接通过增加modifer 函数的方式来解决,因为没有明显的函数去修改;我们实际上是想在transfer的内部域增加一个条件守护。在这种情况下(至少Solidity),我们可以创建一个新的(inline)函数:

This rather goes against the grain of COP. However we cannot address this directly with a new function modifier since there is no obvious function to modify; we actually wish to place the guard within an internal scope of transfer. In this case (at least with Solidity), we rather create a new (inline) function:

function clear_undeserved_vote(account _who)
only_with_under(1000)
only_when_voted {
    delete votes[_who];
}

注意inline在Solidity还不能使用;我么可以使用它,当它可以使用时。这个函数依赖两个modifier,它们容易编写(和审计):

Note inline is not yet available in Solidity; we would use it here whenever it be available. This function relies on two modifiers which are easily coded (and audited):

modifier only_with_under(uint x) { if (balances[msg.sender] < x) _ }
modifier only_when_voted { if (votes[msg.sender] != 0) _ }

然后,我们可以在我们的transfer函数中使用这个函数:
We can then use this function within our transfer function:

function transfer(uint _amount, address _dest)
only_with_at_least(_amount) {
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
    clear_undeserved_vote();
}

结论

我们的最终的合约代码从:

Conclusion
The main part of our final contract has changed from:

contract Token
{
    //...
    function transfer(uint _amount, address _dest) {
        if (balances[msg.sender] >= _amount) {
            balances[msg.sender] -= _amount;
            balances[_dest] += _amount;
            if (balances[msg.sender] < 1000) {
                votes[msg.sender] = 0;    // Clear their vote.
            }
        }
    }
    function vote(uint _opinion) {
        if (balances[msg.sender] >= 1000) {
            votes[msg.sender] = _opinion;
        }
    }
}

变成:

To the new:

contract Token
{
    //...
    modifier only_with_at_least(uint x) {
        if (balances[msg.sender] >= x) _
    }
    modifier only_with_under(uint x) {
        if (balances[msg.sender] < x) _
    }
    modifier only_when_voted {
        if (votes[msg.sender] != 0) _
    }
    function clear_undeserved_vote(account _who)
    only_with_under(1000) only_when_voted {
        delete votes[_who];
    }
    function transfer(uint _amount, address _dest)
    only_with_at_least(_amount) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
        clear_undeserved_vote();
    }
    function vote(uint _opinion)
    only_when_at_least(1000) {
        votes[msg.sender] = _opinion;
    }
}

现在代码有一点长,但是它强制程序员文档化内部实现,鼓励他们考虑分离条件和抽象重要的代码逻辑,确保没有隐患的copy/paste bugs。执行部分是扁平的,减少了概念打包。它容易被文档化和审计,可以一小块一小块的,全面而有条不紊的来进行。而且,即使没有被文档化,它也更易懂,根据条件的名字,而不是像原来那样将他们和状态变化逻辑混合在一起。

The code we have is somewhat longer, however it has now forced the coder to document the internals, encouraging them to place weight on considering the conditions in isolation and abstracts the important parts to ensure that no copy/paste bugs creep in. The execution structure is flat, easing the conceptual baggage that the auditor must consider. It can be documented and audited, piece-by-piece in a comprehensive and methodical fashion. And, even if left undocumented, it is far more comprehensible, with the named conditions over the original version which muddles them into the transition logic.

啰嗦的讲,COP肯定不是符合每个人的口味。在缺乏特定语言支持下,在巨大的合约中它可能变的有些笨重。但是,对于小和中等规模的合约,它提供给程序员和审计者清晰的方法可循,否则某些目标将比较难达到。

Being rather verbose, COP certainly won’t be to everyone’s taste. And without certain language support, it can become somewhat unwieldy with large contracts. However, for small and medium-size contracts it provides the programmer and auditor with a clear path to enlightenment that can otherwise be difficult to attain.

在下一篇文章中,在此系列中,我将会带来一个真实世界的合约,使用COP风格,来展示代码如何被分解,文档化,非正式地证明这种方式是正确的。

In the next article in this series I’ll take a “real-world” contract, coded in COP style, and show how it can be broken down, documented and informally demonstrated to be correct.

原文:https://medium.com/@gavofyork/condition-orientated-programming-969f6ba0161a

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,395评论 0 13
  • 2016年10月12日,星期三,天气阴,晩上九点, 我在图书馆想念我的小黑。 她是小黑,我是大白。那一年,我们高...
    单宋文阅读 436评论 0 1
  • 秋霜念想横空出世 傍在寒露的门旁 举目向四野观看 菊怒放的花瓣像太阳的光芒 秋霜腻着寒露 躺在草丛许诺永生难忘 忽...
    曹天成阅读 234评论 15 8
  • 一 理想,在我的人生里永远都是不确定的,年少时给自己设定了一个又一个的小理想,今天觉得自己有绘画的天...
    心急吃不了热豆腐阅读 114评论 0 0
  • 天山天池 碧水瑶池飞觴 闲云仙台斗方 无瑕美玉合璧 心底澄明万丈
    洛希亚阅读 199评论 0 0