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.