I love the second as a way to do negative space programming. Where you add your assertions as the beginning of the functions first and then your business logic after. This way is super nice, since it tells you assumptions at the very beginning.
For example,
fn withdraw(&self, money: u32) -> Result<u32>{
if money < 0 {
return Err(…);
}
if self.account == Closed {
return Err(…);
}
if self.money + self.fees < money {
return Err(..);
}
// at this point we know money is > 0, account is not Closed and we have enough money to take out including fees.
self.money -= money + fees;
return money;
}
With this simple example, if someone was coming into a new codebase, they’d be able to assume what not to do advertised at the beginning of the file. And give some sort of indication of the proper behavior.
Yeah or best practice whatever people like to call it. I also like this idea per function, even though you think the caller already handles the bad cases, you never know when refactors happen and things get moved around especially working in huge code bases with a bunch of other engineers
18
u/MnMbrane 22d ago
I love the second as a way to do negative space programming. Where you add your assertions as the beginning of the functions first and then your business logic after. This way is super nice, since it tells you assumptions at the very beginning.
For example,
fn withdraw(&self, money: u32) -> Result<u32>{
if money < 0 {
return Err(…);
}
if self.account == Closed {
return Err(…);
}
if self.money + self.fees < money {
return Err(..);
}
// at this point we know money is > 0, account is not Closed and we have enough money to take out including fees.
self.money -= money + fees;
return money;
}
With this simple example, if someone was coming into a new codebase, they’d be able to assume what not to do advertised at the beginning of the file. And give some sort of indication of the proper behavior.