moonbit-refactoring

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MoonBit 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:
  1. Architecture first: Review package structure, dependencies, and API boundaries.
  2. Inventory public APIs and call sites (
    moon doc
    ,
    moon ide find-references
    ).
  3. Pick one refactor theme (API minimization, package splits, pattern matching, loop style).
  4. Apply the smallest safe change.
  5. Update docs/tests in the same patch.
  6. Run
    moon check
    , then
    moon test
    .
  7. Use coverage to target missing branches.
Avoid local cleanups (renaming, pattern matching) until the high-level structure is sound.
先全局梳理,再局部优化:
  1. 架构优先:审核包结构、依赖关系和API边界。
  2. 盘点公共API和调用位点(
    moon doc
    moon ide find-references
    )。
  3. 选定一个重构主题(API最小化、包拆分、模式匹配、循环风格)。
  4. 执行最小粒度的安全变更
  5. 在同一次提交中更新文档/测试
  6. 先执行
    moon check
    ,再执行
    moon test
  7. 使用覆盖率定位未覆盖的分支。
在高层结构稳定前,不要进行局部清理(重命名、模式匹配替换)。

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
A
into
A
and
B
:
  1. Create the new package and re-export temporarily:
    mbt
    // In package B
    using @A { ... }  // re-export A's APIs
    Ensure
    moon check
    passes before proceeding.
  2. Find and update all call sites:
    bash
    moon ide find-references <symbol>
    Replace bare
    f
    with
    @B.f
    .
  3. Remove the
    use
    statement once all call sites are updated.
  4. Audit and remove newly-unused
    pub
    APIs from both packages.
当要将包
A
拆分为
A
B
两个包时:
  1. 创建新包并临时重导出:
    mbt
    // In package B
    using @A { ... }  // re-export A's APIs
    确保
    moon check
    通过后再继续后续操作。
  2. 查找并更新所有调用位点:
    bash
    moon ide find-references <symbol>
    将裸写的
    f
    替换为
    @B.f
  3. 所有调用位点更新完成后,移除
    use
    语句。
  4. 审计并移除两个包中新的未使用
    pub
    API。

Guidelines

指导原则

  • Prefer acyclic dependencies: lower-level packages should not import higher-level ones.
  • Only expose what downstream packages actually need.
  • Consider an
    internal/
    package for helpers that shouldn't leak.
  • 优先使用无环依赖:底层包不应导入上层包。
  • 仅暴露下游包实际需要的内容。
  • 可以考虑使用
    internal/
    包存放不对外暴露的辅助工具。

Minimize Public API and Modularize

最小化公共API并模块化

  • Remove
    pub
    from helpers; keep only required exports.
  • Move helpers into
    internal/
    packages to block external imports.
  • 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
#as_free_fn(old_name, ...)
on the method; it emits a deprecated free function
old_name
that forwards to the method. Then you can check call sites and update them gradually by looking at warnings. Example (chaining):
mbt
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_name
,转发请求到新方法。之后你可以通过查看警告,逐步检查并更新调用位点。 链式调用示例:
mbt
buf..write_string("#\\")..write_char(ch)

Prefer Explicit Qualification

优先使用显式限定

  • Use
    @pkg.fn
    instead of
    using
    when clarity matters.
  • 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]
    .
  • String
    /
    StringView
    indexing yields
    UInt16
    code units. Use
    for ch in s
    for Unicode-aware iteration.
  • 直接对数组做模式匹配,编译器会隐式插入ArrayView。
  • 可以在中间使用
    ..
    同时匹配前缀和后缀。
  • 直接对字符串做模式匹配,避免转换为
    Array[Char]
  • String
    /
    StringView
    的索引返回
    UInt16
    码元,使用
    for ch in s
    实现Unicode感知的迭代。

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
Char
,
UInt16
, and
Int
; the examples below rely on it. This is handy when matching
String
indexing results (
UInt16
) against a char range.
mbt
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 { () }
}
Char
UInt16
Int
使用字符字面量重载,下面的示例依赖该特性。当你需要将
String
索引结果(
UInt16
)和字符范围做匹配时,这个特性非常好用。
mbt
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

  • Use
    is
    patterns inside
    if
    /
    guard
    to keep branches concise.
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 { ... }
    ,
    for i in large>..small
    , or
    for i in large>=..small
    for simple index loops.
  • Keep functional-state
    for
    loops for algorithms that update state.
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
    for x in xs
    loops.
  • 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
    *_test.mbt
    or
    *.mbt.md
    .
  • Add docstring tests using
    mbt check
    fenced blocks for public APIs, and verify with
    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.mbt

Moon 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 info
Use these commands for reliable refactoring.
Example: spinning off
package_b
from
package_a
.
Temporary import in
package_b
:
mbt
using @package_a { a, type B }
Steps:
  1. Use
    moon ide find-references <symbol>
    to find all call sites of
    a
    and
    B
    .
  2. Replace them with
    @package_a.a
    and
    @package_a.B
    .
  3. Remove the
    using
    statement and run
    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_a
拆分出
package_b
package_b
中临时导入:
mbt
using @package_a { a, type B }
步骤:
  1. 使用
    moon ide find-references <symbol>
    查找
    a
    B
    的所有调用位点。
  2. 将其替换为
    @package_a.a
    @package_a.B
  3. 移除
    using
    语句并执行
    moon check