Assuming you are writing in a language that is OO first rather than functional first.
Run time checks
Hard to read code? Why? You can start the method by expressing that certain values are not valid. That should make it easier to read/understand.
Validation is a different concern anyway, you should know where it starts and where it ends, refactor your code so that is easy to see, thus improving readability. If the code to validate the input is actually complicated enough to make it hard to read, that is a clue that you should be extracting the validation to another method.
I understand there are some situations where it is non-obvious how do to that without a compromise in efficiency. For example, validating a list could mean to iterate over it. And then iterate over it again to actually use it... Well, if you have a solution such as Linq or similar, you should be able to write the validation code separate from the use of the list, while iterating over it only once, thanks to Linq being lazily evaluated.
You might also want to avoid duplicated validation in the internals of the code... you could use conditional compilations, debug assertions, code contracts, or similar solutions to make the extra validation only on development and testing environments.
Compile time checks
If you have a good code contracts solution, it would be validated on build.
Speaking of tools validating the code... can you get or write one? I'm thinking of a lint or similar, which could check preconditions on the code and be added to build integration. I know such tool is not always cost effective, however if your situation is specific enough and important enough, then such tool would be of value.
However, it is better to express validations as part of the type system, so they are checked at compile time without the need of additional tools.
You could – for example – have a type
T, and a type
ValidatedT, that can only be initialized with valid values of
T. Then your internal code will only accept
ValidatedT (of course, with a better name), making sure that the validation already happened, and regardless of how many method the
ValidatedT value is passed down, the validation only happened once. This should also improve readability, as you could clearly see where do you have
T and where do you have
ValidatedT. Not only that, but you could have
ValidatedT for different situations...
I mean, if I say the type
string, what is it? A username? a product description? what? However, if I say the type
Username, then you know what it is in the system, and you know it is a valid value.
Oh, by the way, you can still get the benefits of easier to read code, although without the compile time checking, if you use type aliases.