智能合约编写之智能合约编写之Solidity的高级特性的高级特性
合理控制函数和变量的类型
基于最少知道原则(Least Knowledge Principle)中经典面向对象编程原则,一个对象应该对其他对象保持最少的了解。优秀
的 Solidity 编程实践也应符合这一原则:每个合约都清晰、合理地定义函数的可见性,暴露最少的信息给外部,做好对内部函
数可见性的管理。
同时,正确地修饰函数和变量的类型,可给合约内部数据提供不同级别的保护,以防止程序中非预期的操作导致数据产生错
误;还能提升代码的可读性与质量,减少误解和 bug;更有利于优化合约执行的成本,提升链上资源的使用效率。
守住函数操作的大门:函数可见性
Solidity 有两种函数调用方式:
内部调用:又被称为『消息调用』。常见的有合约内部函数、父合约的函数以及库函数的调用。(例如,假设 A 合约中存在 f
函数,则在 A 合约内部,其他函数调用 f 函数的调用方式为 f()。)
外部调用:又被称为『EVM 调用』。一般为跨合约的函数调用。在同一合约内部,也可以产生外部调用。(例如,假设 A 合
约中存在 f 函数,则在 B 合约内可通过使用 A.f() 调用。在 A 合约内部,可以用 this.f() 来调用)。
函数可以被指定为 external ,public ,internal 或者 private 标识符来修饰。
基于以上表格,我们可以得出函数的可见性 public > external > internal > private。
另外,如果函数不使用上述类型标识符,那么默认情况下函数类型为 public。
综上所述,我们可以总结一下以上标识符的不同使用场景:
public,公有函数,系统默认。通常用于修饰可对外暴露的函数,且该函数可能同时被内部调用。
external,外部函数,推荐只向外部暴露的函数使用。当函数的某个参数非常大时,如果显式地将函数标记为 external,可以
强制将函数存储的位置设置为 calldata,这会节约函数执行时所需存储或计算资源。
internal,内部函数,推荐所有合约内不对合约外暴露的函数使用,可以避免因权限暴露被攻击的风险。
private,私有函数,在极少数严格保护合约函数不对合约外部开放且不可被继承的场景下使用。
不过,需要注意的是,无论用何种标识符,即使是 private,整个函数执行的过程和数据是对所有节点可见,其他节点可以验
证和重放任意的历史函数。实际上,整个智能合约所有的数据对区块链的参与节点来说都是透明的。
刚接触区块链的用户常会误解,在区块链上可以通过权限控制操作来控制和保护上链数据的隐私。
这是一种错误的观点。事实上,在区块链业务数据未做特殊加密的前提下,区块链同一账本内的所有数据经过共识后落盘到所
有节点上,链上数据是全局公开且相同的,智能合约只能控制和保护合约数据的执行权限。
如何正确地选择函数修饰符是合约编程实践中的『必修课』,只有掌握此节真谛方可自如地控制合约函数访问权限,提升合约
安全性。
对外暴露最少的必要信息:变量的可见性
与函数一样,对于状态变量,也需要注意可见性修饰符。状态变量的修饰符默认是 internal,不能设置为 external。此外,当
状态变量被修饰为 public,编译器会生成一个与该状态变量同名的函数。
具体可参考以下示例:
这个机制有点像 Java 语言里 lombok 库所提供的 @Getter 注解,默认为一个 POJO 类变量生成 get 函数,大大简化了某些合
约代码的书写。
同样,变量的可见性也需要被合理地修饰,不该公开的变量果断用 private 修饰,使合约代码更符合『最少知道』的设计原
则。