Analyzing the problem on a single level operation is incorrect. New code and simple operations are fully covered with exception/try/catch flow.
The main point i tried to attract your attention is that if you working in batches/queues/pipelines you still need errors collection, and if you made error flow with exceptions - you have to extract validations to separate classes/dto-s, made return types as objects, try/catching as fast as possible, and then you will still met errors from remote systems, network errors, and invalid response parsing that need to be logged as much close to task registration place. So correct analysis - providing an example with at least foreaches/yields instead of simply calling native functions. In small functions IF statements are overkill, and even try-catch could be skipped, because the whole script will fall and that's correct behavior. Case is in that the script should continue working while working with chaining/nesting, and should be able to easily refactor any step to skip one or more bulk-data rows of each step on any nesting level.