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 ide 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 ide 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万行以内。
- 保持文件的可管理性:每个文件代码量尽量控制在2千行以内。
- 保持函数的聚焦性:每个函数代码量尽量控制在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
// 在包B中 using @A { ... } // 重新导出A的API确保通过后再继续。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
// 重构前
fn reader_next(r : Reader) -> Char? { ... }
let ch = reader_next(r)
// 重构后
#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
// 模式匹配 - 为匹配的值添加注解
let tree : @pkga.Tree = ...
match tree {
Leaf(x) => x
Node(left~, x, right~) => left.sum() + x + right.sum()
}
// 嵌套构造函数 - 仅外层需要完整路径
let x = @pkga.Tree::Node(left=Leaf(1), x=2, right=Leaf(3))
// 返回类型提供上下文
fn make_tree() -> @pkga.Tree {
Node(left=Leaf(1), x=2, right=Leaf(3))
}
// 集合 - 为数组添加类型注解
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
// 重构前
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 - Use if xs is an iterator(e.g, Array) or
for x in xs {...}if you also want to use the indexfor i,x in xs {...} - Keep functional-state loops for algorithms that update state.
for
Example:
mbt
// Before
for i = 0; i < len; i = i + 1{
items.push(fill)
}
// 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 - 如果xs是迭代器(如Array),使用;如果还需要索引,使用
for x in xs {...}。for i,x in xs {...} - 函数式状态循环优先用于需要更新状态的算法。
for
示例:
mbt
// 重构前
for i = 0; i < len; i = i + 1{
items.push(fill)
}
// 重构后
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
} nobreak { 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
} nobreak { acc }
where {
invariant: 0 <= i <= xs.length(),
reasoning: (
#| ... 严谨的解释 ...
#| ...
)
}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
///|
/// 返回非空数组的最后一个元素。
///
/// # 示例
/// ```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 ide doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> <new_name>
moon ide analyze [path]
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 ide doc "<query>"
moon ide outline <dir|file>
moon ide find-references <symbol>
moon ide peek-def <symbol>
moon ide rename <symbol> <new_name>
moon ide analyze [path]
moon check
moon test
moon info使用这些命令进行可靠的重构。
示例:将从中拆分出来。
package_bpackage_a在中临时导入:
package_bmbt
using @package_a { a, type B }步骤:
- 使用查找
moon ide find-references <symbol>和a的所有调用位置。B - 将它们替换为和
@package_a.a。@package_a.B - 移除语句并运行
using。moon check