Haskell solves this with newtypes, “here is a transparent container that certifies that you did the appropriate validation already,” that helps for this.
The advice that I really want to hammer into people's heads is, prefer “sad ifs.” That is, I will almost always find this
if (something_is_wrong_in_way_1) {
// fix it or abort
}
if (something_is_wrong_in_way_2) {
// fix it or abort
}
if (something_is_wrong_in_way_3) {
// fix it or abort
}
more readable and maintainable than this if (things_are_ok_in_way_1) {
if (things_are_ok_in_way_2) {
if (things_are_ok_in_way_3) {
// do the happy path!
} else {
// fix or abort thing 3
// if fixed, do the happy path
}
} else {
// fix or abort thing 2
// if fixed, test way 3 again
// if way 3 is good do the happy path, else fix it
// ...
}
} else {
// ...
}
I feel like it's in human nature to focus on the expected case, I want everyone whose code I meet to do the exact opposite, focus primarily on the unexpected. Every “if” imposes a mental burden that I am keeping track of, and if you have to go to an external system to fetch that information, or you need to exit early with an error, I can immediately discharge that mental burden the moment I know about it, if the handling and the detection are right next to each other.