HTTPStat https://github.com/davecheney/httpstat now uses the HTTP Tracing library to provide some pretty neat graphs, letting you visualise the HTTP Tracing output.
I haven't explicitly needed HTTP tracing yet, but am glad to see it's added to the tool set specifically for debugging DNS which can be tough.
On another note, it's pretty f*cking lame that the only discussion in this post is a tangent about error checking and not even remotely related to the content.
Is it bad that the first thought that came to mind upon reading the example code on the page is "Wow, this is ugly"?
...And I don't mean the "if err" sprinkling that has a whole subthread, I mean the actual layout of the code. It's not hard to understand just.. ugly. Aesthetically. And it's hard for me to put my finger on why.. maybe it's the symbols everywhere.
I was about ready to make a remark about how I hope that's not idiomatic go code, but no, this is from the official site.
I've never wanted to like a language that makes me not want to like it as much as Go, and that's the god's honest truth.
Give it a chance. I feel the opposite in many cases today because of two reasons: static typing and gofmt. In Python code I frequently find myself playing "what the hell are these objects" or "what the hell does this overloaded operator do" and in lower level code (esp. C++) it looks like a mangled jaggy mess. Linux kernel code is one of the few exceptions due to the strict style guidelines.
One of these things is a compiler-enforced piece of tooling; I know my errors are handled because I don't have any alternatives to handling them. The other is a code standard that people regularly ignore. If you want to assert equivalence between these two options, I suggest you show your work.
If you have to use a pattern, how is the compiler enforcing it? Suppose you don't use the pattern? Since you spoke only in the abstract without so much as naming your language, I'd suggest your work exhibition should come first.
In languages like Java, with checked exceptions, you are required by the compiler to have a try-catch block that handles the checked exception at some point, or you have to add "throws FooException" to your function, which passes over the error-handling responsibility to callers of that function.
Stack unwinding (with 'finally' clean-up blocks) results in higher brevity and relatively cleaner code than having a err != null check after every function call. Not to say, checking err != null is optional, could be accidentally forgotten, and cause unpredictable runtime behavior.
This is true, but I was referring to something like Scala's Try[T] (which wraps Java's exceptions and returns either the value T or the exception caught, and allows you to chain Try calls together through `map()`, `recover()` from them as appropriate, etc.) or Rust's Result<V, E>.
I agree with this except for two cases, which I would LOVE counter arguments for.
Case 1: checked exceptions. This is what Java used heavily in the early days, a bit less today, which basically requires you to deal with known exception scenarios. It was good because it wouldn't compile unless you explicitly said "I'm not dealing with this" or you dealt with it. But its bad because it requires your methods to throw some list of exceptions all the way up the call stack. I think there are some other problems that Experts(tm) around here can probably point out better than I. Still, checked exceptions seem to be a valid pattern in some contexts, and this feels like the same pattern Go follows. That said, I dislike Go's error handling for the same reason: my code is really cluttered and unclear this way.
Case 2: when you catch everything, you may not throw the right thing to your caller(s). Even worse, you may swallow an exception unintentionally and leave callers hanging in the balance. It's not that it's impossible or particularly difficult to avoid making these mistakes by wrapping everything in a Try[T], just that it's easy to make the mistake of catching more than you intended.
Like everything else in language design, it's a trade-off. The negative of the try/catch paradigm is that it's effectively a goto statement, and is often abused as such. Though I haven't worked professionally with a statically typed language with try/catch, I've spent many afternoons trying to find out exactly where an exception is caught (often six or seven frames down) in python frameworks like django, flask, and aiohttp. When exceptions are passed as values it's much more explicit.
Are you referring to the instances where error is a return value, but an error result under the known inputs would be impossible, so it would be pointless to check it? That's not exactly the same as removed for brevity.
Does the Go standard library document that on any function like you are expecting?
The code for both functions make it pretty clear that an error will only be returned if the input is invalid, which is not something that will occur with the known constants being fed into them.
That isn't quite the same thing. That says that an error is returned only because it is required to conform to the io.Writer interface. Without that requirement, it wouldn't return error in the first place. It tells the reader that they should not be confused as to why writing to a buffer might return an error, when such an operation should fundamentally not return an error.
These other functions in question have very good reasons to return an error: Invalid input.
> Looking at the implementation is no guarantee that the implementation won't be changed in future.
The Go compatibility promise says that the behaviour won't change, except under exceptional circumstances – like a security flaw that cannot be fixed using the original behaviour. It is highly unlikely that is an issue for those particular functions.
But, if you still think it is important, what do you plan to do with an error that might just randomly appear in the future anyway?
> But, if you still think it is important, what do you plan to do with an error that might just randomly appear in the future anyway?
Terminate processing and pass the error back up the call chain. This is basically what exceptions do, and basically what 'if err != nil { return err }' does.
One could also pause processing, interrogate the call chain for what to do about the error condition and the proceed as directed; this is what conditions & restarts do. IMHO it's a better solution, but it requires some syntax to be readable.
> Terminate processing and pass the error back up the call chain. This is basically what exceptions do, and basically what 'if err != nil { return err }' does.
The function in question that could return an error is being called in the main function. Who are you going to pass it to, exactly? error is a not a return value on main. If you knew what the error could be, you might find a suitable workaround under those conditions. But since the error does not even exist for you to handle, about all you can do is terminate the application. Which is exactly what will happen in this circumstance anyway, even if you don't check the error result.
I'm still not clear what you are really going to meaningfully do with the error information here even if you did magically start getting errors against the compatibility promise? If you are going to handle errors, you actually have to know what you want to do with them. The following adds nothing to your code.
if err != nil {
// I didn't expect that. Oh well.
}
It's not like anyone is suggesting you should avoid checking errors in all circumstances. We're talking about very specific cases where all of the conditions are known. If GET stops being an HTTP request method, or http://example.com/ is no longer a valid URL, you've got way bigger problems than not gracefully handling the world flipping upside down.
Two. The URL is a constant that is guaranteed not to return an error. It is not brevity.
Anyways, the point is I don't understand the point of err != nil bashing here, given there are two programs in the article that only contain one error check each.
As someone who writes a lot of Go I have mixed feelings about it. The problem is that there are implicit errors regardless and the explicitness in err != nil tricks beginners into thinking that they've covered all edge cases, they haven't. Recover() is probably the only way to handle internal panics and even then it only works if you know to that internal call might call panic().
That said I can't think of an alternative other then to reduce the number of panic()s hidden in various libraries.
Sorry, I guess I'm not really making one. If anything I'm commenting that (IMO) explicit error handling isn't as explicit as people thing it is. There are internal errors induced by panic() that are really inconvenient.
Panics are supposed to be launched on an "end all work on this; code path is FUBAR". On a web server that usually means dropping the connection, logging everything and firing an automated e-mail to the dev team to notify there's a bug in the code that needs to be fixed.
All other error conditions are expected and handled. That's what Go's error handling philosophy is all about.
Well, the reason why I'm using a high-level programming language is there are a bunch of mechanical things that the computer will do better and more reliably than me. Otherwise I'd just write assembly.
What sorts of things fall into this category, and what tradeoffs you're willing to make to have the computer do things for you, is a matter of application (and taste), and so we reasonably have many different programming languages. But it's entirely defensible to want, in the abstract, your language to do repetitive and important work for you. That's what computers are good at.
There's not one reliable, or a set of reliable, way to handle errors. A high level language could handle it for you reliably in the sense of throwing exceptions and hoping you'll catch it. That'd work, you'd get Java software that throws exceptions all the time during normal operations.
Errors are valid values, handling them is as normal as handling other values you get from functions.
let foo = try!(something_that_might_error())
// or soon...
let foo = something_that_might_error()?
The try!() macro or the ? operator unwrap a Result<T, E> value, which is basically "either the return value or an error value". If it's a return value (T), the macro/operator just gets the value out of it - but if it's an error (E), it will convert the error into your own function's Result<T, E> return type using the From trait, and return that from your own function immediately.
The practical result? When I'm parsing input or doing other things that are heavy on errors, I can split it out into another function, wrap every call that might fail in try!(), and only have to actually handle the error once. This allows me to see my code clearer - I can see the success case very clearly, but I'm still forced to decide whether to handle or return errors by the compiler, because there's no other way to get the T out of a Result<T, E>.
There's a few other languages which implement this, though they lean towards the functional side a lot more than Rust does.
Experience: I'm writing a small internal certificate authority in Go because it has x.509 and signing support in the standard library. I'm no safer by having roughly 3/5 of my lines be if err != nil { return err; }.
Honestly... you could do it as a primitive type if you wanted. The only "special" bits needed are a piece of syntax which checks if it's an error, and if so returns it, else evaluates to the private value; and a way of unwrapping the actual error value when you want to handle it.
Go already has plenty of complicated language primitives with complex behaviour.
I think people are misinterpreting support for try catch family in some high level languages as lazy approaches.
But truth is it is the programmers who are lazy, we should not blame language for providing a feature which some programmer abuse.
I have never seen a Java programmer profiling their code for Memory or CPU at work and I am here running my shitty C code under Valgrind and a profiler all the time.
I guess, it is programmers and not the language.
Go's error != nil approach though rudimentary actually forces lazy programmers to complain, since they cannot abuse it.