10 ms·
What's wrong with this? class Result<T> { bool IsSuccess {get; set;} string Message {get; set;} T Data {get; set;} } On many occasions, I li
by gokhan 6y ago
What's wrong with this?
class Result<T>
{
bool IsSuccess {get; set;}
string Message {get; set;}
T Data {get; set;}
}
On many occasions, I like using result types for defining a standard response for calls. It's typed and success / fail can be handled as a cross-cutting concern.
- londons_explore 6y agoI feel like such a class should either be part of the language, and part of language idioms etc, or it shouldn't be used.
- tomjen3 6y agoCan you articulate why? it seems to me that 'feel' should not be part of the discussion.
- deleted 6y ago[deleted]
- juststeve 6y agoNot GP but: It may frustrate coworkers who need to edit the code. It adds another dependency into your workflow.
- lenish 6y agoNot GP, but I've sometimes found libraries implementing similar concepts differently causing issues. E.g. libraryA.Result struct { Err error Data SomeDataType } libraryB.Result struct { err string Data SomeDataType } func (r libraryB.Result) Error() string { return r.err } Now you have two different implementations of the same fundamental idea, but they each require different handling. In Go, where many things simply return an error type in addition to whatever value(s), you would now have three different approaches to error handling to deal with as opposed to just whatever the language specified as the best practice.
- mrkeen 6y agoThis is what interfaces are for. Let your caller bring their own error type and instantiate your library code over that.
- lenish 6y agoI'm not sure why you'd use a class like this in Go when you have multiple returns and an error interface that already handles this exact use case.
- erik_seaberg 6y agoIt's a lot cleaner to pass a Result<T> through a channel or a slice than to create two channels or slices and confirm everyone's following the same convention when using them.
- lenish 6y agoI concede that there are probably scenarios where this design makes sense within that context. I typically find that either I care about a single error and terminating the computation, or I don't care about errors at all. In the former case, the primitives in the sync package (or just an error channel which we send to once and close) are adequate. The latter case presents no issues, of course. At $work we definitely have examples where we care about preserving errors, and if that tool were implemented in Go a solution like a Result struct containing an error instance and a data type instance could make sense.
- apta 6y agoBecause multiple return values for handling errors is a strictly inferior and error prone way for dealing with the matter.
- californical 6y agoIt's interesting that you say this, because I've had the opposite experience. I wouldn't say it's strictly inferior, because there are definitely upsides. If it was strictly inferior, why would a modern language be designed that way -- there must be some debate right? I love multiple returns/errors. I find that I never mistakenly forget to handle an error when the program won't compile because I forgot about the second return value. I don't use go at work though, I use a language with lots of throw'ing exceptions, and I regularly miss handling exceptions that are hidden in dependencies. This isn't the end of the world in our case, but I prefer to be more explicit.
- AaronFriel 6y agoThat's a generic container of 0 or 1 elements ;) It's also incredibly unsafe and why generics aren't enough. C++, Java, and so on have had generics for ages and with types like the one above, null pointer exceptions are incredibly common. Nothing prevents the user from attempting to retrieve the data without first retrieving the success status. On the other hand, this improves on it dramatically: enum Result<T, E> { Success(T), Failure(E) }
- jcelerier 6y ago> It's also incredibly unsafe and why generics aren't enough. C++, Java, and so on have had generics for ages and with types like the one above, null pointer exceptions are incredibly common. uh, you'd never get a null-pointer exception in C++ given the type that OP mentioned. Value types in C++ cannot be null (and most things are value types by a large margin).
- nine_k 6y agoWell, T can be a pointer / reference here.
- jcelerier 6y agoThat wouldn't change anything to Result<T>'s implicit safety properties. "safe + unsafe == unsafe" - to have a meaningful discussion we should focus on the safe part, else it's always possible to bring up the argument of "but you can do ((char*)&whatever)[123] = 0x66;"
- sedatk 6y agoYou can also constrain a generic type only to value types in C#: class Result<T> where T: struct { ... } In that case it can't be null with C# either.
- AaronFriel 6y agoThen you can't construct it unless it's successful, no? A Result<T> that can only contain successful values doesn't seem very useful
- jasonhansel 6y agoResult types are much better than multiple return values. But now the entire Go ecosystem has to migrate, if we want those benefits (and we want consistent behavior across APIs). It'd be like the Node.js move to promises, only worse...
- dragonwriter 6y agoWell, for one thing, it doesn't actually work like a proper Optional<T> or Either<T, string> type. It works more like Either<(T, string),(T, string)>, which might have some uses, but isn't typically a thing someone would often reach for if they had a type system that readily supported the other two options.
- lmm 6y agoIt has a bunch of invalid states (message and data both set, neither set, message set but IsSuccess is true, etc.). So you have to either check it every time, or you'll get inconsistent behaviour miles away from where the actual problem is. It's like null but even more so.
- alkonaut 6y ago> What's wrong with this? That it's mutable, at the very least!