mcpField
B
boxlang-best-practices

boxlang-best-practices

github.com/ortus-boxlang开发050 次安装今天更新
代码仓库

关于 boxlang-best-practices

为开发者和 AI Agent 编写 BoxLang 代码提供参考,涵盖命名、变量作用域、错误处理、空值安全、性能和代码组织,并说明 BoxLang 与 CFML 的语法差异。

主要功能

  1. 01BoxLang 命名和作用域规范
  2. 02参数与返回值类型建议
  3. 03错误处理和空值安全指导
  4. 04性能及集合操作建议
  5. 05对照 BoxLang 与 CFML 语法

适用场景

  1. 01检查 BoxLang 代码是否符合惯用模式
  2. 02避免作用域泄漏及内置作用域遮蔽
  3. 03规划 BoxLang 类和应用分层
  4. 04从 CFML 迁移时核对 BoxLang 语法

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ortus-boxlang/skills --skill boxlang-best-practices

安装后在 Claude Code 中输入/boxlang-best-practices确认已加载。

详细介绍

BoxLang 最佳实践

BoxLang 是一个 AI 原生的软件生产力平台,用于构建、现代化改造和运行应用程序,让开发者与 AI Agent 协同工作。

概述

BoxLang 是一种现代动态 JVM 语言。这些最佳实践体现了符合惯用方式的 BoxLang 模式,结合了该语言的设计、CFML 的传承以及 JVM 的性能特点。遵循这些实践可以编写出可读、易维护、高性能且安全的代码。


命名约定

项目约定示例
变量小驼峰命名法userProfile、orderTotal
函数/方法小驼峰命名法getUserById()、processOrder()
类大驼峰命名法UserService、OrderProcessor
常量大写蛇形命名法MAX_RETRIES、DEFAULT_TIMEOUT
文件(类)大驼峰命名法UserService.bx
文件(模板)小驼峰命名法或短横线命名法userProfile.bxm、order-details.bxm
文件(脚本)小驼峰命名法buildReport.bxs
// Good
class UserService {
    function getUserById( required numeric id ) {
        var MAX_RETRIES = 3
        var userId = arguments.id
        return userRepository.find( userId )
    }
}

变量作用域

在函数内部始终使用 var 声明局部变量,以免污染 variables 作用域(组件级作用域)。

// BAD — leaks into variables scope
function process() {
    result = doWork()
    return result
}

// GOOD — properly local
function process() {
    var result = doWork()
    return result
}

存在歧义时,请使用显式作用域前缀:

function getUser( required numeric id ) {
    // Explicitly scope to avoid confusion
    var userData = variables.userRepo.find( arguments.id )
    return userData
}
避免遮蔽内置作用域名称

切勿将局部变量命名为与内置作用域相同的名称。BoxLang 会沿作用域链查找未限定作用域的变量引用,遮蔽作用域名称会导致无法访问该作用域的数据,并引发令人困惑的错误。

应避免用作变量名的保留作用域名称: session、server、request、url、form、application、cgi、thread

// BAD — shadows the `session` scope, breaking all session access
function login( required string user ) {
    var session = { user: arguments.user }  // DON'T do this
    session.user = arguments.user           // writes to local var, not session scope
}

// BAD — shadows the `url` scope
var url = parseUrl( input )                 // url.params is now broken

// GOOD — use descriptive names that don't collide
function login( required string user ) {
    var sessionData = { user: arguments.user }
    session.user = arguments.user           // correctly writes to session scope
}

// GOOD — use a different name
var urlParts = parseUrl( input )
var queryParams = url.params                // url scope still accessible

需要在局部变量中引用作用域数据时,请使用描述数据的名称, 而不是作用域本身的名称:

不要使用请使用
var session = ...var sessionData、var userSession、var sess
var request = ...var requestData、var req、var httpRequest
var url = ...var urlParts、var currentUrl、var uri
var form = ...var formData、var formFields、var payload
var application = ...var appConfig、var appData、var settings
var cgi = ...var cgiData、var serverInfo
var thread = ...var threadInfo、var workerThread
var server = ...var serverInfo、var hostConfig
作用域查找性能

BoxLang 每次访问未限定作用域的变量时,都会遍历作用域链。在热点代码路径中 (紧密循环、高流量请求处理程序),请显式指定变量作用域,以获得可预测的性能:

// GOOD in hot paths — no scope chain walk
var len = arguments.items.len()
for ( var i = 1; i <= len; i++ ) {
    // process arguments.items[ i ]
}

函数

始终声明参数类型和必需状态
// BAD — no type information
function processOrder( order, userId ) { ... }

// GOOD — self-documenting, validated at runtime
function processOrder( required struct order, required numeric userId ) {
    ...
}
使用命名参数以提高清晰度
// Hard to read
createUser( "John", "Doe", "[email protected]", true )

// GOOD — named arguments document intent
createUser(
    firstName = "John",
    lastName  = "Doe",
    email     = "[email protected]",
    active    = true
)
返回类型

为公共函数声明返回类型,以说明契约并提供更好的 IDE 支持:

struct function getUser( required numeric id ) {
    return userService.find( arguments.id )
}

array function listActiveUsers() {
    return userService.findByStatus( "active" )
}

错误处理

捕获特定异常类型
// BAD — catches everything, hides bugs
try {
    processOrder( order )
} catch ( any e ) {
    logError( e )
}

// GOOD — handle specific cases, re-throw unknown
try {
    processOrder( order )
} catch ( "Database" e ) {
    handleDatabaseError( e )
} catch ( "Validation" e ) {
    return { success: false, message: e.message }
} catch ( any e ) {
    // Re-throw unexpected errors
    rethrow
}
使用 cffinally 执行清理
transaction {
    try {
        updateOrder( order )
        chargePayment( payment )
        transactionCommit()
    } catch ( any e ) {
        transactionRollback()
        rethrow
    }
}

Null 安全性

使用安全导航运算符(?.)和 Elvis 运算符(?:),以 避免空指针错误:

// Null-safe chained access
var city = user?.address?.city ?: "Unknown"

// Null-safe method calls
var count = order?.items?.len() ?: 0

优先使用 isNull(),而不是直接与 null 比较:

if ( isNull( result ) ) {
    return getDefault()
}

闭包与 Lambda

仅对参数执行纯确定性操作时,使用 Lambda(->)。 访问外部作用域变量或调用外部函数/BIF 时,使用 闭包(=>)。

// Lambda — only uses the item argument (pure transform)
var doubled = numbers.map( ( n ) -> n * 2 )

// Closure — accesses outer variable `threshold`
var filtered = numbers.filter( ( n ) => n > threshold )

// Closure — calls external BIF
var upper = words.map( ( w ) => uCase( w ) )

Struct 和数组字面量

优先使用字面量语法,而不是构造函数:

// GOOD — literal syntax
var user = {
    name:  "Alice",
    email: "[email protected]",
    roles: [ "admin", "user" ]
}

// Avoid unless dynamic keys are needed
var user = structNew()
user.name = "Alice"

键顺序很重要时,请使用有序 Struct 字面量语法:

// Ordered struct (insertion order preserved)
var config = [=
    host: "localhost",
    port: 5432,
    database: "myapp"
=]

字符串插值

在字符串和模板中使用 #expression# 进行插值。对于复杂 表达式,为提高可读性,请先将其赋值给变量:

// Simple interpolation
var message = "Hello, #user.name#!"

// Complex — extract first
var formattedDate = dateTimeFormat( now(), "long" )
var header = "Report generated on #formattedDate#"

组件(类)设计

构造函数模式

使用 init() 作为构造函数。返回 this 以支持链式构造:

class UserService {

    property name="userRepo" inject="UserRepository"

    function init( required UserRepository userRepository ) {
        variables.userRepo = arguments.userRepository
        return this
    }

}
保持类职责聚焦(单一职责)

每个 .bx 类都应有一个主要用途。避免处理无关事务的“上帝对象”。将代码拆分为服务层、仓库层和模型层。


性能提示

  1. 缓存开销较大的查找 — 将结果存储在 application 作用域中,用于共享的只读数据;数据变更时使缓存失效。
  2. 在生产环境中使用 trustedCache=true — 避免检查类文件时发生磁盘 I/O。
  3. 在构造函数中预先计算 — 如果某个值不会改变,就在实例化时计算一次。
  4. 处理集合时优先使用 each() / map() / filter(),而不是手动循环 — 可读性更好,也更适合 JIT。
  5. I/O 密集型异步工作使用虚拟线程(runAsync 默认使用);CPU 密集型工作使用固定线程池执行器。

代码组织

/app
  /models           -- Business domain classes (.bx)
  /services         -- Service layer classes (.bx)
  /repositories     -- Data access classes (.bx)
  /handlers         -- Request handlers / controllers (.bx)
  /views            -- Templates (.bxm)
  /includes         -- Reusable partial templates (.bxm)
  /scripts          -- Standalone scripts (.bxs)
  Application.bx    -- Application lifecycle

应避免的常见反模式

反模式问题修复方法
函数中的变量未指定作用域变量会泄漏到组件作用域始终使用 var
遮蔽作用域名称(session、url、form 等)无法访问内置作用域使用 sessionData、formData 等描述性名称
静默捕获所有异常的 catch(any)吞掉意料之外的错误重新抛出未知异常
在模板中编写逻辑难以测试,职责分离不佳移至服务或处理程序中
在处理程序中直接编写 SQL无法复用,存在 SQL 注入风险使用带参数化查询的仓库类
在代码中存储机密存在安全风险在配置中通过 ${env.VAR_NAME} 使用环境变量
过度使用 application 作用域会产生并发问题对写操作使用适当的锁(bx:lock)
arr[ 0 ](从零开始的索引)ArrayIndexOutOfBoundsExceptionBoxLang 数组是从 1 开始索引:使用 arr[ 1 ] 或 arr.first()
语句末尾的 ;风格冗杂且不一致语句中的分号是可选的,可以省略
cfheader() / <cfabort>这是 CFML 语法,不是 BoxLang 语法使用 bx:header、bx:abort 等
对 Java 对象使用命名参数不受支持,会引发运行时错误调用 Java 方法时始终使用位置参数
每次调用都使用 createObject("java","path")冗长且重复使用一次 import java:fully.qualified.Class,然后直接使用类名

BoxLang 与 CFML 快速参考

BoxLang 源自 CFML,但许多结构使用了不同的语法。请勿在 BoxLang 源文件中 使用带 cf 前缀的标签或函数。

CFMLBoxLang 等效写法
cfheader(name="X", value="Y")bx:header name="X" value="Y";
cflocation(url="...")bx:location url="...";
cfabortbx:abort;
cfparam name="x" default=""bx:param name="x" default="";
cfinclude template="f.cfm"bx:include template="f.bxm";
<cfsilent>bx:silent { ... }
createObject("java","path.Class")import java:path.Class,然后使用 new java:path.Class()

数组最佳实践

BoxLang 数组是从 1 开始索引的。对于习惯 Java/JavaScript 的开发者来说,这是一个常见的错误来源。

var items = [ "a", "b", "c" ]

// CORRECT
var first = items[ 1 ]          // "a"
var last  = items[ items.len() ] // "c"
var first = items.first()        // preferred — more readable
var last  = items.last()         // preferred

// WRONG (throws ArrayIndexOutOfBoundsException)
var first = items[ 0 ]

// Looping — i starts at 1
for ( var i = 1; i <= items.len(); i++ ) {
    process( items[ i ] )
}
向 Java 可变参数传递单个值

Java 可变参数方法需要 BoxLang 数组,而不是单个标量值:

// CORRECT — wrap in array
storage.query( "SELECT * FROM t WHERE id = ?", [ requestId ] )

// WRONG — bare value is not accepted by Java varargs
storage.query( "SELECT * FROM t WHERE id = ?", requestId )    // throws

评论

支持 Markdown