Neither, you should only do checking on the boundaries between systems.
What is boundaries between system?
- When you receive input from the user,
- When you read a file, network, or socket,
- When you do system calls or interprocess communication,
- In library codes, in all public-facing APIs.
- etc.
In library code, since public APIs may be called by external code, you should always do check in anything that can be called by external code. There is no need to do checking for an internal-only method (even if its access modifier is public). Argument errors may then be signalled by exception or by return code depending on personal taste, however checked exception cannot be ignored so it's usually the preferred method to signal errors to external codes.
In an application code (as opposed to a library), checking should only be done when receiving input from user, when you load a URL, when reading file, etc. There is no need to do input check in public methods because they are only ever called by your own application code.
Within your own code, you should avoid the situation where you need to check in the first place. For example, instead of checking for null
, you can use "null object pattern". If you still need to do a check though, do it as early as possible, this usually means doing it outside but try to find even earlier points.
Even if not written down formally and even if it's not enforced, all methods whether its internal or public-facing should have a contract. The difference is that in external-facing code, you should enforce the contract as part of your error checking, while in internal-only code you can and should rely on the caller knowing what to and not to pass.
In short, since checking is only done on system boundaries, if you actually have a choice of whether to check inside or outside a method, then you probably should not be checking there since that is not a system boundary.
In a system boundary, both sides always have to check. The caller had to check that the external call succeeds and the callee had to check that the caller gives a sane input/argument. Ignoring an error in the caller is almost always a bug. Robustness principle applies, always check everything you receive from external system, but only send what you know that the external system can accept for sure.
In very big projects though, it's often necessary to compartmentalize the project into small modules. In this case, the other modules in the project can be treated as external systems and you should therefore do the checks in the APIs that are exposed to the other modules in the project.
TLDR; checking is hard.