Skip to content

Best Practices for Expression Language Usage

This guide provides comprehensive best practices for working with the expression language, organized by topic and supported with practical examples and implementation details.

1. Expression Clarity and Organization

Use Parentheses for Complex Logic

Always use parentheses to make complex logical expressions clear and unambiguous. This is especially important when combining multiple operators.

js
// Hard to read and potentially ambiguous
status == "active" and priority > 3 or category == "urgent"

// Clear and unambiguous
(status == "active" and priority > 3) or category == "urgent"

// Complex nested logic made clear
((status == "active" or status == "pending") and priority > 3) or 
(category == "urgent" and @hasRole(user))

Implementation Note: The parser (parser_expr_paren.go) handles nested expressions efficiently, so there's no performance penalty for using parentheses.

Maintain Consistent Formatting

  • Use spaces around operators for better readability
  • Break long expressions into multiple lines at logical points
  • Align nested expressions for better structure visualization
js
// Poor formatting
status=="active"and(priority>3or category=="urgent")

// Good formatting
status == "active" and (
    priority > 3 or 
    category == "urgent"
)

2. String Operations Best Practices

Choose the Right String Operation

Select the most appropriate string operation based on your needs:

js
// For exact prefix matching
name starts "Test"  // More efficient than using match "^Test.*"

// For suffix matching
email ends "@example.com"  // More efficient than using match ".*@example.com$"

// For substring presence
description includes "error"  // More efficient than using match ".*error.*"

// For pattern matching (use only when needed)
log match "ERROR|WARN: .*"

Implementation Details: See expr_function.go for the optimized implementation of these operations.

String Matching Optimization

  • Use starts, ends, and includes over match when possible
  • Reserve match for complex pattern matching needs
  • Consider case sensitivity requirements
js
// Inefficient
name match "^user.*"
email match ".*@company\\.com$"

// Efficient
name starts "user"
email ends "@company.com"

3. Type Safety and Comparisons

Handle Type Compatibility

Be mindful of type compatibility in comparisons and ensure proper type handling:

js
// Good practices
age > 18  // Numeric comparison
status == "active"  // String comparison
isEnabled == true  // Boolean comparison

// Avoid implicit type conversions
priority == "5"  // Bad: mixing string and number
age > "18"      // Bad: comparing number with string

Implementation Note: See helper.go for type conversion utilities and type checking implementation.

Collection Operations Type Safety

When using the IN operator, maintain consistent types within the collection:

js
// Good - consistent types
status in ("active", "pending", "completed")
priority in (1, 2, 3)

// Avoid - mixed types unless specifically needed
value in ("active", 42, true)  // Could lead to unexpected behavior

4. Function Usage

Function Naming and Organization

Follow consistent naming conventions for functions:

js
// Good function names
@isAdmin(user)
@hasPermission(user, "write")
@validateInput(data)

// Avoid
@check(user)  // Too vague
@doStuff(data)  // Unclear purpose

Function Return Type Handling

Handle function return types appropriately:

js
// Boolean functions - direct usage
@isAdmin(user)

// Numeric functions - use with comparisons
@count(items) > 0

// String functions - use with string operations
@toLowerCase(name) == "admin"

Implementation Details: See expr_function.go and parser_expr_function.go for function handling implementation.

5. Error Handling and Validation

Validate Input Data

Ensure input data matches expected types and formats:

js
// Include validation checks
@validateEmail(email) and @validateAge(age)

// Handle potential null values
@hasValue(user.profile) and user.profile.age > 18

Error Cases to Handle

Consider common error scenarios:

  1. Missing fields
  2. Null values
  3. Type mismatches
  4. Invalid function arguments
  5. Malformed expressions
js
// Good practice - defensive checking
@exists(user.profile) and (
    @validateAge(user.profile.age) and 
    user.profile.age > 18
)

6. Performance Considerations

Optimize Expression Structure

Structure expressions to minimize evaluation overhead:

js
// Less efficient - evaluates everything
@expensiveCheck(data) and (x == 1 or x == 2)

// More efficient - checks simple conditions first
(x == 1 or x == 2) and @expensiveCheck(data)

Use Appropriate Operators

Choose operators that match your needs while maintaining performance:

js
// Efficient - direct comparison
status == "active"

// Less efficient - regex match for simple comparison
status match "^active$"

7. Testing and Validation

Test Edge Cases

Always test expressions with edge cases:

  1. Empty strings
  2. Null values
  3. Boundary numbers
  4. Empty collections
  5. Special characters in strings
// Edge case examples
status in ()  // Empty collection
name == ""    // Empty string
age >= 0      // Boundary value

Validate Complex Expressions

Break down and test complex expressions in parts:

js
// Complex expression
(status in ("active", "pending") and priority > 3) or 
(@isAdmin(user) and @hasPermission(user, "override"))

// Test individual parts
1. status in ("active", "pending")
2. priority > 3
3. @isAdmin(user)
4. @hasPermission(user, "override")