Versions Compared
Key
- This line was added.
- This line was removed.
- Formatting was changed.
Table of Contents |
---|
General Recommendation: use foreach
When the intent is to iterate over the full collection, the best practice is to use the foreach syntax. It is faster and avoids the problems detailed below. See Control Structures, Lesson 10: Scheduled Functions, Flow Control, Basic Messaging, or Quick Reference for how to use foreach.
Looping Backwards to Avoid Skipping Records
Consider the following DSL snippet:
Code Block |
---|
region[] regions = region:empty(index);
region temp;
for (int i = 0; i < regions.length(); i++) {
temp = regions.get(i);
temp.index = region:get_index();
temp.save();
} |
If an item in a collection is changed in such a way that it does not satisfy the selector criteria that populated it in the first place, it is immediately removed from the collection. In the above example, looping forwards, it means an item is removed while the index is still incremented resulting in a skipped item. Looping backwards, however, means all items are still iterated over even if the current one is removed from the collection. So in this scenario, the following would be considered best practice to achieve what was intended above:
Code Block |
---|
region[] regions = region:empty(index);
region temp;
for (int i = regions.length()-1; i >= 0; i--) {
temp = regions.get(i);
temp.index = region:get_index();
temp.save();
} |
Looping Backwards to Avoid
IOB Errors and Record SkippingIndex out of Bounds Errors
Similar to the previous section, looping forwards over a collection while changing it can result in index out of bound errors. For example, referring to the topmost snippet, this would have happened if regions.length()
was assigned to an int regionsLength
beforehand and the for loop was written as for (int i = 0; i < regionsLength; i++)
. The solution here is also to loop backwards instead.
"Caching" your Query Result for Better Performance
Consider the following DSL snippet:
Code Block | ||||
---|---|---|---|---|
| ||||
Children[] cs1 = /*backing selector*/ for (int i = 0; i < cs1.length(); i++) { /*loop code*/ } |
If the size of the collection cs1 were to change due the loop code removing an item from the list (e.g. by updating an attribute which will exclude it from the selector's result), the selector (and the ensuing SQL query) will execute again to update the collection. If it's a complex selector resulting in a resource intensive SQL query, it might slow down performance significantly.
One way to handle this is to create a new, "static" list, copied from the dynamic list. For example, cs2 in the code below will not be refreshed, and iterating over it will not cause the cs1 backing selector te be re-executed for each iteration causing a collection size change:
Code Block | ||||
---|---|---|---|---|
| ||||
Children[] cs1 = /*backing selector*/ Children[] cs2; for (int i=0; i < cs1.length(); i++){ cs2.append(cs1.get); } for (int c = 0; i < cs2.length(); c++){ /*loop code*/ } |
You can think of cs2 in the above snippet as a cached copy of cs1. Using static in addition to dynamic lists is, however, not considered to be best practice in all scenarios, and should be evaluated on a case by case basis. It's a good idea to "cache" your collection when most or all of the following apply:
- The likelihood of items being changed when looping over them, and thus the likelihood that the collection will need to be "refreshed" resulting in the selector being executed again, is high
- The backing selector is complex and will execute a resource intensive query
- The typical amount of data being processed or iterated over is high
- It's relatively quick to copy the collection contents to a different collection