|
Of course you can. Sad path first means, after you write your data types and interface signatures, writing the first stub implementations as func (t *Thing) Process(id int) (string, error) {
return "", fmt.Errorf("not implemented")
}
and then filling them in gradually like func (t *Thing) Process(id int) (string, error) {
dat, err := t.store.Read(id)
if err != nil {
return "", fmt.Errorf("error reading ID: %w", err)
}
cert, err := dat.ExtractCertificate()
if err != nil {
return "", fmt.Errorf("error extracting certificate: %w", err)
}
return cert.Name(), nil
}
and explicitly not func (t *Thing) Process(id int) (string, error) {
dat, _ := t.store.Read(id) // TODO: error handling
cert, _ := dat.ExtractCertificate() // TODO: error handling
return cert.Name(), nil
}
Writing code this way, explicit error handling upfront, is fundamental to reliability (for a large class of applications). |
Even worse, with this style of programming, someone up the stack who would actually want to handle these errors has no mechanism to do, since you're returning the same type from both error cases. If they wanted to handle the certificate error but not the read error, they would have to parse the error message string, which is brittle. But if you did want to add appropriate context, your function would bloat even more. Not to mention that the standard library doesn't really help, since it generally doesn't define any specific error types to set up some patterns for this. Even in your example, from the start you assumed that your function can either succeed or fail in a generic way, you didn't think that the signature may want to encode multiple different error types, which is what GP was talking about when saying you can't usually think about the sad case before the happy case. Sure, if the extent to which you want to define sad case first is 'sad case can happen', you can.
Go's error handling strategy is its weakest aspect, and it is annoying to hear advocates pretend that Go is doing it right, when 90% of Go code is doing the same thing as 90% of exception-based code, just manually and with less helpful context for either logging or for the 10% of code which actually wants to handle errors.