Both can be useful, but neither is better.
Which way this is handled has to be determined by gathering data and making decisions. Making the decision is a complex process that depends on requirements, what assumptions are safe to make and how the functions will be used.
Start with your "A" case:
...Do expensive validity check, return result...
function doJob(data, x)
if not isValid(data) // Expensive
...Do job, return result...
This favors correctness by forcing the data through a validity check each time
doJob() is called. This is fine when doing it once...
DataType data = ...
Number x = ...
doJob(data, x) // Expensive, but only once
...but sacrifices performance when done repeatedly on the same data:
for x in 1..50
doJob(data, x) // Expensive 50 times
If this meets your performance requirements, you've got all of the safety and performance you need and there's no need to do anything further.
If it doesn't, there has to be a trade-off to regain the lost performance. You might do this by providing a separate, non-validating version of
doJob() to optimize out the repeated validation as might happen in your "B" case:
if not isValid(data) // Expensive, but only once.
// At this point, we know data is valid and can use it safely.
for x in 1..50
You've traded some safety (leaving validation to callers) for performance (validating only once).
When providing code for others (or even yourself) to call, there's no substitute for finding out the intended uses before starting development and how it's actually used after you've released it. If the
1..n case turns out to be common, that's good reason provide a function that does it safely:
function doJobRepeatedly(data, n)
if not isValid(data)
for x in 1..n
The onus for correctness is still on you as a caller to
doJobUnvalidated(), but it concentrates understanding of the rules into your library and there's one implementation. This is much better than spreading knowledge and implementations across each place in calling code where it needs to happen.
Finally, and I can't stress this enough, make sure the assumptions made by your functions are documented so callers understand what they're getting into. There's nothing more frustrating than having your program go kaboom, debugging the failure and tracing it to an undocumented assumption in someone else's code.