mcpField
B
boxlang-best-practices

boxlang-best-practices

github.com/ortus-boxlang開発050 回インストール今日更新
リポジトリ

boxlang-best-practices について

開発者やAIエージェントがBoxLangコードを書く際のリファレンススキルです。命名、スコープ、エラー処理、ヌル安全性、パフォーマンス、コード構成に加え、BoxLangとCFMLの構文の違いも扱います。

主な機能

  1. 01BoxLangの命名規則とスコープ規約
  2. 02引数と戻り値の型指定
  3. 03エラー処理とヌル安全性の指針
  4. 04パフォーマンスとコレクション操作の推奨事項
  5. 05BoxLangとCFMLの構文比較

活用シーン

  1. 01BoxLangコードの慣用パターンの確認
  2. 02スコープ漏れや組み込みスコープの遮蔽防止
  3. 03BoxLangのクラスとアプリケーション層の構成
  4. 04CFMLからの移行時のBoxLang構文確認

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/boxlang-best-practicesを入力して読み込まれたか確認します。

詳細

BoxLang のベストプラクティス

BoxLang は、開発者と AI エージェントが連携してアプリケーションを構築、モダナイズ、実行するための、AI ネイティブなソフトウェア生産性プラットフォームです。

概要

BoxLang は、最新の動的 JVM 言語です。ここで紹介するベストプラクティスは、言語の設計、CFML の伝統、JVM のパフォーマンス特性を踏まえた、BoxLang らしいパターンを反映しています。これらに従うことで、読みやすく、保守しやすく、高パフォーマンスで安全なコードを記述できます。


命名規則

項目規則例
変数キャメルケース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 )
    }
}

変数のスコープ

variables スコープ(コンポーネントレベルのスコープ)を汚染しないよう、関数内では常に var を使ってローカル変数を宣言してください。

// 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 安全性

ヌルポインターエラーを避けるため、安全ナビゲーション演算子(?.)とエルビス演算子(?:)を使用してください。

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

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

null との直接比較よりも、isNull() を優先してください。

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

クロージャとラムダ

引数だけを対象とする純粋で決定的な操作にはラムダ(->)を使用してください。外側のスコープ変数にアクセスしたり、外部関数や 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 ) )

構造体と配列のリテラル

コンストラクター関数よりもリテラル構文を優先してください。

// 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"

キーの順序が重要な場合は、順序付き構造体のリテラル構文を使用してください。

// 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 つだけ持たせてください。関連のない処理を扱う「神クラス」は避けてください。サービス、リポジトリ、モデルの各レイヤーに分割してください。


パフォーマンスのヒント

  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

コメント