V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gitandgit
V2EX  ›  区块链

Clarity 智能合约开发语言编程风格

  •  
  •   gitandgit · 2022-02-12 11:39:10 +08:00 · 974 次点击
    这是一个创建于 1022 天前的主题,其中的信息可能已经有所发展或是发生改变。

    该教程的原英文文档: https://book.clarity-lang.org/ch14-01-coding-style.html

    编程风格

    这里有一些关于 Clarity 编程风格的建议。如果编程风格的 SIPs 标准被批准,那么 SIPs 标准将被包含在这里。所以,这一章就没有被命名为由社区主导的编程 Clarity 指南。在那之前,我们只能根据长期开发者的经验,做出最大的努力。

    Begin 函数的编程风格

    大多数函数的输入量都是不变的。因此,有些开发者有过度使用 begin 函数的倾向。如果你的 begin 函数只包含一个表达式,那么你可以把它去掉。

    (define-public (get-listing (id uint))
        (begin
            (ok (map-get? listings {id: id}))
        )
    )
    
    

    应该去掉 begin 函数的代码:

    (define-public (get-listing (id uint))
        (ok (map-get? listings {id: id}))
    )
    
    

    实际上,begin 函数是有运行时成本的,所以不使用它,会使你的智能合约调用更便宜。其他不变函数表达式中的 begin 函数也是如此。

    >> ::get_costs (+ 1 2)
    +----------------------+----------+------------+
    |                      | Consumed | Limit      |
    +----------------------+----------+------------+
    | Runtime              | 4000     | 5000000000 |
    +----------------------+----------+------------+
    3
    
    >> ::get_costs (begin (+ 1 2))
    +----------------------+----------+------------+
    |                      | Consumed | Limit      |
    +----------------------+----------+------------+
    | Runtime              | 6000     | 5000000000 |
    +----------------------+----------+------------+
    3
    
    

    嵌套的 let 函数

    let 函数允许我们定义局部变量。如果你不得不多次读取数据或者重新做一次计算,那么它就很有用。变量表达式实际上是按顺序计算的,这意味着后面的变量表达式可以引用前面的表达式。因此,如果你想做的只是根据一些先前的变量来计算一个值,就没有必要嵌套多个 let 表达式。

    (let
        (
        (value-a u10)
        (value-b u20)
        (result (* value-a value-b))
        )
        (ok result)
    )
    
    

    避免使用*-panic 函数

    有多种方法来解包值,但一般应避免使用 unwrap-panic 和 unwrap-err-panic 函数。如果它们不能解开提供的值,就会以运行时间错误而中止调用。运行时间错误不会给调用智能合约的应用程序提供任何有意义的信息,并使错误处理更加困难。只要有可能,就使用 unwrap !和 unwrap-err !这 2 个函数,来显示有意义的错误代码信息。

    比较下面例子中的函数 update-name 和 update-name-panic 。

    (define-public (update-name (id uint) (new-name (string-ascii 50)))
        (let
            (
                ;; Emits an error value when the unwrap fails.
                (listing (unwrap! (get-listing id) err-unknown-listing))
            )
            (asserts! (is-eq tx-sender (get maker listing)) err-not-the-maker)
            (map-set listings {id: id} (merge listing {name: new-name}))
            (ok true)
        )
    )
    
    (define-public (update-name-panic (id uint) (new-name (string-ascii 50)))
        (let
            (
                ;; No meaningful error code is emitted if the unwrap fails.
                (listing (unwrap-panic (get-listing id)))
            )
            (asserts! (is-eq tx-sender (get maker listing)) err-not-the-maker)
            (map-set listings {id: id} (merge listing {name: new-name}))
            (ok true)
        )
    )
    
    

    最好将 unwrap-panic 和 unwrap-err-panic 函数的使用限制在你已经事先知道解包不会失败的情况下(例如,有一个事先的防护措施)。

    避免使用 if 函数

    说实话,我们实际上不能避免使用 if 函数。但无论何时你使用它,都要问问自己是否真的需要它。很多时候,你可以重构代码,用 asserts !或 try !函数来代替 if 函数。新的开发者往往喜欢会创建嵌套的 if 结构,因为他们需要按顺序检查多个条件。这些结构变得非常难懂,而且容易出错。

    举例说明:

    (define-public (update-name (new-name (string-ascii 50)))
        (if (is-eq tx-sender contract-owner)
            (ok (var-set contract-name new-name))
            err-not-contract-owner
        )
    )
    
    

    上面的代码可以被修改成:

    (define-public (update-name (new-name (string-ascii 50)))
        (begin
            (asserts! (is-eq tx-sender contract-owner) err-not-contract-owner)
            (ok (var-set contract-name new-name))
        )
    )
    
    

    多个嵌套的 if 表达式通常写成这样样式。

    (define-public (some-function)
        (if bool-expr-A
            (if bool-expr-B
                (if bool-expr-C
                    (ok (process-something))
                    if-C-false
                )
                if-B-false
            )
            if-A-false
        )
    )
    
    

    也可以跟下面的代码做比较:

    (define-public (some-function)
        (begin
            (asserts! bool-expr-A if-A-false)
            (asserts! bool-expr-B if-B-false)
            (asserts! bool-expr-C if-C-false)
            (ok (process-something))
        )
    )
    
    

    什么时候使用 match 函数和什么时候不应该使用 match 函数

    match 是一个非常强大的函数,但在许多情况下,一个 try !函数就足够用了。通常观察到的代码样式是这样的。

    (match (some-expression)
        success (ok success)
        error (err error)
    )
    
    

    对其来说,函数上的简化无非是函数调用本身。

    (some-expression)
    
    

    match 函数解开 response 函数的结果,然后用解开的 ok 或 err 值进入成功或失败分支。因此,立即返回这些值是没有意义的。

    下面是一个在主网智能合约中发现的 transfer 函数使用的真实案例:

    (define-public (transfer (token-id uint) (sender principal) (recipient principal))
        (if (and (is-eq tx-sender sender))
            (match (nft-transfer? my-nft token-id sender recipient)
                success (ok success)
                error (err error))
            (err u500)
        )
    )
    
    

    重构 if 函数和 match 函数,我们可以把代码写成这样:

    (define-public (transfer (token-id uint) (sender principal) (recipient principal))
        (begin
            (asserts! (is-eq tx-sender sender) (err u500))
            (nft-transfer? my-nft token-id sender recipient)
        )
    )
    
    
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1985 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 00:26 · PVG 08:26 · LAX 16:26 · JFK 19:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.