<p>Hi Gophers,</p>
<p>I've been writing a lot of channel heavy packages at the moment and am wanting your opinions on best practices for what I should expose.
For example, If I had a package with a inputWork channel and an outputResult and outputError channel, should I just expose the channels in the package or should I expose a function or method to abstract the channels usage (i.e: "func doWork(work) (result, error)") that uses the channels under the covers?</p>
<p>TL:DR; Expose channels or abstract into a methods/functions?</p>
<p>Thanks for your replies :)</p>
<hr/>**评论:**<br/><br/>alecthomas: <pre><p>I think in general making your API synchronous is best practice. </p>
<p>Citations:</p>
<ul>
<li><a href="http://talks.golang.org/2013/bestpractices.slide#25">http://talks.golang.org/2013/bestpractices.slide#25</a></li>
<li>The standard library is mostly synchronous.</li>
</ul>
<p>Edit: thanks for the gold!</p></pre>jerf: <pre><p>First, alecthomas' point is worth considering. You don't describe enough of what you're doing to know why you need channels. If it is because you are trying to "nicely" package up async behavior, the best solution is just to not do that. For instance, a package to make web requests in some particular manner should not even provide a method to make an asynchronous request; the users of your package do that with <code>go</code>.</p>
<p>But there are also legitimate reasons to need lots of channels, so let's talk about that. In general, you don't want to expose channels in your API; they are an implementation detail that you normally want to hide away from your user. The average package, even one that starts some goroutines that talk to each other a lot, should not have a channel in its publicly-exposed API. The exception is if you have solid reason to believe that a user of your package will want to be able to <code>select</code> on something coming out of your library, in which case you ought to offer the channel directly. But bear in mind the channel becomes part of the public interface at that point.</p>
<p>If you're not <em>sure</em>, I still wouldn't put it in; a user can always</p>
<pre><code>res := make(chan Result)
go func() {
res <- api.ComplicatedFunction()
}
// blah blah
select {
case result := <-res:
// ...
}
</code></pre>
<p>But if you <em>know</em> that you've got something that the user is extremely likely to select on, you can expose the channel directly.</p>
<p>In the standard library, you'll note there's not a lot of channels in it if you look. But, for instance, <code>time.After</code> exposes a channel, because pretty much the only conceivable reason to call it is because you want the channel it returns in a <code>select</code> call. (If you just want to wait you call Sleep.)</p>
<p>The other metric you can use to guide yourself is keep it simple. If you're writing code the user can write themselves, you should probably stop. Users can write code to stuff results down channels, make things async, etc., without violating the package abstractions. Your code should focus on providing things the users can't write themselves because of private function/values/types, etc. I find it helpful to look at my code's godoc and ask every single thing that shows up whether it <em>needs</em> to be there, or whether I can get away with removing it.</p></pre>TheMerovius: <pre><p>All comments are correct. Because it wasn't mentioned in them, here is one additional point about <em>why</em> these are best practices (as far as I know):</p>
<p>Channels have a lot of semantic subtleties that are not exposed in their type. A good example of how this is expressed is the <a href="http://golang.org/pkg/os/signal">os/signal</a> package. Passing a channel is not enough, you must also specify how much of a synchronization point it is. What happens if there is no buffer space anymore (or if it's an unbuffered channels that isn't read at the moment), will the sending goroutine block or will it drop values? If it blocks, what happens to pontientally in-flight requests? Are buffered channels okay and if so, how does this interact with cancellation?</p>
<p>A synchronous API allows you to step all these things (and potential edge-cases) and express them in a safe and clear manner. i.e. if you offer me an API with <code>Next() bool, Value() Val, Error() error</code> (an iterator pattern like <a href="http://golang.org/pkg/bufio#Scanner">bufio.Scanner</a>) I don't have to care how you implemented it, if you buffer or what you do in regards to in-flight requests on cancellation and you are free to change these details later. I don't have to read your tons of documentations about what kinds of channels are okay to pass and troubleshoot weird issues if I accidentally pass the wrong ones.</p>
<p>Concurrency is a first-class part of the language in go, because it allows to hide all the concurrency as an implementation detail and let the caller figure out, if they need to call your blocking API concurrently (wrapping a blocking API into a non-blocking channel-type API is a very simple piece of code) or if it's good enough to just do it serially.</p></pre>mbuhot: <pre><p>With channels, the producer, the consumer and the error handler can be different goroutines. With a function the one caller must synchronously handle all the responsibilities.</p></pre>jerf: <pre><p>Yes, but that's not a problem necessarily. Said function can always route things around if it needs to, and in general, handling errors and successes occurs in the "same place" anyhow.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传