moonbit-refactoring
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMoonBit Refactoring Skill
MoonBit 重构技能
Intent
意图
- Preserve behavior and public contracts unless explicitly changed.
- Minimize the public API to what callers require.
- Prefer declarative style and pattern matching over incidental mutation.
- Use view types (ArrayView/StringView/BytesView) to avoid copies.
- Add tests and docs alongside refactors.
- 除非显式修改,否则保留原有行为和公共契约。
- 将公共API缩减至调用方所需的最小范围。
- 优先使用声明式风格和模式匹配,避免不必要的可变修改。
- 使用视图类型(ArrayView/StringView/BytesView)避免拷贝。
- 重构过程中同步补充测试和文档。
Workflow
工作流
Start broad, then refine locally:
- Architecture first: Review package structure, dependencies, and API boundaries.
- Inventory public APIs and call sites (,
moon doc).moon ide find-references - Pick one refactor theme (API minimization, package splits, pattern matching, loop style).
- Apply the smallest safe change.
- Update docs/tests in the same patch.
- Run , then
moon check.moon test - Use coverage to target missing branches.
Avoid local cleanups (renaming, pattern matching) until the high-level structure is sound.
先全局梳理,再局部优化:
- 架构优先:审核包结构、依赖关系和API边界。
- 盘点公共API和调用位点(、
moon doc)。moon ide find-references - 选定一个重构主题(API最小化、包拆分、模式匹配、循环风格)。
- 执行最小粒度的安全变更。
- 在同一次提交中更新文档/测试。
- 先执行,再执行
moon check。moon test - 使用覆盖率定位未覆盖的分支。
在高层结构稳定前,不要进行局部清理(重命名、模式匹配替换)。
Improve Package Architecture
优化包架构
- Keep packages focused: aim for <10k lines per package.
- Keep files manageable: aim for <2k lines per file.
- Keep functions focused: aim for <200 lines per function.
- 保持包职责聚焦:每个包建议行数<1万行。
- 保持文件可维护:每个文件建议行数<2000行。
- 保持函数职责单一:每个函数建议行数<200行。
Splitting Files
拆分文件
Treat files in MoonBit as organizational units; move code freely within a package as long as each file stays focused on one concept.
将MoonBit中的文件视为组织单元,只要每个文件始终聚焦单个概念,你可以在包内自由移动代码。
Splitting Packages
拆分包
When spinning off package into and :
AAB-
Create the new package and re-export temporarily:mbt
// In package B using @A { ... } // re-export A's APIsEnsurepasses before proceeding.moon check -
Find and update all call sites:bash
moon ide find-references <symbol>Replace barewithf.@B.f -
Remove thestatement once all call sites are updated.
use -
Audit and remove newly-unusedAPIs from both packages.
pub
当要将包拆分为和两个包时:
AAB-
创建新包并临时重导出:mbt
// In package B using @A { ... } // re-export A's APIs确保通过后再继续后续操作。moon check -
查找并更新所有调用位点:bash
moon ide find-references <symbol>将裸写的替换为f。@B.f -
所有调用位点更新完成后,移除语句。
use -
审计并移除两个包中新的未使用API。
pub
Guidelines
指导原则
- Prefer acyclic dependencies: lower-level packages should not import higher-level ones.
- Only expose what downstream packages actually need.
- Consider an package for helpers that shouldn't leak.
internal/
- 优先使用无环依赖:底层包不应导入上层包。
- 仅暴露下游包实际需要的内容。
- 可以考虑使用包存放不对外暴露的辅助工具。
internal/
Minimize Public API and Modularize
最小化公共API并模块化
- Remove from helpers; keep only required exports.
pub - Move helpers into packages to block external imports.
internal/ - Split large files by feature; files do not define modules in MoonBit.
- 移除辅助工具的修饰符,仅保留必要的导出。
pub - 将辅助工具移入包,阻止外部导入。
internal/ - 按功能拆分大文件;MoonBit中文件不定义模块。
Local refactoring
局部重构
Convert Free Functions to Methods + Chaining
将自由函数转换为方法+链式调用
- Move behavior onto the owning type for discoverability.
- Use for fluent, mutating chains when it reads clearly.
..
Example:
mbt
// Before
fn reader_next(r : Reader) -> Char? { ... }
let ch = reader_next(r)
// After
#as_free_fn(reader_next, deprecated="Use Reader::next instead")
fn Reader::next(self : Reader) -> Char? { ... }
let ch = r.next()To make the transition smooth, place on the method; it emits a deprecated free function
that forwards to the method.
Then you can check call sites and update them gradually by looking at warnings.
Example (chaining):
#as_free_fn(old_name, ...)old_namembt
buf..write_string("#\\")..write_char(ch)- 将行为挂载到所属类型上,提升可发现性。
- 可读性好的场景下使用实现流畅的可变链式调用。
..
示例:
mbt
// Before
fn reader_next(r : Reader) -> Char? { ... }
let ch = reader_next(r)
// After
#as_free_fn(reader_next, deprecated="Use Reader::next instead")
fn Reader::next(self : Reader) -> Char? { ... }
let ch = r.next()为了实现平滑过渡,可以在方法上添加标注,它会生成一个废弃的自由函数,转发请求到新方法。之后你可以通过查看警告,逐步检查并更新调用位点。
链式调用示例:
#as_free_fn(old_name, ...)old_namembt
buf..write_string("#\\")..write_char(ch)Prefer Explicit Qualification
优先使用显式限定
- Use instead of
@pkg.fnwhen clarity matters.using - Keep call sites explicit during wide refactors.
Example:
mbt
let n = @parser.parse_number(token)- 可读性要求高的场景下,使用而非
@pkg.fn。using - 大范围重构期间保持调用位点显式化。
示例:
mbt
let n = @parser.parse_number(token)Simplify Enum Constructors When Type Is Known
类型已知时简化枚举构造器写法
When the expected type is known from context, you can omit the full package path for enum constructors:
- Pattern matching: Annotate the matched value; constructors need no path.
- Nested constructors: Only the outermost needs the full path.
- Return values: The return type provides context for constructors in the body.
- Collections: Type-annotate the collection; elements inherit the type.
Examples:
mbt
// Pattern matching - annotate the value being matched
let tree : @pkga.Tree = ...
match tree {
Leaf(x) => x
Node(left~, x, right~) => left.sum() + x + right.sum()
}
// Nested constructors - only outer needs full path
let x = @pkga.Tree::Node(left=Leaf(1), x=2, right=Leaf(3))
// Return type provides context
fn make_tree() -> @pkga.Tree {
Node(left=Leaf(1), x=2, right=Leaf(3))
}
// Collections - type annotation on the array
let trees : Array[@pkga.Tree] = [Leaf(1), Node(left=Leaf(2), x=3, right=Leaf(4))]当上下文可以推断出预期类型时,你可以省略枚举构造器的完整包路径:
- 模式匹配:为被匹配的值添加类型注解,构造器无需写路径。
- 嵌套构造器:仅最外层构造器需要完整路径。
- 返回值:返回类型可以为函数体中的构造器提供上下文。
- 集合:为集合添加类型注解,元素会继承该类型。
示例:
mbt
// Pattern matching - annotate the value being matched
let tree : @pkga.Tree = ...
match tree {
Leaf(x) => x
Node(left~, x, right~) => left.sum() + x + right.sum()
}
// Nested constructors - only outer needs full path
let x = @pkga.Tree::Node(left=Leaf(1), x=2, right=Leaf(3))
// Return type provides context
fn make_tree() -> @pkga.Tree {
Node(left=Leaf(1), x=2, right=Leaf(3))
}
// Collections - type annotation on the array
let trees : Array[@pkga.Tree] = [Leaf(1), Node(left=Leaf(2), x=3, right=Leaf(4))]Pattern Matching and Views
模式匹配与视图
- Pattern match arrays directly; the compiler inserts ArrayView implicitly.
- Use in the middle to match prefix and suffix at once.
.. - Pattern match strings directly; avoid converting to .
Array[Char] - /
Stringindexing yieldsStringViewcode units. UseUInt16for Unicode-aware iteration.for ch in s
- 直接对数组做模式匹配,编译器会隐式插入ArrayView。
- 可以在中间使用同时匹配前缀和后缀。
.. - 直接对字符串做模式匹配,避免转换为。
Array[Char] - /
String的索引返回StringView码元,使用UInt16实现Unicode感知的迭代。for ch in s
We Prefer Pattern Matching Over Small Functions
我们优先使用模式匹配而非小函数
For example,
mbt
match gen_results.get(0) {
Some(value) => Iter::singleton(value)
None => Iter::empty()
}We can pattern match directly; it is often clearer and equally readable:
mbt
match gen_results {
[value, ..] => Iter::singleton(value)
[] => Iter::empty()
}MoonBit pattern matching is pretty expressive, here are some more examples:
mbt
match items {
[] => ()
[head, ..tail] => handle(head, tail)
[..prefix, mid, ..suffix] => handle_mid(prefix, mid, suffix)
}mbt
match s {
"" => ()
[.."let", ..rest] => handle_let(rest)
_ => ()
}例如:
mbt
match gen_results.get(0) {
Some(value) => Iter::singleton(value)
None => Iter::empty()
}我们可以直接做模式匹配,通常更清晰易读:
mbt
match gen_results {
[value, ..] => Iter::singleton(value)
[] => Iter::empty()
}MoonBit的模式匹配表达能力很强,这里有更多示例:
mbt
match items {
[] => ()
[head, ..tail] => handle(head, tail)
[..prefix, mid, ..suffix] => handle_mid(prefix, mid, suffix)
}mbt
match s {
"" => ()
[.."let", ..rest] => handle_let(rest)
_ => ()
}Char literal matching
字符字面量匹配
Use char literal overloading for , , and ; the examples below rely on it. This is handy when matching indexing results () against a char range.
CharUInt16IntStringUInt16mbt
test {
let a_int : Int = 'b'
if (a_int is 'a'..<'z') { () } else { () }
let a_u16 : UInt16 = 'b'
if (a_u16 is 'a'..<'z') { () } else { () }
let a_char : Char = 'b'
if (a_char is 'a'..<'z') { () } else { () }
}对、和使用字符字面量重载,下面的示例依赖该特性。当你需要将索引结果()和字符范围做匹配时,这个特性非常好用。
CharUInt16IntStringUInt16mbt
test {
let a_int : Int = 'b'
if (a_int is 'a'..<'z') { () } else { () }
let a_u16 : UInt16 = 'b'
if (a_u16 is 'a'..<'z') { () } else { () }
let a_char : Char = 'b'
if (a_char is 'a'..<'z') { () } else { () }
}Use Nested Patterns and is
is使用嵌套模式和is
is- Use patterns inside
is/ifto keep branches concise.guard
Example:
mbt
match token {
Some(Ident([.."@", ..rest])) if process(rest) is Some(x) => handle_at(rest)
Some(Ident(name)) => handle_ident(name)
None => ()
}- 在/
if中使用guard模式,简化分支代码。is
示例:
mbt
match token {
Some(Ident([.."@", ..rest])) if process(rest) is Some(x) => handle_at(rest)
Some(Ident(name)) => handle_ident(name)
None => ()
}Prefer Functional Loops to Mutation When Possible
尽可能优先使用函数式循环而非可变赋值
- Use functional state update
Example:
mbt
// Before
let mut a = 1
let mut b = 2
for i = 0 {
if i >= n {
break
}
a = a + b
b = b + a
continue i + 1
}mbt
for i = 0, a = 1, b = 2 {
if i >= n {
break a
}
continue i + 1, b, b + a
}- Functional loops also accept range loops, so they can be simplified:
mbt
for _ in 0..<n; a = 1, b = 2 {
continue b, a + b
} nobreak {
a
}- 使用函数式状态更新
示例:
mbt
// Before
let mut a = 1
let mut b = 2
for i = 0 {
if i >= n {
break
}
a = a + b
b = b + a
continue i + 1
}mbt
for i = 0, a = 1, b = 2 {
if i >= n {
break a
}
continue i + 1, b, b + a
}- 函数式循环也支持范围循环,因此可以进一步简化:
mbt
for _ in 0..<n; a = 1, b = 2 {
continue b, a + b
} nobreak {
a
}Prefer Range Loops to Simple Indexing
优先使用范围循环而非简单索引
- Use ,
for i in start..<end { ... },for i in start..<=end { ... }, orfor i in large>..smallfor simple index loops.for i in large>=..small - Keep functional-state loops for algorithms that update state.
for
Example:
mbt
// Before
for i = 0; i < len; {
items.push(fill)
continue i + 1
}
// After
for i in 0..<len {
items.push(fill)
}- 简单索引循环使用、
for i in start..<end { ... }、for i in start..<=end { ... }或者for i in large>..small写法。for i in large>=..small - 需要更新状态的算法使用带函数式状态的循环。
for
示例:
mbt
// Before
for i = 0; i < len; {
items.push(fill)
continue i + 1
}
// After
for i in 0..<len {
items.push(fill)
}Loop Specs (Dafny-Style Comments)
循环规约(Dafny风格注释)
- Add specs for functional-state loops.
- Skip invariants for simple loops.
for x in xs - Add TODO when a decreases clause is unclear (possible bug).
Example:
mbt
for i = 0, acc = 0; i < xs.length(); {
acc = acc + xs[i]
i = i + 1
} else { acc }
where {
invariant: 0 <= i <= xs.length(),
reasoning: (
#| ... rigorous explanation ...
#| ...
)
}- 为带函数式状态的循环添加规约。
- 简单的循环可以省略不变量。
for x in xs - 当递减子句不明确时添加TODO标记(可能存在Bug)。
示例:
mbt
for i = 0, acc = 0; i < xs.length(); {
acc = acc + xs[i]
i = i + 1
} else { acc }
where {
invariant: 0 <= i <= xs.length(),
reasoning: (
#| ... rigorous explanation ...
#| ...
)
}Tests and Docs
测试与文档
- Prefer black-box tests in or
*_test.mbt.*.mbt.md - Add docstring tests using fenced blocks for public APIs, and verify with
mbt check.moon check && moon test
Example:
mbt
///|
/// Return the last element of a non-empty array.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(last([1, 2, 3]), content="3")
/// }
/// ```
pub fn last(xs : Array[Int]) -> Int { ... }- 优先在或
*_test.mbt中编写黑盒测试。*.mbt.md - 为公共API使用围栏代码块添加文档测试,通过
mbt check验证。moon check && moon test
示例:
mbt
///|
/// Return the last element of a non-empty array.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(last([1, 2, 3]), content="3")
/// }
/// ```
pub fn last(xs : Array[Int]) -> Int { ... }Coverage-Driven Refactors
覆盖率驱动的重构
- Use coverage to target missing branches through public APIs.
- Prefer small, focused tests over white-box checks.
Commands:
bash
moon coverage analyze -- -f summary
moon coverage analyze -- -f caret -F path/to/file.mbt- 使用覆盖率定位公共API未覆盖的分支。
- 优先编写小型、聚焦的测试,而非白盒检查。
命令:
bash
moon coverage analyze -- -f summary
moon coverage analyze -- -f caret -F path/to/file.mbtMoon IDE Commands
Moon IDE 命令
bash
moon doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> -new-name <new_name>
moon check
moon test
moon infoUse these commands for reliable refactoring.
Example: spinning off from .
package_bpackage_aTemporary import in :
package_bmbt
using @package_a { a, type B }Steps:
- Use to find all call sites of
moon ide find-references <symbol>anda.B - Replace them with and
@package_a.a.@package_a.B - Remove the statement and run
using.moon check
bash
moon doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> -new-name <new_name>
moon check
moon test
moon info使用这些命令实现可靠的重构。
示例:从拆分出。
package_apackage_b在中临时导入:
package_bmbt
using @package_a { a, type B }步骤:
- 使用查找
moon ide find-references <symbol>和a的所有调用位点。B - 将其替换为和
@package_a.a。@package_a.B - 移除语句并执行
using。moon check