実行中に中断が発生すると、トランザクションは失敗し、中断コードが呼び出し元に返されます。Move VMは、トランザクションを中断したモジュール名と中断コードを返します。この挙動は、トランザクションの呼び出し元にとって完全に透明ではありません。特に、単一の関数が中断可能な同じ関数への複数の呼び出しを含む場合、呼び出し元はどの呼び出しがトランザクションを中断したのかを知ることができず、問題のデバッグやユーザーへの意味のあるエラーメッセージの提供が困難になります。
module book::module_a {
use book::module_b;
public fun do_something() {
let field_1 = module_b::get_field(1); // 0で中断する可能性あり
/* ... 多くのロジック ... */
let field_2 = module_b::get_field(2); // 0で中断する可能性あり
/* ... さらにいくつかのロジック ... */
let field_3 = module_b::get_field(3); // 0で中断する可能性あり
}
}
上記の例は、単一の関数が中断可能な複数の呼び出しを含むケースを示しています。do_something関数の呼び出し元が中断コード0を受け取った場合、どのmodule_b::get_fieldの呼び出しがトランザクションを中断したのかを理解するのが難しくなります。この問題に対処するために、エラーハンドリングを改善する一般的なパターンがあります。
安全に操作が可能かどうかを示すブール値を返す「チェック」関数を提供することは良い実践とされています。もしmodule_bがフィールドが存在するかどうかを示すブール値を返すhas_field
関数を提供していれば、do_something
関数は以下のように書き換えることができます:
module book::module_a {
use book::module_b;
const ENoField: u64 = 0;
public fun do_something() {
assert!(module_b::has_field(1), ENoField);
let field_1 = module_b::get_field(1);
/* ... */
assert!(module_b::has_field(2), ENoField);
let field_2 = module_b::get_field(2);
/* ... */
assert!(module_b::has_field(3), ENoField);
let field_3 = module_b::get_field(3);
}
}
各get_field
呼び出し前にカスタムチェックを追加することで、module_aの開発者はエラーハンドリングをコントロールでき、第二のルールを実装することが可能です。
中断コードが呼び出し元モジュールによって処理されるようになったら、異なるシナリオに対して異なる中断コードを使用します。これにより、呼び出し元モジュールはユーザーに意味のあるエラーメッセージを提供することができます。module_aは以下のように書き換えることができます:
module book::module_a {
use book::module_b;
const ENoFieldA: u64 = 0;
const ENoFieldB: u64 = 1;
const ENoFieldC: u64 = 2;
public fun do_something() {
assert!(module_b::has_field(1), ENoFieldA);
let field_1 = module_b::get_field(1);
/* ... */
assert!(module_b::has_field(2), ENoFieldB);
let field_2 = module_b::get_field(2);
/* ... */
assert!(module_b::has_field(3), ENoFieldC);
let field_3 = module_b::get_field(3);
}
}
例えば、呼び出し元が中断コード0を受け取った場合、「フィールド1が存在しない」と訳すことができます。中断コード1を受け取った場合は、「フィールド2が存在しない」となります。