← Volver al Blog
3 min de lectura

5 Mejores Prácticas de Apex que Todo Desarrollador Salesforce Debe Seguir

Aprende las mejores prácticas esenciales de Apex que te ayudarán a escribir código más limpio, eficiente y escalable en Salesforce. Desde bulkificación hasta manejo de errores.

5 Mejores Prácticas de Apex que Todo Desarrollador Salesforce Debe Seguir

Como Arquitecto Salesforce con más de 10 años de experiencia, he visto innumerables implementaciones de Apex, algunas elegantes, otras no tanto. En este artículo, compartiré las cinco mejores prácticas más importantes que todo desarrollador Salesforce debería seguir para crear aplicaciones escalables y robustas.

1. Siempre Bulkifica Tu Código

Los governor limits en Salesforce están diseñados para garantizar el uso justo de recursos compartidos. El error más común que veo es escribir código que funciona para un solo registro pero falla al procesar múltiples registros. El código que no está bulkificado fallará eventualmente en producción.

// ❌ Mal: Query dentro de un bucle
for (Account acc : Trigger.new) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

// ✅ Bien: Recoger IDs primero, luego hacer una sola query
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.new) {
    accountIds.add(acc.Id);
}
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}

2. Usa Maps para Acceso Eficiente a Datos

Los Maps son tu mejor aliado cuando necesitas acceder a registros por su ID o cualquier campo único. Proporcionan tiempo de búsqueda O(1) que es significativamente más rápido que iterar a través de una lista (O(n)) para encontrar un registro coincidente.

Map<Id, Account> accountMap = new Map<Id, Account>(
    [SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds]
);

for (Opportunity opp : opportunities) {
    // Acceso instantáneo sin bucles adicionales
    Account relatedAccount = accountMap.get(opp.AccountId);
    if (relatedAccount != null) {
        // Procesar con datos de la cuenta
    }
}

3. Implementa Manejo de Errores Adecuado

Nunca dejes excepciones sin manejar. Usa bloques try-catch de forma estratégica y asegúrate de manejar excepciones específicas como DmlException en lugar de una Exception genérica.

public class AccountService {
    public static void updateAccounts(List<Account> accounts) {
        try {
            update accounts;
        } catch (DmlException e) {
            // Manejar errores DML específicos
            for (Integer i = 0; i < e.getNumDml(); i++) {
                accounts[e.getDmlIndex(i)].addError(
                    'Error al actualizar: ' + e.getDmlMessage(i)
                );
            }
        }
    }
}

4. Separa Responsabilidades con Clases Handler

Mantén tus triggers limpios delegando la lógica a clases handler. Los triggers solo deben ser responsables de enviar el contexto al método handler correcto. Esto hace tu código más testeable, reutilizable y mantenible.

// El trigger es mínimo
trigger AccountTrigger on Account (before insert, before update, after insert) {
    AccountTriggerHandler.handle(Trigger.new, Trigger.oldMap, Trigger.operationType);
}

// La clase handler contiene la lógica real
public class AccountTriggerHandler {
    public static void handle(List<Account> newList, Map<Id, Account> oldMap, TriggerOperation operation) {
        switch on operation {
            when BEFORE_INSERT {
                validateAccounts(newList);
            }
            when BEFORE_UPDATE {
                handleFieldChanges(newList, oldMap);
            }
            when AFTER_INSERT {
                createRelatedRecords(newList);
            }
        }
    }
}

5. Escribe Tests Significativos

Los tests deben cubrir no solo el código, sino la lógica de negocio real. Siempre prueba:

  • Escenarios masivos (200+ registros)
  • Casos positivos (comportamiento esperado)
  • Casos negativos (manejo de errores)
  • Permisos de usuario (runAs)
@isTest
private class AccountServiceTest {
    @TestSetup
    static void setup() {
        // Crear suficientes datos para probar límites
        List<Account> accounts = TestDataFactory.createAccounts(200);
        insert accounts;
    }

    @isTest
    static void testBulkUpdate() {
        List<Account> accounts = [SELECT Id, Name FROM Account];
        
        Test.startTest();
        AccountService.updateAccounts(accounts);
        Test.stopTest();
        
        List<Account> updatedAccounts = [SELECT Id, LastModifiedDate FROM Account];
        System.assertEquals(200, updatedAccounts.size(), 'Todas las cuentas deben actualizarse');
    }
}

Conclusión

Seguir estas mejores prácticas te ayudará a construir soluciones Salesforce más robustas y escalables. Recuerda: el código que funciona para un registro debe funcionar para miles.

Trabajemos juntos

¿Necesitas ayuda con Salesforce?

Guillermo Miranda

Ayudo a empresas a diseñar y construir soluciones Salesforce escalables.