Sui加强Lint和代码不规范提示工具,提高编码速度

Sui推出新的开发者工具,包括linters和编译器warnings,为开发者在编码过程中提供有用的提示。

Sui加强Lint和代码不规范提示工具,提高编码速度

Sui推出新的代码检查工具linter和增强警告信息warning,提升了Move开发者的体验,为许多编程系统中常见的编码提供支持。新增的六个linters主要处理对象方面的问题,可以发现Sui特定代码中潜在的问题模式。此外,Move编译器现在还包括有关未使用结构体的警告,这对于Move开发者是很有帮助的。 

这些新增功能有助于新手和经验丰富的开发者在构建利用Sui创新技术的apps时避免代码问题。这些linter和warning都包括忽略选项,允许开发者自定义其工作流程。

Linting支持

许多开发人员在工作时都会依赖衬垫来发现代码中的问题,尤其是在很少进行代码审查的高产出环境中。然而,由于Move 相对较新,它还没有享受到那些已经存在多年的语言中常见的编码辅助工具的支持。

在Sui上的这一新框架中,目前代码检查支持是自愿选择的。开发人员需要在任何构建、测试、发布和升级命令中指定 —-lint ,以查看linter消息。在将来的版本中,这个框架将切换到默认开启的模式—-no-lint,以便linter消息的默认显示。

目前,Sui支持六种不同的linters,后续还会增加更多。未来的linters发展将受到社区反馈的指导。

1.Coin字段linter

这个分析工具会标记在其他对象和结构字段中使用 sui::coin::Coin 对象的情况。在大多数情况下,开发人员应该使用 sui::balance::Balance 来节省空间。

举个例子,在这个定义了 sui::coin::Coin type:

#[allow(unused_field)]
module coin_field::test {
    struct S1 {}

    struct S2 has key, store {
        id: sui::object::UID,
        c: sui::coin::Coin<S1>,
    }
}

构建此模块会导致以下linter消息:

warning[Lint W03001]: sub-optimal 'sui::coin::Coin' field type
  ┌─ ./sources/test.move:5:12
  │
5 │     struct S2 has key, store {
  │            ^^ The field 'c' of 'S2' has type 'sui::coin::Coin'
6 │         id: sui::object::UID,
7 │         c: sui::coin::Coin<S1>,
  │         - Storing 'sui::balance::Balance' in this field will typically be more space-efficient
  │
  = This warning can be suppressed with '#[lint_allow(coin_field)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

注意,源代码中的 #[allow(unused_field)]注解用于忽略未使用字段的警告,以使输出更加简洁。下文的“增强警告”部分将详细介绍新警告以及如何忽略警告和linter消息。 

2.集合相等性linter

此linter会标记在某些情况下对集合进行相等性比较,比如 sui::table::Table, sui::table_vec::TableVec, sui::bag::Bag 等。此linter存在的理由在于,这种类型的比较并不是很有用,而且没有考虑到结构上的相等性。

举例来说,这个基本模块包含一个函数,该函数尝试比较两个不同的 sui::bag::Bag:

module collection_eq::test {
    public fun bag_eq(bag1: &sui::bag::Bag, bag2: &sui::bag::Bag): bool {
        bag1 == bag2
    }
}

构建此模块会导致以下linter消息:

warning[Lint W05001]: possibly useless collections compare
  ┌─ ./sources/test.move:3:14
  │
3 │         bag1 == bag2
  │              ^^ Comparing collections of type 'sui::bag::Bag' may yield unexpected result.
  │
  = Equality for collections of type 'sui::bag::Bag' IS NOT a structural check based on content
  = This warning can be suppressed with '#[lint_allow(collection_equality)]' applied to the 'module' or module member ('const', 'fun', or 'struct')            

3.自定义状态更改linter

此linter会标记在已经具有 store 功能且开发者可以使用这些调用的对象上,存在对转移、共享和冻结调用的潜在自定义实现。在这种情况下,使用这些调用可能会有风险,因为自定义的转移、共享和冻结操作在这种情况下无法执行。如果一个函数被认为是潜在的自定义实现,那么它会将一个在给定模块中具有 store 功能的结构类型实例作为参数,然后将其作为私有转移、共享和冻结调用的参数传递。

举例来说,在一个包含尝试使用公共 sui::transfer::transfer 函数来传递具有 store 功能的对象作为参数的简单模块:

#[allow(unused_field)]
module custom_state_change::test {
    struct S1 has key, store {
        id: sui::object::UID
    }

    public fun custom_transfer(o: S1, a: address) {
        sui::transfer::transfer(o, a)
    }
}

构建此模块会导致以下linter消息:

warning[Lint W02001]: potentially unenforceable custom transfer/share/freeze policy
  ┌─ ./sources/test.move:7:16
  │
7 │     public fun custom_transfer(o: S1, a: address) {
  │                ^^^^^^^^^^^^^^^ - An instance of a module-private type with a store ability to be transferred coming from here
  │                │                
  │                Potential unintended implementation of a custom transfer function.
8 │         sui::transfer::transfer(o, a)
  │                        -------- Instances of a type with a store ability can be transferred using the public_transfer function which often negates the intent of enforcing a custom transfer policy
  │
  = A custom transfer policy for a given type is implemented through calling the private transfer function variant in the module defining this type
  = This warning can be suppressed with '#[lint_allow(custom_state_change)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

4.冻结包装linter

此linter会标记冻结包含(不论是否具有传递性)内部对象的对象。换句话说,它会标记对象的冻结,而这些对象的字段(直接或非直接)包含其他对象。冻结这种对象会防止内部对象的解包。

举例来说,在一个包含尝试冻结类型为Wrapper的对象的基本模块,其中Wrapper包含另一个对象类型的字段 Inner:

#[allow(unused_field)]
module freeze_wrapped::test {
    struct Inner has key, store {
        id: sui::object::UID
    }

    struct Wrapper has key, store {
        id: sui::object::UID,
        inner: Inner,
    }

    public fun freeze(w: Wrapper) {
        sui::transfer::public_freeze_object(w);
    }
}

构建此模块会导致以下linter消息:

warning[Lint W04001]: attempting to freeze wrapped objects
   ┌─ ./sources/test.move:13:45
   │
 9 │         inner: Inner,
   │                ----- The field of this type is a wrapped object
   ·
13 │         sui::transfer::public_freeze_object(w);
   │                                             ^ Freezing an object of type 'Wrapper' also freezes all objects wrapped in its field 'inner'.
   │
   = This warning can be suppressed with '#[lint_allow(freeze_wrapped)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

5.转移给自己linter

此linter标记将对象转账给从 sui::tx_context::sender() 调用获取的交易发送方。此linter的目标是鼓励开发人员从函数中返回对象,而不是将它们转移给交易发送方。从函数中返回对象可以通过允许调用者直接使用返回的对象来增加 可编程交易区块 中函数的可组合性。

举例来说,在一个包含尝试将新创建的对象转移给交易发送方的简单模块中:

module self_transfer::test {
    struct S1 has key, store {
        id: sui::object::UID
    }

    public fun public_transfer(ctx: &mut sui::tx_context::TxContext) {
        let o = S1 { id: sui::object::new(ctx) };
        sui::transfer::public_transfer(o, sui::tx_context::sender(ctx))
    }
}

构建此模块会导致以下linter消息:

warning[Lint W01001]: non-composable transfer to sender
  ┌─ ./sources/test.move:8:9
  │
6 │     public fun public_transfer(ctx: &mut sui::tx_context::TxContext) {
  │                --------------- Returning an object from a function, allows a caller to use the object and enables composability via programmable transactions.
7 │         let o = S1 { id: sui::object::new(ctx) };
8 │         sui::transfer::public_transfer(o, sui::tx_context::sender(ctx))
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  │         │                                 │
  │         │                                 Transaction sender address coming from here
  │         Transfer of an object to transaction sender address in function public_transfer
  │
  = This warning can be suppressed with '#[lint_allow(self_transfer)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

6.共享和独享对象linter

此linter标记将作为函数参数传递或从可共享对象的拆包中产生的对象(这些对象很可能已经是独享对象),这将导致中止。建议的模式是在同一函数中创建一个新对象并在其中共享它。通常,以按值传递给函数的任何对象都是独享对象。

举例来说,考虑一个包含尝试共享作为参数传递对象的基本模块(该对象的数据流在函数中被跟踪):

#[allow(unused_field)]
module unused::test {
    struct Obj has key, store {
        id: sui::object::UID
    }

    public fun arg_object(o: Obj) {
        let arg = o;
        sui::transfer::public_share_object(arg);
    }
}

构建此模块会导致以下linter消息:

warning[Lint W00001]: possible owned object share
  ┌─ ./sources/test.move:9:9
  │
7 │     public fun arg_object(o: Obj) {
  │                           - A potentially owned object coming from here
8 │         let arg = o;
9 │         sui::transfer::public_share_object(arg);
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  │         │                                  │
  │         │                                  Creating a fresh object and sharing it within the same function will ensure this does not abort.
  │         Potential abort from a (potentially) owned object created by a different transaction.
  │
  = This warning can be suppressed with '#[lint_allow(share_owned)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

增强警告

除了linters及其警告之外,Move编译器现在还通过警告来帮助开发人员识别未使用的结构体,包括未使用的函数、常量、(函数)类型参数以及结构/对象字段。尽管许多开发人员认为这些警告很有帮助,但它们可能不受欢迎,特别是对于之前编译而没有任何警告的代码。希望忽略特定代码元素的这些警告的开发人员可以为模块、常量、类型定义或函数指定注释(形式为 #[...])。

警告忽略示例

以下代码提供了生成一个新警告的示例。这个基本模块定义了一个未使用的常量:

module unused::test {
    const UNUSED_CONST: u64 = 42;
}

构建这个模块将产生以下警告:

warning[W09011]: unused constant
┌─ ./sources/test.move:2:11
│
2 │  const UNUSED_CONST: u64 = 42;
│  ^^^^^^^^^^^^ The constant 'UNUSED_CONST' is never used. Consider removing it.
  │
  = This warning can be suppressed with '#[allow(unused_const)]' applied to the 'module' or module member ('const', 'fun', or 'struct')

编译器返回了有关如何忽略特定警告的建议。在这种情况下,在常量级别放置 #[allow(unused_const)] 注释,如下所示:

module unused::test {
    #[allow(unused_const)]
    const UNUSED_CONST: u64 = 42;
}

在模块级别包含注释可以忽略相同类型的多个警告:

#[allow(unused_const)]
module unused::test {
    const UNUSED_CONST: u64 = 42;
}

忽略Linter

开发人员可以以与标准编译器警告类似的方式忽略linter消息。linter消息包括开发人员可以使用来忽略它的注释描述。用于忽略linter警告的注释中使用的关键字是 lint_allow ,而用于忽略标准警告的关键字是 allow。如果开发人员选择忽略linter,编译器还会打印关于它忽略了多少条消息以及多少种消息的简单统计信息。

在Sui上进行开发

Move 编程语言在四年前刚刚问世。尽管该语言的安全性和实用性使其立即受到了构建者社区的欢迎,但其开发工具的数量却落后于更成熟的语言。在Sui 上添加 linters 和编译器警告(有些是专门为Move 量身定做的)只是改善开发者体验的众多努力中的一项。

然而,Move的开源性质意味着整个开发者社区都可以并且应该参与改进开发者体验。人们可以通过在论坛或其他场合表达需求,或者通过向存储库提交新的linters实现提案或其他语言改进请求来贡献。不管社区希望以哪种方式做出贡献,参与Sui论坛是帮助开发Move的第一步。