In cases like this, I tend to ask myself why I need the error code and error field, what is the desire of the user of the software that makes these things necessary? Usually, a user (or a programmer) does not want erroneous, faulty objects - too much mess downstream, and it is far too easy to use the object without checking the error code first.
I assume that this code would be used in a typical scenario of constructing an object from data from an external source (a file, a database, a website, user input, or whatever). Either this process works, or it fails. If the process works, you get a correct object. Or the process fails, and you get a hopefully informative error message. In the programming world, the function would not return a Thing, but a Result<Thing, Error> or an Either<Thing, Error>, and this object you can interrogate later to see if it contains a valid object or an error value.
For this purpose, Kotlin has a Result structure, but you can also homebrew something if you donât want to require ErrorCause to become a Throwable:
enum class ErrorCause(val id: Short) {
Electrical(1),
Mechanical(2),
Chemical(3);
}
class Thing(val phase: Int)
// derived from https://medium.com/@KaneCheshire/recreating-swifts-result-type-in-kotlin-f0a065fa6af1
sealed class Result<out S, out F>
data class Success<out S, out F>(val value: S) : Result<S, F>()
data class Failure<out S, out F>(val failure: F) : Result<S, F>()
fun result(achievedPhase: Int): Result<Thing, ErrorCause> {
if (achievedPhase <= 1) return Failure(ErrorCause.Electrical)
return when (achievedPhase) {
2 -> Failure(ErrorCause.Mechanical)
3 -> Failure(ErrorCause.Chemical)
else -> Success(Thing(achievedPhase))
}
}
fun processThingResult(cResult: Result<Thing, ErrorCause>) {
println(
when (cResult) {
is Success -> "Thing in phase ${cResult.value.phase}"
is Failure -> "Error because ${cResult.failure}"
}
)
}
fun main() {
val coord1 = result(1)
processThingResult(coord1)
val coord2 = result(2)
processThingResult(coord2)
val coord3 = result(3)
processThingResult(coord3)
val coord4 = result(4)
processThingResult(coord4)
}
Of course, the above code is rather elaborate (though a better Kotlin developer could possibly simplify it), but it would avoid the pain point of accidentally using a faulty object because someone forgot to check for the presence of an error code. A problem that becomes more and more likely if codebases grow and get older.
If in your case the real problem is that you need an object with error code (possibly to be cleaned based on the error code - still tricky, though, since someone may forget to clean the object, one reason why you generally want functions to return either a good object or nothing/an error), then the âResultâ method may be inappropriate, pdvriezeâs Status object would then be most âerror-proofâ, in my opinion.
Still, I think that the âbest styleâ depends very much on the result you want to achieve, the usage of the ErrorCode and Thing âdownstream.â