In the world of AI-generated code, we need to take a step back and go to the fundamentals once more. It is easy to fall into a velocity-over-quality trap. When pressed for deadlines and silly metrics like lines delivered, developers tend to lean on the tooling and trust it to meet the requirements.
In the world of Salesforce, developers and domain experts are always pushed to the side in favour of marketing mantras such as Click Not Code, citizen developers, etc. Aggressive cost-cutting comes into play as well, where skill and knowledge are pushed to the side in favour of low-skilled employees because anyone can do it, or just let AI do it.
Low-code, citizen developers, and all of that is fine marketing material, but sooner or later reality kicks in.
Salesforce Ben did a fine article that touches on governance and some of the topics observed here, so do check it out.
Governance is a topic on its own; here in this article, we would like to present a production bug that sat unnoticed for months in the production environment and which illustrates the necessity for basics
A production bug that seldom surfaces
Let's consider a case from a production Salesforce org. A single bulk operation inserting the total of 4242 records hit the limit:
| System.LimitException: Too many SOQL queries: 101 |
|---|
Investigation of the solution revealed the real issue, but finding it took time, as it was buried in a lot of code. The developer, with or without AI, was planning and anticipating possible issues, but completely missed the mark. The reason: fundamentals. Before jumping into the example, imagine how much damage one such person can do, now supercharged with AI and pressed for deadlines.
- /*
- * Generic trigger handler for SomeObject__c
- */
- public class SomeObjectTriggerHandler {
- public void afterInsert(List
- runSomeCodeAfterInsert(newRecords);
- }
- private void runSomeCodeAfterInsert(List
- LimitSetting__mdt limitSetting = ...; // threshold set to 200
- if (chunkRecords.size() > limitSetting.SomeObject_Trigger_Threshold__c) {
- Database.executeBatch(new SomeBusinessLogicBatch(chunkRecords), ...); // large-volume escape valve
- return;
- }
- new SomeBusinessLogic(chunkRecords).execute(); // SOQL exists here, synchronous
- }
- }
Root Cause
The basics dictate that Salesforce caps every Apex trigger context at a maximum of 200 records, regardless of how many records the originating DML statement actually contains. A single insert/update of N records where N > 200 invokes the object’s trigger ceil(N / 200) times within the same transaction, resulting in each invocation receiving a different set of 200 records (chunks). Knowing that, we go back to the check at line 14 of the pseudo code listed above. The threshold is compared against the size of the current chunk, which can never exceed 200; therefore, the check for bulk processing is pointless and never met. The large-volume escape valve is dead code.
The bug reads as follows: a trigger handler method runs once per chunk invocation. Each chunk is treated as an independent unit of work, so the identical (or structurally identical) query found in the SomeBusinessLogic method is repeated roughly N / 200 times. For a set of 4200 records, the SomeBusinessLogic method is generating 21 extra queries from a single piece of logic.
This is not a classic “SOQL inside a for loop over records” bug, as the query found in the SomeBusinessLogic method is perfectly correct, by-the-book code. Reviewing SomeBusinessLogic will find nothing out of the ordinary. The bug is the chunk-to-chunk repetition: the same kind of query fires again on the next chunk, and the next, and the next, until the number is exhausted or the limit is hit. As all of it exists and runs within the same context, all of it is counting against the same transaction’s 100-query synchronous limit.
Why it’s easy to miss in review
This bug passed multiple quality gates: developer review, unit test, peer review, AI review, testing by testers. A good unit test would have caught it; peer reviewers should have known better, AI reviews are hit or miss, testers are often pressed for time.
- Each method, read in isolation, looks correctly bulkified
- The check at line 12 might throw off a less or non observant reviewer
- Small test data with < 200 records or usual production activities never expose it
- The multiplication issue only becomes visible when the triggering DML is large enough to span multiple chunks
Why AI makes this worse, not better
An AI assistant, ever eager to please, asked to write a trigger handler to such a specification would almost certainly produce something that passes review. The code would look professional, bulkified, etc. The output would still carry the chunk-multiplication bug, because nothing in the spec instructed it otherwise.
The same applies in reverse: an AI asked "why did this fail?" can correctly explain the 200-record chunking rule beautifully. But one has to know how to ask. The developer who doesn’t know the fundamentals doesn’t know what they don’t know. The gap between “I don’t know” and “I don’t know that I should know” is exactly the gap AI can’t bridge.
There’s a second, more damning failure. The developer who accepts the AI’s answer without being able to verify it is a ticking time bomb. Models are non-deterministic systems prone to hallucinations and other issues. If one can not check and verify the answer against one's own understanding/reasoning, then the floodgates are wide open.
The fundamentals are the review layer
AI raises the floor of what one can produce, but it does not raise the floor of what one can verify. And in software, the ability to verify is everything. Otherwise, the price of speedy delivery will be paid later in technical debt. At the moment of writing this article, there is no replacement for senior people, experience, and plain old basics.
The developer who knows fundamentals can (in the context of this example):
- Spot the query that runs per-chunk instead of per-transaction, even when it looks bulkified
- Write proper unit tests
- Recognize that a threshold guarding against Trigger.new.size() > 200 is impossible to trip.
The practical takeaway
For a Salesforce professional, knowing the basics means knowing what is possible. That’s governor limits, transaction boundaries, trigger execution. Having a good intuition for what is a good direction and what is a dead end.
This was a coding example, but it can be extended to illustrate any activity on the platform. For those who write code by hand, do it now more than ever. Keep learning the code, the patterns, the platform, and platform principles. Lean into AI but understand the output. The review burden went up with it, and the knowledge required to review went up with it.
No matter how much marketing is applied, stakeholders should understand reality. One would not accept a dentist to perform brain surgery. One would not accept a medical prescription one cannot read. In the same manner, one should not ship solutions one cannot verify.