-1

Many native objects in Apex rely on static methods and properties.

This seems an abnegation of S.O.L.I.D., or at least of the Dependency inversion principle.

As a developer who values the test pyramid, and therefore creates true unit tests (in addition to integration tests), this requires me to create wrappers for many native SFDC classes in order that I can mock out, spy upon, and otherwise control any code which I'm not targeting with my tests.

One might say this is bloating the code and, to look at typical Salesforce code, one might say my code is not "idiomatic", even though it is obviously relying on features supported by the language.

Are there any good reasons I should be ignoring S.O.L.I.D. principles and making my methods static?

closed as primarily opinion-based by battery.cord, Pranay Jaiswal, David Reed, Sebastian Kessel, Martin Lezer Jun 18 '18 at 18:57

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • Can you be more specific or concrete about the ways in which the use of static methods negatively impact testing in user code? There's nothing we can do, of course, about the core design of the Apex APIs. – David Reed Jun 18 '18 at 16:59
  • Feels very close to your other question here, I think these two could be made more distinct from each other. – battery.cord Jun 18 '18 at 17:04
  • Best practices are usually opinion-based, but opinions can be well reasoned. SfdcFox provided exactly the sort of answers I am looking for with this question. Also, I think best practices are an important topic for discussion and as such, at least some opinion based questions should be allowed. – Brian Kessler Jun 18 '18 at 23:36
  • @battery.cord, the questions are similar but distinct. The difference isn't even subtle. Anything more different would either be a different question or reworded just for the sake of gaming search engine robots. As SfdcFox's answers demonstrate -- prior to being rudely put on hold -- these questions were generating exactly the sort of discussion I wished for. – Brian Kessler Jun 18 '18 at 23:56
  • @DavidReed, when I write unit tests, I only want to test exactly what is happening in my unit. What's happening in any other class is out of scope for my unit test (yes, there should be integration tests as well, but I'm not concerned with that right now). So, I want to mock out all such dependencies (especially if there is anything that might make the result unpredictable such as a date or a random number) so I can control the return. Yes, arguably sometimes I am using too much mock, so I'd like to have a better understanding of appropriate uses of static so I can make better decisions. – Brian Kessler Jun 19 '18 at 0:03
10

In programming, we generally have two types of objects; those that deal with data, and those that deal with algorithms. We are definitely advised to use SOLID in Salesforce, but it has nothing to do with the static-ness of a method.


For example, let's look at a standard library, Math.

Single responsibility principle

Math only deals with doing math-y type stuff. It doesn't manipulate strings, or parse Integers, or anything else. It has one responsibility: to expose mathematical algorithms.

Open/closed principle

It's a standard library, so it's closed. If we need a whole new set of algorithms, we can write our own math library. It would clearly be a Bad Idea if we could modify the system library.

Liskov substitution principle

Does not apply to Math, because it has no subclasses. It does only one thing, and that's the Math-y stuff it does.

Interface segregation principle

This is a specific interface. It does math, and that's it, and each method is clearly defined for specific cases (e.g. handling Decimal versus Integer).

Dependency inversion principle

There's no dependencies here, so it's not even relevant.


Now, you're saying, "but what about my classes?" And the answer is the same. Each class should do one thing and do it well. We're told to avoid the use of "god" classes, and best practices state that we should keep each class as small as possible, for maintenance reasons.

There's different types of classes we use in Salesforce, including Visualforce Controllers, Lightning Controllers, Web Service classes, Rest Resource classes, Schedulable classes, and so on. Most everything that you do will be dictated by the standard APIs laid out for you.

A general rule of thumb is that if you have a choice, you should consider if any data is encapsulated in the class. If there is no data (or, at least, no client-specific data), methods should be static. All Math functions fall in this category. For classes where there is data being operated on (i.e. this actually means something useful), then the methods would not be static.

Also note that you can certainly use interfaces, that's a feature of the Dependency Inversion Principle. In fact, we use them all the time without really thinking about it. System.schedule, Database.executeBatch, System.enqueueJob, etc are all things we can only do with DIP in play. And you're definitely encouraged to do the same in your own code. Write interfaces, use existing interfaces, etc.

Without actually trying to sample how many methods out there seem to "violate" DIP as you understand it, realistically, that number is probably 1. I can think of a single case where it seems odd to construct an object that isn't used for data.

Going back to Math for a second, which code looks right to you?

Decimal d1 = Math.round(12.5);

Or

Decimal d2 = new Math().round(12.5);

The methods that are static are static because they make sense. They don't deal with data, they only implement algorithms. Even going by SOLID and TDD, etc, you'll find that it's often not necessary to mock or extend anything, or block of parts of code with Test.isRunningTest or TestVisible, etc.

Testing is about verifying output, and well-written code can generally do so using only its public interfaces. If it can't, I would consider that code suspect and probably in violation of best practices.

An E2E test does not care about how something got from point A to point B, only that it does. The next layer of testing, the service layer, doesn't care about the UI tests, it cares only that the services provided are working. Finally, unit tests don't care about the service layer or the E2E tests; it only cares about what's going on at this level.

There's a reason why it's the test pyramid, the lower tests support the upper tests. The upper tests are only there to care about the entire thing moving as one large unit, but the pieces inside are all black boxes. The lower unit tests verify their little small corner of the world without caring about what happens in the bigger world.

  • Don't all methods deal with algorithms? Aren't input values data? Granted, with Math we are talking about very well defined and understood algorithm with easily predicted output which can never change and still be correct. But consider Database. Validation rules can change, exceptions messages can change, and by definition it indisputably dealing with data. When my code consumes Database, doesn't the Dependency Inversion Principle suggest that I should not be depending upon Database, but rather that both the Database and my solution should depend on a common abstraction? – Brian Kessler Jun 19 '18 at 1:06

Not the answer you're looking for? Browse other questions tagged or ask your own question.