I often struggle with finding the balance between safe but complex looking code vs unsafe but very easy to understand code in Swift. A good example would be:
struct Account {
var coins: Int
}
vs.
struct Account {
var _coins: Int
var coins: Int {
get { _coins }
set { _coins = abs(newValue) }
}
}
vs.
struct Account {
var _coins: Int
var coins: Int {
get { _coins }
set {
// Outright aborting the set
guard newValue >= .zero else { print("Negative set aborted"); return }
_coins = newValue
// or going through with it but printing a warning
if newValue < .zero else { print("Coins set to negative value") }
_coins = newValue
}
}
}
vs.
struct Account {
var coins: Int
mutating func setCoins(to newValue: Int) -> Bool {
guard newValue >= .zero else { return false }
coins = newValue
return true
}
}
vs.
struct Account {
var coins: Int
mutating func setCoins(to newValue: Int) throws {
guard newValue >= .zero else { throw AccountError.negativeCoins }
// potentially different types of errors could be thrown here
coins = newValue
}
}
vs.
struct Account {
var _coins: Int
var coins: Int {
get { _coins }
set {
assert(coins >= .zero)
// or
precondition(coins >= .zero)
_coins = newValue
}
}
}
Each one of these have soooo many different implications to the safety of my code (yes I know, welcome to coding). For example:
Option 1: Relying on not making a mistake
Pro: Definitely the easiest to read. I can simply be very careful while coding.
Con: I have to be very careful while coding.
Option 2: Correcting input silently
Pro: Still maintains the naturalness of myAccount.coins += 100
Con: My code will silently convert the negative value to a positive one and I'll never know that somewhere in my code this strange thing happened.
Option 3: Printing that something went wrong
Pro: Still has the naturalness of using setters like myAccount.coins = <whatever>
Con: This is the weirdest cuz it's an unsatisfying mix between "I'm trying to be careful" and "I don't care". Printing an error message seems like a flimsy way to deal with this.
Option 4: Using a boolean to check for success
Pro: Now I am properly notified that something went wrong and my code hasn't become crowded with any do-try-catch blocks
Con: I wouldn't know what exactly caused the error so I won't know how to handle it
Option 5: Using Error Handling
Pro: Seems like the most solid solution
Con: Makes code kinda ugly in my opinion. Also using a function-setter when Swift has it's own custom setters for properties makes me feel like I'm doing something wrong
Option 6: Using assert or precondition
Pro: This is what I tend to use as it maintains cleanliness while also raising a flag
Con: Now I have to decide between using assert
or precondition
which is the whole essence of my dilemma. The balance between too safe vs. too unsafe.
How do you guys go about balancing this out?
P.S. This entire crisis stemmed from this: (My Stackoverflow Question)
struct Account {
var strikes: Int = 0
private var _monetizationLevel: Int = 1
var monetizationLevel: Int {
get { _monetizationLevel }
set {
guard strikes == 0 else { print("Please resolve copyright strike before adjusting moentization level"); return }
guard newValue <= _monetizationLevel else { print("Monetization level can only be increased by a server admin"); return }
_monetizationLevel = newValue
}
}
}
I was reluctant to use function-setters and really wanted to preserve the cleanliness of myAccount.monetizationLevel -= 7
. So if in your replies you can keep this structure in mind that'll help me a lot with my actual problem