2

I have been asked in Interview that why did I use Design patterns just for one or two methods, when can you use helper classes to get the things done for you.

I explained that having Quote with more than 200 quote lines, the rep may wants to go for splitting the lines by linking to another cloned quote so that order and order lines creation might be easy. Some times we might do cloning as well for quote with quotelines(another business use case).

I mentioned that the methods that are common i kept in parent class, and individual responsible methods i kept in subclass. Using strategic design pattern, I have done the solution. and also i mentioned that this is useful to be implemented for contract and its lines, order and its lines..He is not convinced as he was arguing saying we can use helper classes to achieve the common functionality. Even singelton design pattern, why do you require that process when you know using static you can achieve the functionality. I tried to explain that static methods will load irrespective of need. but where as singelton gets called only when we tried to call. But somehow he is mentioning that helperclass is enough in apex.

I know we do design patterns for apex triggers and they are really good than just implementing straight way of using triggerhelper classes... But honestly whats the benifit of using Design patterns..Is it just to show the code clean and elegant? How to answer this question better way or correct way?

1
  • 3
    From an extensive study summarizing research on benefits of design patterns: primary studies provide empirical evidence on the positive effect of documentation of designs pattern instances on program comprehension, and therefore, maintainability . The study claims that other benefits of design patterns are less clear, at least empirically speaking.
    – identigral
    Commented Aug 10 at 3:18

4 Answers 4

6

The problem with programming in Apex, as opposed to most languages, is that we have a relatively unique set of constraints in the form of governor limits. We can't use heaps of memory (pun intended), and our transactions can't run as long as they'd like to completion. Even worse, some well-intentioned models that offer clean code are often detrimental to performance in many cases. As such, not all design patterns are necessarily ideal in Apex.

For example, in many languages, constants are used at compile-time and embedded directly into places they are used. In Apex, so-called constants (those marked final) always incur a CPU penalty if the class is referenced at all. This means that a class for every possible constant will suffer performance problems because of all the assignments that need to run for every transaction that uses the class. This means that constants need to be carefully considered.

Similarly, the deeper down the OOP rabbit hole you get, subclasses, interfaces, and so on, may harm overall performance. You do want your code to be maintainable, of course, but not at the risk of your app becoming so bloated that it cannot run in a reasonable amount of time. There's no point in having maintainable code that doesn't provide reasonable performance. The users don't care what your code looks like, they care that it runs and feels "snappy."

In most projects, there will be a balance of compromises between philosophical advantages of design patterns and the practicality of having code that actually runs within governor limits. Many design patterns actually run the risk of introducing unacceptable inefficiencies, such as accidentally running queries inside of loops. One commonly overlooked problem is that a framework may actively encourage unnecessary usage of governor limits for the sake of legibility.

This can mean, for example, that it is better to make sure all methods accept bulk parameterization, even if there is little-to-none chance that it needs to be called in bulk. Without an accurate crystal ball, it is impossible to predict how your code will be used in a year, five years, etc. Some level of defense against unexpected usage needs to be built in to any development model.

There is nothing inherently wrong with either helper classes or higher level abstractions, such as factory methods, singletons, virtual classes, and so on. What primarily matters is that the code is reasonably easy to read and write, performs well even when called in bulk operations, and can be consistently applied to a large variety of situations.

At the end of the day, what matters most are three core principles. First, it should be possible to cover as many test cases as possible with the least amount of effort for code coverage requirements, to minimize implementation time. Second, the system should be easy to reason through, even for a programmer new to a project, so that they can quickly identify problems/bugs and add new features. Third, the system should run efficiently, so that the user doesn't feel the urge to "cheat" the system by using unorthodox shortcuts.

Are you wrong? Maybe. Is the interviewer wrong? Also maybe. In fact, it's entirely possible for both to be wrong at the same time, as well as both to be right at the same time. Trigger frameworks, for example, are not specifically about just unit testing. They're also useful for ensuring efficient batching of operations, having a way to selectively disable triggers for bulk data operations, and so on.

In many cases, the design choices we make in Apex are more philosophical in nature, rather than grounded in pure computer science. There are tradeoffs that we must decide as acceptable losses. Maintainability over performance. Error handling versus debug logs. Every developer has their own unique experiences that drive their decisions, and not all options are mutually exclusive.

If you don't get the job, it's probably okay. A difference in opinions can make it much harder to work with a given project, or in a given role. Compromises will be made, or the project will suffer overall. In the end, what matters is the final user experience. If changes can be implemented swiftly, that's great. If the UI is as fast as possible, that's arguably even better. There is no One Final Solution to every problem, only a set of systems that offer a compromise between performance and speed to release.

2
  • 1
    I completely get all your points. There are many times, I get benifited your answers. I have the same opinion as yours, but you made the points in depth and crystal clear. My code is bulkified, Optimized, always. Thanks for your continous avaiablity and support.
    – Pratap M
    Commented Aug 10 at 4:33
  • I think the interviewer is correct. He was expecting better example of where we use design patterns.
    – Pratap M
    Commented Aug 10 at 4:38
6

To be honest, I wouldn't be convinced either. "Perfect is the enemy of good".

Sounds to me like you may have memorized some design patterns, but that you may not understand why they're used. I.e. you're falling victim to the "Law of the Instrument" (when the only tool you have is a hammer, every problem looks like a nail).

Even though code spends the vast majority of its lifetime in maintenance, the extra effort it takes to build something that strictly adheres to a set of principles, e.g. SOLID, may not be worth it. Especially when it's a single, small feature. YAGNI (You Aren't Gonna Need It). Why build a CalculatorFactory to get a concrete instance of AbstractCalculator where you can plug in a MultiplyOperation that implements a CalculationStrategy when you can do something like Integer customerPrice = lineItemPrice * discountPercent;?

Having to jump between 4-5 separate classes to understand how something works incurs a non-trivial mental load, even if you're the one who wrote it (ask me how I know), when you need to come back a year later and fix a bug or make a change. It's a challenge even if you have good documentation (and very few devs document well. Even fewer keep documentation up to date, be it in code comments or elsewhere).

If I were interviewing someone for a mid-level position, I'd be looking for things like:

  • Do they generally know how to write bulkified code?
  • Do they know how to write code in a way that's easily testable?
  • Can they resist the urge to overbuild a solution to a simple problem or add extra requirements that aren't actually required?
  • If there's a reason to write more sophisticated code, can they provide a convincing argument as to why the extra effort is worth it?
1
  • I see all your points. Thanks for enlightened me.
    – Pratap M
    Commented Aug 10 at 4:45
4

Every single non-trivial implementation using Apex is required to use design patterns - not GoF or similar, but Salesforce's own - in order to adhere to Salesforce's governor limits. I am talking about the use of async processing via Batchables, Queueables or Schedulables, and the various micro-patterns required for implementing bulkification.

These enforced patterns allow the implementation to fit within the guard rails imposed, as governor limits, and typically address CPU, heap, query and DML limits. Without applying these patterns, the implementation simply will not function at scale.

On top of that, there is Salesforce's requirement for unit testing with a minimum level of code coverage. This does not enforce use of any specific patterns, but applying them gives the opportunity to write testable code that only requires a fraction of the database interactions (and therefore massively more performant testing) than would otherwise be possible.

To benefit this way, the following patterns are needed:

  • Layers/separation of concerns. Make sure that database interactions are separated from business logic. This is an approach taken by fflib with domain, selector, unit of work and service concepts.
  • Dependency injection. To allow mocking of supporting code, code that is separately unit tested but that would be used by the code under test, the instances of that code must be "replaceable" with mock versions. This can be achieved by applying this pattern. Note that this demands the use of instance, rather than static, methods, since Apex does not permit mocking of static methods.

There are others that can be used, successfully, such as singleton (despite it being considered by some an anti-pattern, this can really help with "dependency injection" or "inversion of control" and testability), factories (again, helping testability and abstraction), builder (yup, helping testability and abstraction), flyweight (improving heap usage), command (encapsulating business logic, selected in a manner dependent on the request), fluid interface (to make code compact and readable), inversion of control (to allow different implementations to be returned, when requested, in support of "dependency injection", enabling mocking and therefore testability) etc.

Many of these patterns are actually quite light-weight to implement and quite compatible with the constraints of Apex and the Salesforce platform, and many contribute to making the code flexible, testable and reusable.

And did I mention how you can make your tests run way faster by only using the database when essential (e.g. testing your selector or unit of work classes). And that requires those patterns to be applied.

1

I have quite different views compared to the rest of the responses. Salesforce had a statement limit before the CPU limit came into the picture. Thus, it was extremely efficient to make things public static and get them done quickly. Even most of the Salesforce documentation/tutorials focus on public static methods, whereas, in other technologies like Spring Boot, OOP is at the heart and soul of what they do.

We need to understand Salesforce’s perspective: it’s marketed as a no-code/low-code solution, and if coding is necessary, it can be done with fewer lines of code and without the complexity of learning frameworks like SOLID, unlike C# or Java. As a result, most senior developers have only seen code that never followed the OOP paradigm.

Inversion of control and dependency injection are extremely important for properly testing your code, but these are hard concepts, so Salesforce introduced Test.isRunningTest() and CalloutMocks, which could have been easily eliminated by using a robust mocking framework like ApexMocks.

Even though Salesforce calls it a Unit Testing framework, when you test a trigger, you are actually doing integration testing. This has led to extremely slow deployment times—3-4 hours to get changes validated in production for major banks and healthcare organizations. Yes, I agree there are things like unlocked packages to speed this up, but they never give confidence that someone else's code won’t break my functionality if everyone is building in isolation. In the end, all packaged code runs in the same environment and is subject to the same set of governor limits, so skipping tests in production by using unlocked packages scares the bejesus out of me.

Yes, there is a learning curve, but does that mean we should continue to follow mediocrity our whole lives because new people won’t understand the code? If we always followed this philosophy, we would not have progressed in the fields of Math and Science.

Sometimes, I do understand that it can be overkill, but simply discarding everything into a 1,000-line helper class would never do any good. you dodged a bullet there.

2

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .