Hardcoding
The final common mistake I want to discuss here is that of hardcoding within Apex—particularly, hardcoding any type of unique identifier such as an ID or a name. For IDs, it is probably quite obvious for most developers as it is well established that between different environments, including sandbox and production environments, IDs can and should differ. If you are creating a sandbox from a production environment, then at the time of creation, the IDs are synchronized for any data that is copied down to the sandbox environment. Following this, IDs do not remain synchronized between the two and are generated when a record is created within that environment.
Despite this, many developers, particularly those working within a single environment such as consultants or in-house developers, will hardcode certain IDs if needed. For example, consider the following code:
for(Account acc : Trigger.new) { if(acc.OwnerId = 'SOME_USER_ID') { break; } //do something otherwise }
This code is designed to skip updates on account records within our trigger context owned by a particular user, most commonly an application programming interface (API) user or an integration user. This pattern enables a developer to filter these records out so that if an update is coming via an integration, actions are skipped and the integration can update records unimpeded.
Should this user ID change, then we will get an error or issue here, so it is wise to remove this hardcoded value. Given that the ID for the user should be copied from production to any sandboxes, you may ask why this is needed. Firstly, there is no guarantee that the user will not be changed for the integration, going forward. Secondly, for development purposes, when initially writing this code, the user will not likely exist in the production organization (org), and so, in your first deployment, you will have to tweak the code to be environment-specific. Thirdly, this also limits your ability to test the code effectively, going forward. We will see how shortly.
As an update to this code, some may recommend making the following change (note that this is precisely the instance where we would extract this query to a utility class; however, we are inlining the query here for ease of display and reading):
for(Account acc : Trigger.new) {     User apiUser = [SELECT Id FROM User WHERE Username = 'my.    api@user.com'];     if(acc.OwnerId = apiuser.Id) { break;     }     //do something otherwise }
This code improves upon our previous code in that we are no longer hardcoding the user record ID, although we are still hardcoding the name. Again, should this change over time, then we should still have an issue, such as when we are working in a sandbox environment and the sandbox name is appended to the username. In this instance, the code would not be executable, including in a test context, without updating the record to be correct. This could be done manually every time a new sandbox is created, or through the code in the test. This means that our test is now bound to this user, which is not a good practice for tests, should the user change again.
In this instance, we should remove the name string to a custom setting for flexibility and improved testability. If we were to define a custom setting that held the value (for example, Integration_Settings__c
), then we could easily retrieve the custom setting and the username for a query at runtime without the query, as follows:
for(Account acc : Trigger.new) {     Integration_Settings__c setting = Integration_Settings__c.    getInstance();     User apiUser = [SELECT Id FROM User WHERE Username =     setting.Api_Username__c];     if(acc.OwnerId = apiuser.Id) { break;     }     //do something otherwise }
Such a pattern allows us many benefits, as follows:
- Firstly, we can now apply different settings across the org and profiles, should we so desire. This can be increasingly useful in orgs where multiple business units operate.
- Secondly, for testing, we can create a user within our test data and assign their username to the setting for the test within Apex. This allows our tests to run independently of the actual org configuration and makes processes such as continuous integration (CI) simpler.
- Finally, we could extract both of these lines (the retrieval of the custom setting and the user query) to a utility class to abstract away, for the entire transaction to use as needed.