3rd Degree Black Belts in Go, what are the most common mistakes and stylistic faux-pas you see?

agolangf · · 906 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I come from a Python background, and I&#39;ve been using Go in production for a couple of months now. I think I have a reasonably good handle on the language and I&#39;m finally at the point where I can crank out small projects without any inertia. I tend to be meticulous, so I&#39;m always checking for errors and always running <code>gofmt</code>, but I&#39;m wondering if there are common patterns and idioms that tend to go unnoticed by neophytes?</p> <p>Could you kindly share the most common mistakes and stylistic atrocities you come across?</p> <p>Again, I&#39;m looking for common errors, not &#34;one time I saw a guy implement a database by bitshifting strings&#34;-type rants.</p> <p>Thanks!</p> <p><strong>Edit:</strong> It seems my question may have been a bit unclear. I&#39;m hoping for Go-specific issues, rather than general programming mistakes. For instance, error-handling patterns would fit the intent of this post quite nicely.</p> <hr/>**评论:**<br/><br/>robertmeta: <pre><ol> <li><p>Concrete types where interfaces should be making it harder to test / stub. </p></li> <li><p>(related to 1) Not doing tests from the start, tests impact program design -- hard to test code is probably not your best code. </p></li> <li><p>Lots of embedding / confusing embedding with inheritance.</p></li> <li><p>Trying to &#34;tag&#34; goroutines for various reasons, logging is the most common (and least atrocious), but I have seen far worse. </p></li> <li><p>Channel-insanity is a condition that seems to be suffered by gophers with no experience in concurrency or threading prior to Go. They see channels and concurrency as the answer to all problems. They complicate up the simplest programs gaining no benefits and often huge downsides. </p></li> </ol></pre>ericanderton: <pre><blockquote> <p>Channel-insanity</p> </blockquote> <p>This is usually multiplied by the fact that &#34;good&#34; and &#34;idomatic&#34; channel-based code is extremely copy-happy. Reliable? Yes. Correct? Absolutely. Runs like molasses in January? You better believe it.</p></pre>robertmeta: <pre><p>Not sure I agree, I haven&#39;t found channels more copy happy than other go code, and in almost all cases we implemented them for performance gains. </p></pre>davecheney: <pre><p>copies are cheap, garbage collection is expensive; this is true even for very large values of copy.</p></pre>ihsw: <pre><p>Channel insanity was a hard one for me to get past -- I&#39;ve had to rewrite some hairy code that became <em>completely</em> unmanageable.</p> <p>Thank heavens it was a personal project with no deadline whatsoever, I&#39;d hate to have such madness running in production.</p></pre>AnimalMachine: <pre><blockquote> <p>Channel-insanity is a condition that seems to be suffered by gophers with no experience in concurrency or threading prior to Go. They see channels and concurrency as the answer to all problems. They complicate up the simplest programs gaining no benefits and often huge downsides.</p> </blockquote> <p>Are there some good intermediate/advanced channel tutorials? I got bit by this one myself in an earlier project ...</p></pre>maester_chief: <pre><p>What&#39;s a good way to log per goroutine?</p></pre>AYBABTME: <pre><p>If you want to track the context of some work being done, use golang.org/x/net/context . In that context you&#39;d have some identifiable bit, like a UUID for instance. You can have the context carry around the logger for the work in progress. Works really well with structured loggers.</p> <p>So the answer is basically, don&#39;t care about the goroutine, care about the context of the work. And use <code>net/context.Context</code> for that.</p></pre>dmikalova: <pre><p>Check out the <a href="http://golang-challenge.com/evaluator-feedback/">feedback</a> from the recent Go challenge, and Go&#39;s list of common <a href="https://github.com/golang/go/wiki/CodeReviewComments">code review</a> comments.</p></pre>omginternets: <pre><p>This is a veritable goldmine! Thank you!</p></pre>dmikalova: <pre><p>Yeah, also look at effective Go if you haven&#39;t already and try out the next Go challenge for this month!</p></pre>davecheney: <pre><p>Too many packages, too many files.</p></pre>ericanderton: <pre><p>The worst I&#39;ve seen can be lumped into one of two categories:</p> <ul> <li>Trying to code with a formal <em>OOP</em> approach</li> <li>Trying to code with a formal <em>functional</em> approach</li> </ul> <p>These approaches map very poorly onto Go at a certain scale and sophistication. Formal OOP requires lots of data hiding, and accessor/mutator functions (think Java). Formal functional programming in Go requires something like generics or template support, unless you&#39;re okay with casting everything to/from interface{}.</p> <p>Go deliberately cuts its own path by resembling &#34;a better C&#34; than a better Python, C++, Java, Haskell, etc. To that end, it works best with a C-coder&#39;s mindset of structures for data, and functions to manipulate that data. The addition of interfaces in the language makes some common casts and other common kinds of encapsulation easy to do. </p> <p>Now, one mistake I found myself making was trying too hard to accomplish <em>nested diagnostic contexts</em> for stack-based logging. I had the realization that, since goroutines can be created anytime, anywhere, these logging contexts could become huge under the right condictions. Nevermind that it&#39;s not possible to pull off without goroutine-local storage, or passing logggers around everywhere. I learned that it&#39;s better to be more simple and direct when logging, and just describe the context for the current function call; don&#39;t worry about what context you&#39;re in since you could just be the root of a goroutine&#39;s stack.</p> <p>Edit: failed to read your Edit, OP. Sorry about that. If I can recall anything specific, I&#39;ll post it here.</p></pre>omginternets: <pre><blockquote> <p>&#34;a better C&#34;</p> </blockquote> <p>That actually makes a lot of sense despite the fact that the only C I&#39;ve ever written has been during an undergrad class. I&#39;ll be sure to bear this in mind, especially since I felt myself blush when you mentioned OOP and functional approaches...</p> <blockquote> <p>Now, one mistake I found myself making was trying too hard to accomplish nested diagnostic contexts for stack-based logging.</p> </blockquote> <p>I&#39;m having trouble understanding exactly what you&#39;re talking about. Could you please elaborate?</p></pre>ericanderton: <pre><blockquote> <p>I&#39;m having trouble understanding exactly what you&#39;re talking about. Could you please elaborate?</p> </blockquote> <p>I&#39;ll admit it&#39;s not a commonly used approach. Let me explain.</p> <p>Generally speaking, the industry does the wrong thing when generating log output for consumption by centralized logging aggregators like Splunk. Our typical approach is to just format a string with %s interpolations so a person can read a nicely formatted sentence. What you really want your web server to do is generate key=value sequences on a single line per log event so map/reduce can chew on the data and generate useful information and alerts:</p> <pre><code>priority=&#34;DEBUG&#34; message=&#34;user logged in&#34; username=&#34;eanderton&#34; last_login=&#34;1 Apr 2015&#34; priority=&#34;ERROR&#34; message=&#34;something bad happened&#34; </code></pre> <p>In this approach, for a <em>typical</em> multi-threaded application, it helps to provide additional context so you can distinguish one thread&#39;s logging from another. Rather than pass this contextual information down the stack to every possible call in the program, you use a <em>Nested Diagnostic Context</em> to build that into the thread&#39;s local storage, which is implicitly picked up on every call to the logger. Now we have really useful log lines, each one of which can be used <em>in isolation</em> to diagnose a potential error:</p> <pre><code>priority=&#34;DEBUG&#34; tid=12345 message=&#34;user logged in&#34; username=&#34;eanderton&#34; last_login=&#34;1 Apr 2015&#34; priority=&#34;ERROR&#34; tid=12345 message=&#34;something bad happened&#34; username=&#34;omginternets&#34; last_login=&#34;1 Apr 2015&#34; session_id=7890 </code></pre> <p>Now that error stands out as belonging to completely different user! Before we&#39;d have to guess using timestamps, or to just try and re-create the error on a test box with nobody else logged in.</p> <p>What i learned is that the elegance of this approach relies on thread local storage, unless you explicitly pass loggers around all over the place. Go has no such animal for goroutines, and with good reason: an application can have many more times goroutines than any traditional application would have threads. Since it&#39;s possible for goroutines to yield goroutines, forever, you could wind up in a real mess if you tried to manage contexts in this way. At the same time, if you disallowed a goroutine from inheriting a parent&#39;s logging context, you severely limit the effectiveness of the approach. You simply can&#39;t get there from here without some bad consequences.</p> <p>More info: <a href="https://lizdouglass.wordpress.com/2009/12/09/nested-diagnostic-contexts/">https://lizdouglass.wordpress.com/2009/12/09/nested-diagnostic-contexts/</a></p> <p>TL;DR: NDCs provide implicit currying for log data. They rely on TLS which isn&#39;t available in Go for non-obvious reasons.</p></pre>omginternets: <pre><p>Thanks, this seems like a very interesting read (and incidentally, a very relevant one!)</p> <p>Thanks for the two very useful posts!</p></pre>jerf: <pre><p>The Go solution to this is to manually create new contexts as you need them.</p> <p>Incidentally, this <em>can</em> be factored out:</p> <pre><code>func spawn_with_NDC(f func(Context), name string) { newContext = GetNewContext(name) go f(newContext) } </code></pre> <p>which can of course be elaborated on to properly retrieve the context. However, note this technique can compose poorly if you&#39;ve got something else that also wants to wrap the <code>go</code>. This can be fixed with more work. I suppose my main point here is to note the lack of <code>interface{}</code>... you can always do something like</p> <pre><code>spawn_with_NDC(func (log NDC) { complexFunction(log, blah1, blah2, etc...) }) </code></pre></pre>dfndoe: <pre><blockquote> <p>Formal functional programming in Go requires something like generics or template support, unless you&#39;re okay with casting everything to/from interface{}.</p> </blockquote> <p>I don&#39;t understand why this would be true. Doesn&#39;t strict functional program only imply that functions are devoid of side effects and that state is passed as (generally immutable) objects? The lack of generics certainly makes DRY hard to follow, but it doesn&#39;t seem antithetical to functional programming. Am I missing something here?</p></pre>ericanderton: <pre><p>It&#39;s not impossible to do, it just yields sub-par code when compared to more functional languages. The presence of casts everywhere to support DRY, is generally seen as an impediment to this kind of coding. It&#39;s also harder to read. At the same time, you can&#39;t overload functions by argument type like you can in C++, which also makes things kind of unpleasant.</p></pre>dfndoe: <pre><p>Not trying to pick an argument, but why would you be casting everywhere and using bare interfaces? Functional doesn&#39;t imply that the functions are generic, sure you repeat yourself when you write the same function for two different signatures to account type difference, but how much code do you write expecting that you won&#39;t know the type going into a given function? That seems like an anti-pattern, yah?</p> <p>I too find that lack of operator overriding to be bothersome, but I don&#39;t see what that has to do with functional programming either.</p></pre>ericanderton: <pre><p>Oh hey, it&#39;s cool. My understanding of &#39;functional&#39; programming may be a bit off base here, so let&#39;s pull this apart.</p> <blockquote> <p>sure you repeat yourself when you write the same function for two different signatures to account type difference, but how much code do you write expecting that you won&#39;t know the type going into a given function? That seems like an anti-pattern, yah?</p> </blockquote> <p>So, the &#34;bad&#34; pattern I tend to see, is stuff like this:</p> <p><a href="https://github.com/nyrkovalex/funky" rel="nofollow">https://github.com/nyrkovalex/funky</a></p> <p>So not to beat up on this guy&#39;s library (my apologies if you&#39;re in this thread), but this is sort of where I was going with this. The library is no doubt useful, but the function signatures require use of <code>interface{}</code> to achieve DRY. It&#39;s this bad pattern where you are left wanting for a lighter-weight syntax for what amounts to duck typing, like you have in LISP or Python. It&#39;s not impossible to achieve generic functional composition this way, but it requires embracing <code>interface{}</code> for a lot of situations.</p> <p><a href="http://stackoverflow.com/a/17368798" rel="nofollow">http://stackoverflow.com/a/17368798</a></p> <p>In contrast, this SO answer builds around a single type, which is limited in its usefulness, rather than just requiring <code>interface{}</code> heavy closure signatures. This pattern isn&#39;t as bad, but it is still awful if you&#39;re building a general purpose library that needs to support more than one type.</p> <p>My opinion is that the coder winds up with better Go code if they steer towards something like <a href="http://golang.org/pkg/sort/" rel="nofollow">http://golang.org/pkg/sort/</a> . Here, the targeted <code>Interface</code> interface is used, and there are some easy-to-reach concessions for integer and float arrays since those come up frequently. It&#39;s a hybrid approach. The consequence is that the user has to write more code to mesh a custom struct with the generic sort facilities, but the library itself winds up staying generic.</p></pre>dfndoe: <pre><p>I understand the distinction you are making, but I think you are still confusing &#34;functional&#34; with &#34;generic&#34;. I thought maybe I was missing something about your argument, but I think it is a semantics thing. I don&#39;t think you are talking about a problem that makes functional programming hard I think you are talking about general/generic solutions hard to do without choosing between bare interfaces and DRY or type specific signatures and a lot of repeated code. Which is a fair observation and one which I was quite frustrated with coming from python.</p> <p>But, ultimately I think that most people aren&#39;t writing general code meant to be used with arbitrary types. Especially when you&#39;re using a strongly typed language it would be much more appropriate to expect that each signature is explicit and at runtime is immune to problems from type errors. IMHO it&#39;s a trade off on ease of code vs ease of debugging.</p> <p>Go isn&#39;t python isn&#39;t C isn&#39;t java.... etc.</p></pre>jerf: <pre><blockquote> <p>Functional doesn&#39;t imply that the functions are generic,</p> </blockquote> <p>Depends on your definition. If your idea of functional programming is the older idea of &#34;first-class functions&#34;, sure, Go does that, but that is becoming a useless distinction since pretty much everything does.</p> <p>If you mean map/filter/reduce, then your definition of functional <em>does</em> imply some form of generic, as the signature of map is something like <code>(a -&gt; b) -&gt; [a] -&gt; [b]</code>. I use Haskell since this is inexpressible in Go type signatures. Functional programming is theoretically possible with you manually writing the &#34;map&#34; definition every time, but that&#39;s not much fun.</p></pre>dfndoe: <pre><p>I think the key features in functional programming are:</p> <ol> <li>Functions are first class objects</li> <li>Functions are devoid of side effects</li> <li>State is passed in immutable objects or alternately there is no global state</li> </ol> <p>The map/filter/reduce stuff is very much part of modern functional programming, but isn&#39;t what defines it. Hence my comment about this being a semantics issue; because ultimate whether it is a thing or not a thing depends on how you define &#34;thing&#34;.</p> <p>So, no I don&#39;t mean &#34;map/filter/reduce&#34; when I say functional. However I can see what the frustration would be in trying to implement general solutions for these operations.</p> <p>FWIW I think the &#34;functional programming&#34; article on wikipedia closely resembles the definition I was operating from.</p></pre>downvotes_puffins: <pre><blockquote> <p>how much code do you write expecting that you won&#39;t know the type going into a given function? That seems like an anti-pattern, yah?</p> </blockquote> <p>Obviously, you&#39;ve never written C++, which has a powerful, expressive template system that allows you to do exactly that.</p></pre>Jhsto: <pre><p>I think <a href="https://github.com/golang/lint">golint</a> might be something you are looking for. Additionally you may find <a href="https://godoc.org/golang.org/x/tools/cmd/vet">go vet</a> useful. These tools get you quite far.</p></pre>rossij: <pre><p>Take your Lint to the next level with Metalinter :) <a href="https://github.com/alecthomas/gometalinter">https://github.com/alecthomas/gometalinter</a> Great tool. </p></pre>Jhsto: <pre><p>Wow, thanks. I was not aware of this!</p></pre>omginternets: <pre><p>Are you using the sublimetext plugin?</p> <p>I&#39;ve installed gometalinter via <code>go get</code>, checked that it&#39;s discoverable via <code>which gometalinter</code>, but I can&#39;t seem to install <code>SublimeLinter-contrib-gometalinter</code> from ST3&#39;s package manager...</p></pre>omginternets: <pre><p>Yes, golint is fantastic! I&#39;m using the sublime text plugin and it&#39;s been a real life-saver so far.</p> <p>I&#39;ll be adding go vet to my toolchain as well. Thank you!</p></pre>pinpinbo: <pre><p>The part that tripped me often is: Writing tests too late in the game.</p> <p>Goroutines and channels can get complicated fast. Writing tests along side the primary code can help break your functions into smaller parts.</p></pre>ericanderton: <pre><p>Here&#39;s one: Using panic/recover in the main flow of a program.</p> <p>This should be self-explanatory. :)</p></pre>exch: <pre><p>This is not exactly specific to Go, but I find it comes up more in my time with Go. Mostly because it&#39;s so easy to quickly mash up small packages for all sorts of things:</p> <p>Assuming that everything on github/google code/bitbucket is production ready and fit to solve problem X. I used to get rather trigger happy with <code>go get</code> when I needed some solution to some problem. Which ended with me importing ludicrous amounts of unvetted, third party code, without really knowing what it really does.</p> <p>Aside from the often questionable quality, 99% of it is not really being used by my code. So even if you really do need package X, it often pays to just extract the paths you do use and toss everything else out. At the very least, it forces you to actually look at what it does, making it more likely you will spot bad apples.</p> <p>These days, I&#39;ve forced myself to copy the necessary parts into my own code base (with proper attribution), where ever possible. Keeps everything small, clean and predictable.</p></pre>zond: <pre><p>Not trying out the most simple and straight forward solution before creating something theoretical and computer sciency.</p></pre>omginternets: <pre><p>This is undoubtedly true, but not Go-specific. I might not have been clear in my question, but I was hoping for guidance relating to the Go language.</p></pre>zond: <pre><p>I meant it specifically for Go!</p> <p>Go, more than any other language I have used, punishes you for over engineering and rewards straight forward solutions.</p> <p>Compare to Ruby, or C++ or almost any other language having lots of magic features to create elegant solutions. In those languages your elegant solutions will actually be elegant, and not just something to make you curse, revert and choose the simplest possible naive solution instead.</p></pre>flambasted: <pre><p>Converting between []byte and string and back repeatedly.</p></pre>anoland: <pre><p>This is something I have a hard time with.</p> <p>For example with http.Get() I always have to convert from []byte to a string in the response.Body because I&#39;m really looking for some specific portion of the HTML. </p> <p>What should be done instead?</p></pre>davecheney: <pre><p>you probably want to wrap that []byte in a bytes.Reader and then pass it to something that consumes an io.Reader, not a string.</p></pre>The_Sly_Marbo: <pre><p>It depends how you&#39;re processing the response, but I&#39;d recommend using &#34;golang.org/x/net/html&#34; (IIRC). The documentation isn&#39;t amazing but it&#39;s a really good package.</p></pre>anoland: <pre><p>That looks like just the thing I need. </p></pre>SingularityNow: <pre><p><del>Just be careful because it&#39;s also eXperimental (/x/) and just as likely to change out from under you as anything</del></p> <p>Edit: Don&#39;t listen to me I have no idea what I&#39;m talking about.</p></pre>kingfishr: <pre><p><a href="https://groups.google.com/d/msg/golang-dev/lY9vownnJXE/SHB0VooXmkkJ">x does not stand for experimental.</a></p></pre>dchapes: <pre><p>I&#39;m pretty sure the <code>/x/</code> does not mean experimental. There is <code>golang.org/x/exp</code> for that. The <code>golang.org/x/</code> <a href="https://golang.org/pkg/#subrepo">sub-repo packages</a> are just separated out from the core standard packages to (a) not clutter the core with things needed less often or with things that change rapidly and (b) not tie them down to the compatibility promise of Go 1.x. The later does <em>not</em> mean or imply that the APIs get changed willie-nillie and are &#34;just as likely to change out from under you as anything&#34;. (I.e. they have the same contribution and code review process as the core unlike many random GitHub packages that may have a single developer that may change anything whenever they feel like it).</p></pre>SingularityNow: <pre><p>Yah, my mistake. I knew something somewhere was experimental, and I recently had <code>golang.org/x/tools/astutil</code> move out from under me and I just correlated those two things in my mind.</p></pre>The_Sly_Marbo: <pre><p>True, but the source is there so you can just vendor it.</p></pre>flambasted: <pre><p>In just about every case, you can do anything you can do with strings with []bytes. For instance, the &#34;bytes&#34; package has near equivalents for most of the functions available in the &#34;strings&#34; package. Are you using some HTML parsing library which only works with strings?</p></pre>oscarreyes: <pre><p>what&#39;s the best practice to avoid this?</p></pre>davecheney: <pre><p>stick with one as long as possible, which one doesn&#39;t really matter, choose the one that gives you the least conversions.</p></pre>charitable_view: <pre><p>Go newbies use too many pointers. One of Go&#39;s best and most innovative features is the ability to pass objects by value. It is often faster because it avoids memory allocation and garbage collection, which are much slower than copying a few extra bytes.</p></pre>anoland: <pre><p>Well, the docs almost literally say the majority of the time you should use them more often than not.</p> <p><a href="https://golang.org/doc/faq#methods_on_values_or_pointers">https://golang.org/doc/faq#methods_on_values_or_pointers</a></p> <p><a href="https://github.com/golang/go/wiki/CodeReviewComments#receiver-type">https://github.com/golang/go/wiki/CodeReviewComments#receiver-type</a></p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

906 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传