I have seen classes having 100s of static variables defined. They are there for different purposes and I don't think, mostly are used in single transaction. They are used in different transactions. Does this impact transaction performance? What will be the best approach in this case?
3 Answers
In a previous role I worked in, we had such a class. We used it in every single class, both live and unit tests, and it had over a thousand lines of code. When I started there, it required about 45 minutes to deploy our code base. I convinced my superiors to make three specific optimizations.
The first one was the most dramatic: we turned off triggers while setting up unit tests using some static Boolean flags, which shaved off over 15 minutes in our unit tests. The second one was to move all required data initialization to a @TestSetup method. The third final dramatic change was to move static initialization of this "god class" to a lazy loading system.
The thing was, this static class had so much static initialization that it actually required over 1 second of CPU time by itself for every single transaction. Saving a record, any record, to the database, where triggers were involved (Accounts, Contacts, Opportunities, etc) all had a bare minimum of one extra second added to the transaction. This affected the UI, all of our asynchronous methods, and dramatically increased deployment time or production, since we had hundreds of unit tests.
By using lazy loading, we saved over one second on every unit tests, which reduced our deployment time by upwards of seven minutes, even after shaving off over 20 minutes from the first two optimizations. Our code was so bloated that we literally could not save more than 10 records in a single DML list because it would exceed governor limits. Gaining that extra second also significantly improved that limitation.
It would have been nice to have many of those variables split across a number of classes, but the refactoring load was just too significant. As such, I leave you with three specific things, not just one, in regards to this technique.
First, do not put everything in one class. We're given the ability to have hundreds of classes for a reason. Put constants nearby where they're used, when possible, and group them by category for each class. Second, use lazy loading if you have hundreds of variables in a class. Third, consider alternative solutions to using constants, such as Custom Labels. Custom Labels are like constants, but can also be translated automatically by the platform during runtime.
As an example of lazy-loading, here's a typical example:
// Avoid this
public static final String HELLO_WORLD = 'Hello World';
// Instead:
public static String HELLO_WORLD { get { return 'Hello World'; } }
Without a set
property, it is effectively final
(read-only), and because it returns a value only when accessed, it incurs zero overhead whenever it is not used. However, and I cannot stress this enough, you should strongly consider using Custom Labels, instead.
We're allowed 5,000 of them in our projects, which should be enough for most applications. If you don't upload any translations for a given language, they don't error, but instead just return the default language version. They also allow us to change critical strings without a new code deployment.
I'm missing some other, less vital stuff here, like how having a large class risks more merge conflicts when people start adding stuff in to it, how it can be harder to maintain the code when you need to make some types of fixes, uses more heap, which is a precious resource, etc. This answer is already long enough, and focuses on the main question of performance.
tl;dr
- Don't put hundreds of properties in a single class, it can be hard to read/maintain, can cause merge conflicts, etc.
- Don't use
static final
properties if you can help it, because it can significantly impact CPU time and heap usage. - Do use Custom Labels whenever possible to minimize the need for such a class to begin with, less code is better code.
-
2IMHO, labels should not be used for values that do not require localisation. I have seen people use them for configuration values etc. and this is, again IMHO, an abuse of the mechanism.– Phil WMar 19, 2022 at 15:31
-
2
-
I agree with @PhilW here, IMO we should not use custom labels if translations are not required. Instead we should use classes having only required variables. For existing one, lazy loading is good idea. Mar 20, 2022 at 13:14
-
I am accepting this as answer, though it was tough to select anyone. This was a subjective questions and will suggest to check other responses as well. Mar 22, 2022 at 4:19
-
Very informative! I think the
lazy loading
example is a little misleading though. My understanding is that this is just a getter, and it will be evaluated every time the variable is referenced. Maybe it won't matter here, but I came upon this trying to see if I could lazy load aPattern.compile()
with something as elegant aslazy val
in Scala vs anull
variable and check with a handful of lines, and in that scenario, you'd see a big difference from the lazy perspective.– jon_wuNov 23, 2022 at 19:31
There is some risk of opinion clouding this Q&A. However from my perspective note that:
- Every static member of a class is initialized when that class is first loaded in a transaction.
- The processing required for such initialization depends on the type of the static variable.
A static variable that is a primitive type, including string, will simply have an assignment performed, which is likely going to have negligible performance overhead.
A static variable that is a custom apex class that is assigned to a new instance of that class (rather than just null) not only has the assignment but must also execute the apex class's:
- static initialization, if this is the first use of the class in the transaction,
- constructor and initializer blocks.
Thus if you have a class that performs some expensive operation, such as getting some SObject describes through the dynamic schema access methods like Schema.getGlobalDescribe
, during construction or initialization, and have an instance held in another class's static variable, the latter class will necessarily suffer a performance hit.
In my opinion over use of both static members and static methods is an anti-pattern. Both should be minimised to:
- Ensure only relevant initialization is performed (e.g. in parameterized constructors that selectively set instance variables)
- Enable mocking of methods to allow appropriate unit tests to be developed, isolating one class from the other, using the StubProvider API or virtual classes and methods.
The latter point is covered well in other Q&As here and in various blog posts by well-known Salesforce devs. Just do an Internet search to discover more.
-
1Anecdotally, that negligible performance overhead had cost one of my previous projects 1 second of CPU time for every unit test, across a few hundred unit tests. I saved at least 7 minutes from refactoring trivial strings to lazy loaded strings, nothing more complicated than that.– sfdcfoxMar 19, 2022 at 15:20
-
Obviously a very large number of values can have an impact. Like Keith said, perhaps there is room for some refactoring in such a case. I am guessing you had some arrays or maps involved, or very large numbers of "constants"?– Phil WMar 19, 2022 at 15:26
Couple of points to add to Phil W's excellent answer:
- Having 100's of variables in a class whether they are static or not is likely an indicator of poor design choices e.g.:
- Grouping logic together that should be broken out into smaller pieces so the logic of each piece can be thought about separately
- Named variables per SObject field that should not be there
- Debug logs get flooded by the initialization whether it is trivial or not
- Unless lazily initialized, some static initialization, often of collections, can impact performance
So you are right to think that the code you've seen is likely not a good example to follow on the use of static
, so likely also in other factors of its design. Short term, you may have to live with the code; longer term consider refactoring - reading Martin Fowler's book Refactoring : Improving the Design of Existing Code is a great place to start.
-
can impact performance downplays the significance of the matter. I actually had a class in a project that added over one second of CPU time to every single UI interaction, every single asynchronous call, and every single unit test. In my anecdotal example, I reduced our deployment times by over 7 minutes just by refactoring our class to lazy loading.– sfdcfoxMar 19, 2022 at 15:15
-
1Hi @sfdcfox, You are right, particularly where people come from frameworks where statics are loaded once and shared across many transactions and users. That optimisation is is an anti-pattern in Apex where statics only live for a single transaction.– Keith CMar 19, 2022 at 15:54