Skip to content

9.4 Custom Code Best Practices

You can write custom code. You've seen examples. Now let's cover best practices to write code that's fast, reliable, and easy to maintain. These practices will save you time, money, and prevent problems.


Why Best Practices Matter

The difference between good and bad code:

Bad CodeGood Code
Crashes on errorsHandles errors gracefully
Wastes money on duplicate API callsCaches results intelligently
Takes hours to debugEasy to troubleshoot
Breaks when data changesAdapts to edge cases
Hard to modify laterClear and maintainable

Let's learn how to write good code from the start.


Performance Best Practices

Practice 1: Cache API Results (Save Time & Money)

The Problem: Every time your column runs, it makes an API call—even if nothing has changed. This is slow and expensive.

Example: You're enriching 1,000 leads with company data. If each API call costs $0.01 and takes 2 seconds:

  • Cost: $10.00
  • Time: 33 minutes
  • Problem: Most of that data hasn't changed since yesterday!
😰

Problem: Calling API Every Time

This code calls an external API for EVERY row, even if the data hasn't changed:

// Get email from previous column
let email = [Email Address]

// Call API (SLOW and EXPENSIVE!)
let result = callEnrichmentAPI(email)

return result

💸 Cost Impact

1,000 rows processed:1,000 API calls
At $0.01 per call:$10.00
Time per call (2 seconds):33 minutes total

How caching works:

  1. First run: No cached data exists → Make API call → Save result with timestamp
  2. Second run (1 hour later): Cached data is fresh (< 24 hours) → Return cached data instantly
  3. Third run (2 days later): Cached data is stale (> 24 hours) → Make new API call → Update cache

When to use caching:

  • ✅ Data enrichment (company info, contact details)
  • ✅ External API lookups (weather, stock prices, geo data)
  • ✅ Expensive calculations that don't change often
  • ❌ Real-time data that must always be fresh (current stock price)
  • ❌ User-specific data that changes frequently

Practice 2: Use Built-in Methods for Lists

The Problem: Writing manual loops to process lists is slow and error-prone.

Bad approach - Manual loop:

groovy
// Get list of 10,000 orders
let orders = [Order List]
let total = 0

// Slow: Manually loop through each item
for (let i = 0; i < orders.length; i++) {
  total += orders[i].amount
}

return total

Good approach - Built-in method:

groovy
// Get list of orders
let orders = [Order List]

// Fast: Use built-in sum method
let total = orders.sum(order => order.amount) || 0

return total

Common built-in methods:

TaskBuilt-in MethodExample
Filter listfilter()orders.filter(o => o.status == 'paid')
Transform itemsmap()orders.map(o => o.customerName)
Count matchingfilter().lengthorders.filter(o => o.amount > 100).length
Sum valuessum()orders.sum(o => o.amount)
Find first matchfind()orders.find(o => o.id == 123)
Check if anysome()orders.some(o => o.urgent)
Check if allevery()orders.every(o => o.paid)

Performance difference: 10× to 100× faster for large lists!


Practice 3: Return Early (Don't Process Unnecessary Data)

The Problem: You do expensive processing even when you know the result will be useless.

Bad approach:

groovy
// Get email and company data
let email = [Email Address]
let company = [Company Data]

// Do expensive processing (even if email is missing!)
let enriched = callExpensiveAPI(company)
let scored = calculateComplexScore(enriched)
let classified = classifyLead(scored)

// Check email at the end (too late!)
if (!email) {
  return null  // Wasted all that processing!
}

return classified

Good approach:

groovy
// Validate early - exit immediately if invalid
let email = [Email Address]
if (!email) return null  // Exit now!

if (!email.includes('@')) {
  return { error: 'Invalid email format' }
}

let company = [Company Data]
if (!company) return null  // Exit if no company data

// Only do expensive work if all required fields are present
let enriched = callExpensiveAPI(company)
let scored = calculateComplexScore(enriched)

return classifyLead(scored)

Benefit: Save CPU time, reduce costs, and speed up execution by 50-90% for invalid rows.


Practice 4: Filter Data Before Processing

The Problem: Processing more data than you actually need.

Bad approach:

groovy
// Get ALL orders (100,000 orders!)
let allOrders = [All Orders]

// Process everything (slow!)
let stats = calculateStatistics(allOrders)

return stats

Good approach:

groovy
// Get all orders
let allOrders = [All Orders]

// Filter FIRST (reduce from 100,000 to 500)
let recentOrders = allOrders.filter(order => {
  let orderDate = new Date(order.date)
  let daysAgo = daysSince(orderDate)
  return daysAgo <= 30  // Only last 30 days
})

// Process filtered data (200× faster!)
let stats = calculateStatistics(recentOrders)

return stats

Performance impact: Processing 500 items instead of 100,000 = 200× faster


Error Handling Best Practices

Practice 5: Wrap Code in Try-Catch Blocks

The Problem: When an error occurs, the cell shows a red "Error" badge with no details. You don't know what went wrong, and the automation stops.

💥

Problem: Crashes on Errors

// Get email and process
let email = [Email Address]
let domain = email.split('@')[1]  // CRASH if email is empty!

return domain
ErrorCell shows red badge with no details
You don't know what went wrong
🛑Entire automation stops

Real-world example - API call with error handling:

groovy
try {
  // Get data
  let email = [Email Address]
  let apiKey = [API Key]

  // Validate
  if (!email) throw new Error("Email is required")
  if (!apiKey) throw new Error("API key is missing")

  // Make API call
  let result = callAPI(email, apiKey)

  return {
    success: true,
    data: result
  }

} catch (error) {
  // Return error details instead of crashing
  return {
    success: false,
    error: true,
    message: error.message,
    email: [Email Address],  // Show which email failed
    timestamp: now()
  }
}

Benefits of try-catch:

  • ✅ Cell completes (green badge) instead of failing (red badge)
  • ✅ You see exactly what went wrong
  • ✅ Other columns can check for errors: if ([Column].error) { ... }
  • ✅ Automation continues processing other rows

Practice 6: Validate Data Before Processing

The Problem: Assuming data exists and is valid leads to crashes.

Common validation checks:

groovy
try {
  // Get data
  let email = [Email Address]
  let company = [Company Data]
  let score = [Lead Score]

  // Validate email exists
  if (!email) {
    throw new Error("Email is required")
  }

  // Validate email format
  if (!email.includes('@')) {
    throw new Error("Email format is invalid")
  }

  // Validate company data exists
  if (!company) {
    throw new Error("Company data is missing")
  }

  // Validate score is a number
  if (typeof score !== 'number') {
    throw new Error("Lead score must be a number")
  }

  // Validate score is in valid range
  if (score < 0 || score > 100) {
    throw new Error("Lead score must be between 0 and 100")
  }

  // All valid - process safely
  let result = processLead(email, company, score)

  return {
    success: true,
    result: result
  }

} catch (error) {
  return {
    success: false,
    error: error.message,
    email: email,
    timestamp: now()
  }
}

Validation checklist:

  • ✅ Check if required fields exist (if (!field))
  • ✅ Check format (email has @, phone has digits)
  • ✅ Check data type (number vs string)
  • ✅ Check value ranges (score between 0-100)
  • ✅ Check list lengths (not empty, not too large)

Practice 7: Handle Missing/Null Data Gracefully

The Problem: Code crashes when accessing properties of null/undefined data.

Bad approach:

groovy
let company = [Company Data]
let employees = company.employeeCount  // CRASH if company is null!
return employees

Good approach - Use safe navigation:

groovy
let company = [Company Data]

// Safe navigation operator (?.)
// Returns null if company is null, instead of crashing
let employees = company?.employeeCount || 0

return employees

Safe navigation examples:

groovy
// Nested data access
let city = [Company Data]?.address?.city || 'Unknown'

// Array access
let firstOrder = [Orders]?.[0]?.amount || 0

// Method calls
let upperName = [Customer Name]?.toUpperCase() || 'NO NAME'

// Multiple levels
let zipCode = [Customer]?.address?.zip?.toString() || 'N/A'

Provide defaults with || operator:

groovy
let email = [Email Address] || 'no-email@example.com'
let score = [Lead Score] || 0
let status = [Status] || 'unknown'
let tags = [Tags] || []
let metadata = [Metadata] || {}

Code Quality Best Practices

Practice 8: Use Clear, Descriptive Variable Names

The Problem: Unclear what variables represent—hard to understand and maintain.

Bad approach:

groovy
let d = [Company Data]
let x = d?.size || 0
let y = x > 100 ? 30 : 10
let z = y + (d?.industry == 'Tech' ? 25 : 0)
return z

Good approach:

groovy
let companyData = [Company Data]
let companySize = companyData?.size || 0

// Calculate size score
let sizeScore = companySize > 100 ? 30 : 10

// Add industry bonus
let industryBonus = companyData?.industry == 'Technology' ? 25 : 0

// Calculate total
let totalScore = sizeScore + industryBonus

return totalScore

Variable naming guidelines:

  • ✅ Use full words: customerEmail not ce
  • ✅ Be specific: leadScore not score
  • ✅ Use camelCase: orderCount not order_count
  • ✅ Make booleans clear: isValid, hasErrors, canProcess
  • ✅ Use plural for lists: orders, customers, emails

Practice 9: Add Comments for Complex Logic

The Problem: Hard to understand what code does months later (or even tomorrow!).

Bad approach:

groovy
let s = [Score]
let i = [Industry]
return s > 70 && i in ['A', 'B', 'C'] ? 'P1' : s > 50 ? 'P2' : 'P3'

Good approach:

groovy
// Classify leads into priority tiers based on score and industry
let score = [Lead Score]
let industry = [Industry]

// Priority 1: High score + target industry
// These leads should be contacted immediately
if (score > 70 && ['Technology', 'Finance', 'Healthcare'].includes(industry)) {
  return 'P1 - Immediate Follow-up'
}

// Priority 2: Medium score
// Standard follow-up process
if (score > 50) {
  return 'P2 - Standard Follow-up'
}

// Priority 3: Low score
// Add to nurture campaign
return 'P3 - Nurture Campaign'

When to add comments:

  • ✅ Complex calculations: Explain the formula
  • ✅ Business rules: Explain why (not just what)
  • ✅ Magic numbers: Explain where 70, 100, etc. come from
  • ✅ Edge cases: Explain special handling
  • ✅ Workarounds: Explain why you did it this way

Add header comment:

groovy
/**
 * Lead Scoring Algorithm
 *
 * Purpose: Calculate lead quality score based on:
 * - Company size (0-30 points)
 * - Industry fit (0-25 points)
 * - Engagement (0-unlimited points)
 *
 * Returns: Object with total_score, grade, qualified
 *
 * Dependencies: Company Data, Email Opens, Link Clicks
 *
 * Last updated: 2024-11-19
 */

let score = 0
// ... rest of code ...

Practice 10: Break Complex Code into Steps

The Problem: One giant expression is hard to read, debug, and modify.

Bad approach:

groovy
return ([Orders]?.filter(o => o.status == 'completed')?.map(o => o.amount)?.reduce((sum, amt) => sum + amt, 0) || 0) / ([Orders]?.length || 1)

Good approach:

groovy
// Step 1: Get all orders
let allOrders = [Orders] || []

// Step 2: Filter completed orders
let completedOrders = allOrders.filter(order => order.status == 'completed')

// Step 3: Extract amounts
let amounts = completedOrders.map(order => order.amount)

// Step 4: Calculate total revenue
let totalRevenue = amounts.reduce((sum, amount) => sum + amount, 0)

// Step 5: Calculate average
let orderCount = allOrders.length
let avgOrderValue = orderCount > 0 ? totalRevenue / orderCount : 0

// Return detailed results
return {
  total_revenue: totalRevenue,
  order_count: orderCount,
  completed_count: completedOrders.length,
  avg_order_value: avgOrderValue
}

Benefits of breaking into steps:

  • ✅ Easy to debug: Test each step individually
  • ✅ Easy to modify: Change one step without breaking others
  • ✅ Easy to understand: Clear progression of logic
  • ✅ Easy to reuse: Extract steps into separate columns

Practice 11: Return Consistent Data Structure

The Problem: Sometimes returning strings, sometimes objects—hard to use in other columns.

Bad approach:

groovy
let status = [Status]

// Sometimes returns string
if (status == 'active') {
  return "User is active"
}

// Sometimes returns object
if (status == 'suspended') {
  return { error: true, message: "Account suspended" }
}

// Sometimes returns null
return null

Good approach - Always return same structure:

groovy
let status = [Status]

// Always return object with consistent fields
return {
  status: status,
  is_active: status == 'active',
  is_suspended: status == 'suspended',
  message: status == 'active'
    ? 'User is active'
    : status == 'suspended'
      ? 'Account suspended'
      : 'Unknown status',
  timestamp: now()
}

Benefits:

  • ✅ Other columns can always access .status, .message, etc.
  • ✅ Conditions work reliably: [Column].is_active == true
  • ✅ No unexpected crashes from missing fields
  • ✅ Easy to document what the column returns

Debugging Best Practices

Practice 12: Test Incrementally (Build Step by Step)

The Problem: Writing all code at once, then trying to debug 100 lines when something breaks.

Bad: Write Everything at Once

1
Write 100 lines of complex code
2
Click Save button
3
Run on a row
4
ErrorBut which of 100 lines failed?
5
😫 Spend hours debugging

⏰ Time wasted: Hours

Good: Build Step by Step

1
Start simple: return "test"
Works ✓
2
Add data: return [Email]
Works ✓
3
Add validation: if (!email) return null
Works ✓
4
Add processing: let domain = email.split('@')[1]
Works ✓
5
😊 Continue building gradually

⚡ Time saved: Hours → Minutes

You know exactly which line caused any error

💡

Pro Tip: Test After Every Change

Every time you add 2-3 lines, click Save and run the column on a test row. If it works, keep building. If it breaks, you know the problem is in the last 2-3 lines you just added.

Incremental testing workflow:

Step 1: Start with simplest possible code

groovy
return "test"

→ Click Save → Run on test row → ✅ Works!

Step 2: Add data access

groovy
let email = [Email Address]
return email

→ Click Save → Run on test row → ✅ Works!

Step 3: Add validation

groovy
let email = [Email Address]
if (!email) return null
return email

→ Click Save → Run on test row → ✅ Works!

Step 4: Add processing

groovy
let email = [Email Address]
if (!email) return null

let domain = email.split('@')[1]
return domain

→ Click Save → Run on test row → ✅ Works!

Step 5: Keep building gradually, testing after every 2-3 lines

Benefits:

  • ✅ Know exactly which line caused any error
  • ✅ Catch problems early before they compound
  • ✅ Save hours of debugging time
  • ✅ Build confidence as you go

Practice 13: Return Debug Information

The Problem: Can't see intermediate values when debugging—hard to understand what went wrong.

Bad approach:

groovy
let score = calculateComplexScore()
return score  // Can't see how it was calculated

Good approach - Return breakdown:

groovy
// Calculate components
let sizeScore = calculateSizeScore()
let industryScore = calculateIndustryScore()
let engagementScore = calculateEngagementScore()

let totalScore = sizeScore + industryScore + engagementScore

// Return score + detailed breakdown
return {
  total_score: totalScore,
  components: {
    size: sizeScore,
    industry: industryScore,
    engagement: engagementScore
  },
  inputs: {
    company_size: [Company Size],
    industry: [Industry],
    email_opens: [Email Opens]
  },
  calculated_at: now()
}

What to include in debug info:

  • ✅ Final result
  • ✅ Intermediate calculations
  • ✅ Input values used
  • ✅ Timestamps
  • ✅ Any warnings or issues encountered

Tip: Once working well, you can simplify the return value. But during development and debugging, more information is better!


Practice 14: Create Test Cases for Edge Cases

The Problem: Only testing with "normal" data—code breaks on edge cases in production.

Create test rows with these scenarios:

Test Case 1: Normal data

Email: john@example.com
Company Size: 500
Expected: Should process normally

Test Case 2: Missing required field

Email: (empty)
Company Size: 500
Expected: Should return error about missing email

Test Case 3: Invalid format

Email: "not-an-email"
Company Size: 500
Expected: Should return format error

Test Case 4: Boundary values ⚠️

Email: john@example.com
Company Size: 100 (exactly at threshold)
Expected: Should handle threshold correctly

Test Case 5: Extreme values 🔥

Email: john@example.com
Company Size: 999999999
Expected: Should not crash on large numbers

Test Case 6: Special characters 🔤

Email: test+filter@sub-domain.co.uk
Company Size: 50
Expected: Should handle complex email formats

Test Case 7: Null/undefined data

Email: john@example.com
Company Size: null
Expected: Should use default value (0)

Where to create test rows:

  1. Create a section in your taible called "Test Cases"
  2. Add 5-10 test rows covering different scenarios
  3. Run your column on all test rows
  4. Verify each behaves correctly
  5. Keep these rows for future testing when you modify the code

Maintenance Best Practices

Practice 15: Duplicate Column Before Major Changes

The Problem: You make changes to working code, it breaks, and you can't remember how it worked before.

Good practice - Version your code:

Before making major changes:

  1. Right-click the column header
  2. Choose "Duplicate Column"
  3. Rename: lead_score_backup or lead_score_v2
  4. Make changes to the duplicate
  5. Test thoroughly on test rows
  6. If it works: Delete the old version
  7. If it breaks: Keep both, revert to backup

When to create backups:

  • ✅ Before major algorithm changes
  • ✅ Before adding complex new features
  • ✅ Before optimizing working code
  • ✅ Before customer demo (have a known-working version)

Column naming convention:

  • lead_score - Current production version
  • lead_score_v2 - New version being developed
  • lead_score_backup - Known working backup
  • lead_score_2024_11_19 - Date-stamped backup

Practice 16: Document Column Purpose

The Problem: Six months later, you (or your teammate) look at the code and have no idea what it does or why.

Good practice - Add header comment:

groovy
/**
 * Lead Scoring Algorithm
 *
 * Purpose: Calculate lead quality score based on multiple factors
 *
 * Scoring breakdown:
 * - Company size: 0-30 points (larger = higher)
 * - Industry fit: 0-25 points (Tech/Finance/Healthcare = bonus)
 * - Engagement: 0-unlimited (email opens × 2, clicks × 5)
 * - Contact role: 0-30 points (decision-makers = higher)
 *
 * Returns: Object with:
 * - total_score: Number (0-100+)
 * - grade: String ('A', 'B', 'C', or 'D')
 * - qualified: Boolean (true if score > 50)
 *
 * Dependencies:
 * - Company Data (from enrichment)
 * - Email Opens (from email tracking)
 * - Link Clicks (from email tracking)
 * - Contact Role (from form)
 *
 * Used by:
 * - Route to Sales (condition: qualified == true)
 * - Priority Assignment (uses grade)
 *
 * Last updated: 2024-11-19
 * Author: Marketing Team
 * Notes: Thresholds based on Q3 2024 conversion analysis
 */

let score = 0

// Company size score (0-30 points)
// Based on historical data: companies > 100 employees convert 3× better
let companySize = [Company Size] || 0
// ... rest of code ...

What to document:

  • ✅ Purpose: What does this column do?
  • ✅ Algorithm: How does it calculate the result?
  • ✅ Inputs: What data does it depend on?
  • ✅ Outputs: What does it return?
  • ✅ Used by: Which other columns use this?
  • ✅ Business rules: Why these specific thresholds/logic?
  • ✅ Last updated: When was it changed?
  • ✅ Author: Who can answer questions?

Practice 17: Don't Repeat Yourself (DRY)

The Problem: Copying same logic to multiple columns—when you need to change it, you must update everywhere.

Bad approach - Duplicate logic:

Column: Lead Score

groovy
let score = 0

// Company size logic
let companySize = [Company Size] || 0
if (companySize > 100) score += 20
else if (companySize > 50) score += 10

// ... 50 more lines of scoring ...
return score

Column: Contact Score (duplicate logic!)

groovy
let score = 0

// Same company size logic (duplicated!)
let companySize = [Company Size] || 0
if (companySize > 100) score += 20
else if (companySize > 50) score += 10

// ... different scoring logic ...
return score

Good approach - Extract to shared column:

Column: Company Size Score (reusable)

groovy
let companySize = [Company Size] || 0

if (companySize > 100) return 20
else if (companySize > 50) return 10
else return 0

Column: Lead Score (uses shared logic)

groovy
let score = 0

// Use reusable component
score += [Company Size Score]

// Add lead-specific scoring
let industryScore = [Industry] == 'Technology' ? 25 : 0
score += industryScore

return score

Column: Contact Score (also uses shared logic)

groovy
let score = 0

// Use same reusable component
score += [Company Size Score]

// Add contact-specific scoring
let roleScore = [Role].includes('Director') ? 30 : 0
score += roleScore

return score

Benefits:

  • ✅ Change logic once, applies everywhere
  • ✅ Easier to test (test the shared component once)
  • ✅ Easier to maintain (one place to update)
  • ✅ Easier to understand (clear component names)

Security Best Practices

Practice 18: Never Hardcode API Keys or Passwords

The Problem: API keys and passwords in code can be seen by anyone with access to the taible.

Very bad approach - DON'T DO THIS:

groovy
// NEVER DO THIS!
let apiKey = "sk_live_abc123xyz789"  // Anyone can see this!
let result = callAPI(apiKey, [Email])
return result

Why dangerous:

  • ❌ Anyone with taible access sees the key
  • ❌ Can't rotate keys without changing code everywhere
  • ❌ Keys might appear in error messages or logs
  • ❌ Security breach if taible is exported or shared

Good approach - Use account system:

  1. Go to SettingsIntegration Accounts
  2. Create a new account for the service
  3. Enter your API key there (stored encrypted)
  4. Use the account in your column configuration

In your column: You'll reference the account, not the key directly. The system handles authentication securely.

For custom HTTP calls: Use dedicated integration columns that support accounts, rather than custom code with hardcoded keys.


Practice 19: Validate Input Data

The Problem: Trusting external data without validation can lead to security issues or data corruption.

Validation example:

groovy
try {
  // Get user input
  let userComment = [User Comment]

  // Validate exists
  if (!userComment) {
    return { error: 'Comment is required' }
  }

  // Validate length
  if (userComment.length > 1000) {
    return { error: 'Comment too long (max 1000 characters)' }
  }

  // Validate format (only alphanumeric and basic punctuation)
  let allowedChars = /^[a-zA-Z0-9\s.,!?'-]+$/
  if (!allowedChars.test(userComment)) {
    return { error: 'Comment contains invalid characters' }
  }

  // Sanitize - remove potentially dangerous content
  let sanitized = userComment
    .replace(/[<>]/g, '')  // Remove < and >
    .trim()

  // Process sanitized input
  return {
    success: true,
    comment: sanitized,
    length: sanitized.length
  }

} catch (error) {
  return {
    success: false,
    error: error.message
  }
}

What to validate:

  • ✅ Length limits (prevent overflow)
  • ✅ Format (email, phone, etc.)
  • ✅ Allowed characters (prevent injection)
  • ✅ Value ranges (numbers within bounds)
  • ✅ Required fields (not empty)

Best Practices Summary

📋

Best Practices Quick Checklist

Performance

Cache API results (save time & money)

Check for recent data before calling APIs

Use built-in methods for lists

Faster than writing manual loops

Return early on invalid data

Don't process data you won't use

Filter data before processing

Process only what you need

🛡️

Error Handling

Wrap code in try-catch blocks

Catch errors and return error details

Validate data before processing

Check that required fields exist

Provide helpful error messages

Explain what went wrong and where

Handle missing/null data gracefully

Use safe navigation and defaults

📝

Code Quality

Use clear, descriptive variable names

Make code readable for future you

Add comments for complex logic

Explain the "why" not the "what"

Break complex code into steps

Easier to debug and modify

Return consistent data structure

Same format every time

🐛

Debugging

Return debug information

Include intermediate values

Test incrementally (2-3 lines at a time)

Know exactly what broke

Create test cases with edge cases

Test with missing, invalid, and extreme values

🔧

Maintenance

Duplicate column before major changes

Keep a backup of working code

Document column purpose

Add comments explaining what it does

Don't repeat yourself (DRY)

Extract common logic to reusable columns


Quick Reference: Common Patterns

Pattern 1: Safe Data Access

groovy
let value = [Column Name]?.property?.nested || defaultValue

Pattern 2: Error Handling Wrapper

groovy
try {
  // Your code here
  return { success: true, data: result }
} catch (error) {
  return { success: false, error: error.message }
}

Pattern 3: Early Return Validation

groovy
if (!requiredField) return null
if (!validFormat) return { error: 'Invalid format' }
// Continue with processing

Pattern 4: Caching Check

groovy
let cached = [Cached Data]
if (cached?.timestamp && hoursAgo(cached.timestamp) < 24) {
  return cached
}
// Fetch fresh data

Pattern 5: List Processing

groovy
let items = [Item List] || []
let filtered = items.filter(item => item.condition)
let processed = filtered.map(item => item.property)
let total = processed.reduce((sum, val) => sum + val, 0)

Pattern 6: Consistent Return Structure

groovy
return {
  success: true/false,
  data: { /* results */ },
  error: null/errorMessage,
  timestamp: now()
}

Next Steps

You've completed Part 9: Custom Code Columns!

You now know:

  • ✅ When to use Custom Code (9.1)
  • ✅ How to write Custom Code (9.2)
  • ✅ Real-world examples (9.3)
  • Best practices for reliable, efficient code (9.4) ← You are here

What's next?

Apply these best practices to your own automations:

  1. Review existing columns: Find ones that could benefit from these practices
  2. Test with edge cases: Create test rows covering different scenarios
  3. Add error handling: Wrap code in try-catch blocks
  4. Optimize performance: Add caching where appropriate
  5. Document your work: Add comments explaining business logic

Remember: Good code is:

  • 🎯 Correct: Handles all cases including errors
  • Fast: Caches results and filters early
  • 📖 Readable: Clear names and helpful comments
  • 🔧 Maintainable: Easy to modify and debug
  • 🛡️ Reliable: Validates input and returns consistent structure

Happy coding! 🚀

Built with VitePress