Hacker Read top | best | new | newcomments | leaders | about | bookmarklet login
Clear defensive programming with Go using Verifier library (itnext.io) similar stories update story
70 points by kiyanwang | karma 33312 | avg karma 3.94 2018-08-09 01:00:55 | hide | past | favorite | 45 comments



view as:

This looks neat, but I would not call it clear. In cases where I need to check many conditions and return, I would simply use a conditional switch (and also use exact error values instead of generated ones):

  switch {
  case transfer == nil:
      return nil, ErrInvalidTransfer
  case person == nil:
      return nil, ErrInvalidPerson
  // ...
  }
This, to me, is both concise and clear.

I do the same.

I can see the appeal of wanting to create a package to manage error checking when you have dozens of conditions but this doesn't bring anything new to the table yet creates code that is arguably less maintainable and certainly less easily read.


You can actually return specific errors using verifier library. Check out documentation: https://godoc.org/github.com/storozhukBM/verifier#Verify.Wit...

I believe the point of the OP is, you don't need the verifier library at first place. This is one of these dependencies that offer very little for the cost of adding a new dependency to a project.

I'm not going to use OP's package, but I'll definitely copy the idea into a local helper. https://gist.github.com/icholy/fc41b8cfe74506cf93ec6b0fba1fc...

Small benefit to Verifier in this case: it can capture multiple errors. A switch can only capture one.

The more I read these things the more I believe that contract systems like clojure.spec and strong type systems/contracts like ADA were too much advanced for its time... golang is just above the stone age of programming languages... better than assembly barely above C,...

Some days I even believe that by promoting developer's mental laziness to not learn new paradigms/more advanced languages/technical systems we are just delaying the progress of humanity.

EDIT: advanced for its age -> advanced for its time


Already the world is complex enough, everybody wants to add their piece of grain.

I think Go was deliberately designed to be like C but less insane because people still use C!

Go is a practical language designed to improve the situation. You aren't going to convince someone who writes socat or curl or whatever in C to use Clojure or Ada instead. They might plausibly use Go though.


I get your point; GO is a low hanging fruit from the marketing point of view: it is a smart business move; because as we all know markets as well as societies have a strong inertia in regard of consumed products. But that doesn't change the technical opinion of the topic and the fact that we are still undeveloped using "C+" (aka go).

P.S. I am being a bit too hard on Go, but my whole point is that after seeing really complete solutions for this problem, everything else pales next to them and that we still encounter unnecessary problems.


I both agree and disagree with you. More sophisticated languages do have an obvious benefit to a great many problems and you're also right that often the problem is the developer's capabilities rather than the language. But you could also flip that argument and say a good developer could write good code in any language so why bother with anything more sophisticated.

However arguments about the developer aside, Go has other advantages that drew me to it. Such as

* cross-compiling requiring no additional effort aside setting an env var (It's been a why since I've written C++ but I seem to recall no end of bother there. Again a seasoned developer will argue it's easy but I certainly didn't find it that way)

* It compiles down to a portable executable (there are surprisingly few languages that do that)

* It's not overly verbose. Ok, this is only true if you're not having to work around generics or the "if err != nil" syntax. But for most problems I've found Go to be refreshingly direct.

* It enforces strict code layout guidelines so I can pick up anyone's code and can run with it easily enough.

* Compiler error messages are very easy to understand.

Sure there are other languages that cover some, maybe most of those points. But few cover all. Which is why I often describe Go as "a boring language that's pleasant for just getting shit done". Which is why I tend to favour Go these days despite having learned more than a dozen different languages over the years.

Needless to say a great many of people - both on and off HN - will disagree with me. But that's the beauty of personal opinion. Programming languages really are just the realm of preference.


> But you could also flip that argument and say a good developer could write good code in any language so why bother with anything more sophisticated.

Because both inexperienced and experienced developers will be prevented from doing the wrong thing more often when using a better language.


I do agree with your point but this is about more than just language safety. The point the GP was making was more about expressiveness of languages (or at least that was how I interpreted it). You can have safety even in arguably unsophisticated languages (eg Visual Basic pre-dotnet). However it's true that some languages use their expression as a method of enabling developers to write less buggy software.

I mostly agree with you in regard the positive aspects of its tooling, however there are plenty of libraries in Go which use the void pointer, casting is a common thing and the whole duck typing is based on interfaces which is at least uncomfortable. On top of that Go is slower than C++ but more productive because of the lack of distractions which is why I agree with the "a boring language that's pleasant for just getting shit done".

Of course Go has its problems. There's no such thing as a perfect language because everyone has their own requirements. That's why I repeatedly state that a choice of language usually just boils down to personal preference.

I mean if performance Vs C++ was a primary concern then you're limiting yourself to quite a small subset of languages, most of which either have a steep learning curve and/or sacrifice security. This doesnt make sense for the majority of developers out there since very few people really need the performance of C++. That's not to say performance doesn't matter but rather Go's performance is more than adequate for most of the applications it is applied to.

To further that point just look at how I've used Go. I've written two FUSE file systems and a UNIX $SHELL in Go and found the performance to be good enough even on my relatively modest dev machine. I've also written log file parsers that can handle gigabytes or records in seconds. I'm happy with that level of performance because for me developer productivety matters a lot more since I have 2 small kids so hobby projects get very little time allocated to them. This means I'd rather prototype code then optimise later rather than spend comparatively longer building a POC that ultimately might never get enough time to see the light of day.

However you will have your own set of requirements which Go might underperform on and those reasons are as important to you as my requirements are to me.

This is why arguing about how some languages are bad or which are the best is about as worthwhile as arguing which colour is the "best".


I don't believe the notions of 'marketing' or 'smart business move' played heavily into the design and development of Go.

I think the problem is that we assign too much importance to the last 5% of type safety. Go’s typing guarantees are somewhere between Haskell and Python, and it ends up being a very practical place on the spectrum. Further, have a great type system is just one of many competing factors; tooling has to be good, deployment has to be good, runtime has to be good, testing has to be good, libraries have to be good, code has to be readable, etc etc. Most languages drive hard at one of these; Go gets 90% of the way on all of them.

Go was designed by dudes who live the Unix philosophy. It was designed to be like C, be lean and efficient and most importantly to be scalable on multi CPU architectures with some beautifully simple primitives that enable channels/routines.

Go is awesome for what it is.


Go's primitives are not really ideal for CPU parallelism. To make CPU parallelism nice you want things like parallel iteration constructs, which Go can't have because of lack of generics. Furthermore, any CPU parallelism work wants the best optimizer around and SIMD support to make best use of those cycles—otherwise it'd be better to just write in C++.

Goroutines and channels are best used as concurrency primitives, not parallelism tools.


Just to +1 this: it aligns with very nearly all Go-core-team concurrency/parallelism stances I've seen. Go intends to support concurrency, not parallelism, and they go to great lengths to remind people that they're not the same thing.

Who said anything about parallelism? Concurrency still works beautifully on multicore CPUs. I'm writing jobs that have lots of tasks, not jobs that parallelyze a single task...

Concurrency works just as beautifully on single-core CPUs. :)

yup, it works great on both :)

I think the value of simplicity is often underestimated, especially by really smart people.

Go's bare-bones practical, opinionated simplicity can eliminate, right from the start, huge numbers of decision points developers might face any given day while using more complex and expressive languages, with plethoras of elegant abstractions to fit every situation. Thats really valuable IMHO, when any one of those decision points can lead to technical debt, or lost time and energy on internal (and external) technical debates.

Its an especially huge win when building software to perform necessarily complex work (like distributed container orchestration).


Go is simplistic, not simple. Bug densities are largely constant across languages, so the less expressive language will enable more bugs.

>Bug densities are largely constant across languages

I would like a citation on that.

I remember there being a study on how several groups of students would solve a problem in C, Go, and Rust. I can't find the link unfortunately, but IIRC, the conclusion was that C was the "buggiest" and Rust the "safest" with Go being in the middle, but the speed of development was much higher for Go and C.


Some programmers complain about Go' simplicity.

This reminds me of Oulipo, a group of writers and mathematicians seeking to create literary work using constrained writing techniques.

Go is the Oulipo of programming: its simplicity stimulates creative programming :-)

https://en.wikipedia.org/wiki/Oulipo


Patent Trolls encourage innovation!

The only benefit I can see in this, is that if each time I call GetError, it will give a useful list of conditions that were not met, so that in conditions where fast-fail is not necessary, but letting the caller know everything that might have been incorrect, useful information is available.

If that's not the case (and it's not clear from any of the documentation), it seems like this adds no real value, and in my opinion actually makes the error handling ugly and harder to maintain.

Also, use package-defined, exported error types that you can refer to by name, and errors become useful data again like Go intended.


You can actually return specific errors using verifier library. Check out documentation: https://godoc.org/github.com/storozhukBM/verifier#Verify.Wit...

Cool, I've been waiting for something like 'verifier'. A while ago I read 'Errors are values' [1] and always wanted to upgrade my error handling, but never really did. Verifier looks like a small library which can help me with that easily :-)

[1]: https://blog.golang.org/errors-are-values


So, I'm trading potentially error-prone if statements that are plainly visible in the code for a potentially buggy library whose code I'm not familiar with just for some syntactic sugar?

Sorry, no thanks.


Notice how OP had to add a check after verifying two pointers against nil. A junior developer could've easily skipped it, and, if tests only go through happy paths or are non-existent, deploy it somewhere where it would explode in runtime.

> A junior developer could've easily skipped it...

But the senior developer should catch it in a review before ...

> ...deploy it somewhere where it would explode in runtime.


I have yet to work anywhere with sufficient senior developers to cover every single chunk of code. And review misses stuff all the time regardless.

Computers are much better at consistently catching errors than developers

The whole library has 199 lines (with comments), and the code is very clear (at least, for me). You can give a try to read it.

If only there were some features in some computer languages or type systems that allowed to eliminate 99% of these kind of errors AT COMPILE TIME, making these kind of libraries redundant... but Go is so simple "you don't need all that" because sophisticated type systems "are just too hard", just do it all at runtime instead...

If you're talking about nil, then yes, I would really like Go to never have it.

But the other validations are more about contracts. Preconditions, to be precise. Yes, I know that a sufficiently advanced type system (dependent types?) could encode those in the function signature, but I am not entirely sure if the technology is already there.


Unfortunately, there are lots of more important and even competing factors besides sheer type correctness. Performance, tooling, ecosystem, documentation, learning curve, readability, performance, etc all matter and Go performs better in aggregate than any of the sophisticated type system languages around today, even if it isn’t the best in any one category.

I’d pay you a lot of money to develop a readable functional language that interoperates seamlessly with Go and shares its runtime.


Very cool! Error handling is a shortcoming of Go. If you enjoy coding in Go, as I do, then you'll appreciate this pattern (errors are values).

Yes, I wish there are more powerful options similar to scala's for comprehension:

  for {
    x <- xf
    y <- Try(fooy())
  } yield x + y
But you don't get that without other trade-offs. If you're using Go, you'll appreciate this pattern.

> I think we all can agree that these lines are repetitive and even error-prone to some degree.

I don't agree. I'll take if-statements over a validation library 99% of the time.

This is how we validation a message coming into our REST+JSON API: https://github.com/ohsu-comp-bio/funnel/blob/master/tes/vali...


Legal | privacy