Why are packages imported into file scope instead of package scope? Why is file scope a thing at all?

agolangf · · 445 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>This really doesn&#39;t make sense to me. Go does not let me use a package imported within the same package if the import occurred in another file. However, I am free to use variables between files in a package.</p> <p>This does not make sense to me. I could see eliminating file scope entirely, or reversing the behavior, so that variables cannot be shared between files (at least not without Exporting them), but package imports can. </p> <p>From a dependency analysis standpoint, I&#39;m far more concerned with the total dependencies of a package, not with the dependencies of an individual file. It also confuses things because now I don&#39;t really know if the import in file a is the same as the import in file b. What happens if I import the same package in two different files, but that file implements a singleton pattern with a mutex? </p> <p>It also makes go&#39;s (very nice) feature of failing to compile due to unused packages far more annoying than it needs to be. If go complained only when imports aren&#39;t used anywhere within your package, the dance of enabling/disabling fmt over and over would be far less painful. </p> <p>The only argument I can see is that it allows support for testing; it allows me to import dependencies into &#39;_testing&#39; files without making them dependencies of the main package. But this behavior easily could have been added with some special casing. </p> <p>I also recognize that it gives some more granularity with imports, but that granularity is honestly pretty undesirable, especially when coupled with go&#39;s already bad versioning and dependency management tooling.</p> <p>I feel like I must be mistaken or totally misunderstanding something. What is it?</p> <hr/>**评论:**<br/><br/>cs-guy: <pre><p>Consider how the current design allows different dependencies when your package has build tags to work with older versions of Go, or to support different OS/Platform combinations. For example, you may need to import golang.org/x/sys/windows in a code_windows.go file, but doing so in a code_linux.go file would make no sense.</p></pre>dontwantanaccounttt: <pre><p>Read through stdlib or bigger projects. Typically packages are made up for 1-~10 medium to large files. The default idiom should be &#39;write everything in 1 file, and then split out bits when it makes sense&#39; -- not &#39;write everything in it&#39;s own file, combine when it makes sense&#39;.</p></pre>Ploobers: <pre><p>If there were only package scope, then you would either have to put all dependencies into a single separate file, or you would have to have the same imports at the top of every file, both of which are significantly worse options than they are today.</p> <blockquote> <p>If go complained only when imports aren&#39;t used anywhere within your package, the dance of enabling/disabling fmt over and over would be far less painful.</p> </blockquote> <p>Are you aware of <code>goimports</code>? Any editor supports on save hooks, and then there is never any dance.</p> <p>While it may not be theoretically consistent to have both file and package scopes, it perfectly reflects the ideals behind Go. It is the clearest and simplest way to solve the issue of importing packages.</p></pre>MALE_SHOEGAZE: <pre><p>That makes sense, too. I don&#39;t buy that having a single file for imports is strictly worse than the current method, but I haven&#39;t really considered it yet.</p> <p>And yeah, I&#39;m aware of and use goimports. It is a good solution to the problem, but its existence shows that there is maybe an actual problem to discuss.</p></pre>TheMerovius: <pre><blockquote> <p>And yeah, I&#39;m aware of and use goimports. It is a good solution to the problem, but its existence shows that there is maybe an actual problem to discuss.</p> </blockquote> <p>Hm? No, it&#39;s a <em>solution</em> to an actual problem (namely, that unused imports being an error is annoying, but we don&#39;t want to allow them to improve compile times and basic code hygiene). Saying goimports&#39; existence shows there is a problem, is like saying git&#39;s existence shows that there is something fundamentally wrong with version control.</p></pre>sh41: <pre><p>It&#39;s just a way of organizing code.</p> <p>If you want to effectively eliminate or not have file scope and only have package scope, you can already do that today: create a Go package with a single .go file.</p> <p>It works great for really small Go packages. Once they&#39;re bigger, you&#39;ll appreciate the ability to split into multiple files and having file stope.</p></pre>dontwantanaccounttt: <pre><p>Yeah, exactly this. Go encourages you to build medium sized files in your packages. Most related functionality should be within a single <em>file</em>. I can&#39;t see the use for the behavior that the OP is describing, outside of putting a ton of very small files in a package.</p></pre>MALE_SHOEGAZE: <pre><p>It&#39;s odd behavior because most declarations happen within package scope and are still shared between files, while package imports are not. </p> <p>I totally understand the desire to group related functionality into files. I would like go to go further and prevent me from sharing variables/functions/other declarations between files, but I understand the choice not to do that. </p> <p>What is weird to me is that these declarations are shared between files, while imports are not. It&#39;s the opposite of what I would expect.</p></pre>dontwantanaccounttt: <pre><p>If anything I would say it&#39;s unexpected that declarations are shared at a package level, but not that imports aren&#39;t. Which seems like maybe something you agree with, just would expect declarations to stay in step with imports.</p></pre>MALE_SHOEGAZE: <pre><p>Yep, exactly. I&#39;d be happy with declarations being kept to file scope (thrilled actually, I hate that it doesn&#39;t work this way). It&#39;s just odd that file scope is mostly only for imports. Especially because dependencies are usually discussed in terms of a project or packages dependencies, not in terms of the dependencies of the individual files in that project.</p> <p>The crux of what I&#39;m trying to get at is: file scope exists. It could be really useful for keeping variables from leaking between files in a package (I struggle with this problem constantly). Instead it&#39;s used to force files to explicitly declare their imports*, which doesn&#39;t seem like meaningful behavior to me because who cares about the dependencies of a file when we should be more concerned about the dependencies of the package?</p> <p>* And while it succeeds at this, it fails to achieve the more meaningful goal of keeping dependencies (in the DI sense) explicit. Variables are still declared anywhere in package scope, so while the file&#39;s imports are explicit, its dependencies are not.</p></pre>sh41: <pre><p>Are you using <code>goimports</code> on save? If not, I highly recommend it. This is exactly the kind of issue it makes a non-issue.</p> <p>With <code>goimports</code> on save, having to import packages in each file becomes something so effortless you literally forget about it. Just like many other things that disappear once they&#39;re easy and efficient to manipulate.</p> <hr/> <p>Most of the time there&#39;s no advantage to having each file have its own imports, but there are times when it&#39;s nice. Let me demonstrate with an example.</p> <p>Suppose you have a package <code>service/issues</code> that defines an issue tracking service, and a package <code>service/reactions</code> that defines a reactions keeping service. Suppose each service interface has many implementations in subfolders, but you&#39;re interested in using the filesystem one for each. So, the packages you want to import are <code>service/issues/fs</code> and <code>service/reactions/fs</code>.</p> <p>Since both packages are called <code>fs</code>, you can&#39;t really import them in the same file. You&#39;d have to rename them to <code>fsissues</code>/<code>fsreactions</code> or so.</p> <p>However, consider the alternative. If you already have a separate file <code>issues.go</code> for dealing with issues, and <code>reactions.go</code> for everything reactions related, then each file can simply import <code>fs</code> package and use it like <code>fs.NewService()</code>. There&#39;s no collision, and it&#39;s pretty clear what&#39;s going on IMO.</p> <p>You can see a real-world example of this here:</p> <p><a href="http://instantshare.virtivia.com:27080/1zxby9puytzh.png" rel="nofollow">http://instantshare.virtivia.com:27080/1zxby9puytzh.png</a></p> <p>Keep in mind this is simply <em>one example</em> of what you can do as a result of imports being at file scope. There are other nice things.</p></pre>metakeule: <pre><p>That is in fact an argument against the current behavior. It is confusing to the casual reader which will be you in a few weeks. Package aliases are there to handle collisions.</p></pre>sh41: <pre><p>In what way do you find it confusing?</p></pre>metakeule: <pre><p>In the same package name for different things. When I glare over a file of a project I don&#39;t read everything top to bottom. When I saw a certain package &#39;fs&#39; imported somewhere and then switch the file first intuition is to expect the fs package to be the same. </p></pre>sh41: <pre><p>I see.</p> <p>It&#39;s the same package name for very similar things. Both provide filesystem-based implementations. It just wouldn&#39;t make sense to put all the services into a single <code>fs</code> package, so they&#39;re split up with one <code>fs</code> package for each server. But logically, you can think of <code>fs</code> as a single package providing multiple service definitions.</p> <p>So I think it&#39;s okay if you thought they&#39;re the same package upon cursory look - the point is that the details can stay out of sight until you need them. Only if you need to investigate the fs implementation details would you descend into that package, and then you&#39;d see exactly where each one comes from.</p> <p>Put simply, this setup may cause you to miss some unimportant information at first, but if it becomes important to you, it&#39;s easy to see.</p></pre>shovelpost: <pre><p>From what I understand, you seem to be having problems with imports and unused imports. Use <code>goimports</code> on save and your problems will be gone.</p> <p>Stop thinking so much about structure and just write the code that solves your problem. Plain, boring and simple. Then adapt when and if you have to. Go allows that. See <a href="https://github.com/golang/go/wiki/GoTalks#go-code-that-grows-with-grace" rel="nofollow">Go: code that grows with grace</a>.</p> <p>If you still insist on finding out why things are the way they are when it comes to imports then I recommend you reading <a href="https://talks.golang.org/2012/splash.article" rel="nofollow">Go at Google: Language Design in the Service of Software Engineering</a>.</p></pre>mixedCase_: <pre><blockquote> <p>Go does not let me use a package imported within the same package if the import occurred in another file.</p> </blockquote> <p>What? This is definitely not the case.</p> <p>EDIT: Okay people, there was some room for interpretation, please read below before downvoting.</p></pre>MALE_SHOEGAZE: <pre><p>Are you sure? Maybe I&#39;m not explaining clearly.</p> <pre><code>. ├── b.go └── main.go </code></pre> <p>in b.go:</p> <pre><code>package main import &#34;fmt&#34; var _ = fmt.Println </code></pre> <p>in main.go: </p> <pre><code>package main func main() { fmt.Println(&#34;foo&#34;) } </code></pre> <p>go build:</p> <pre><code>./main.go:4: undefined: fmt in fmt.Println </code></pre></pre>mixedCase_: <pre><p>I understand now what you mean. The phrase I quoted has another interpretation: If b.go imports fmt, you wouldn&#39;t be allowed to import fmt in main.go.</p> <p>In any case, per-file imports instead of per-package means clarity, which is one of the major design goals of the language. What we edit are the files, not the packages. With the import line in every file it becomes clear that you&#39;re dealing with an external package and not a package-level variable.</p></pre>MALE_SHOEGAZE: <pre><p><em>That</em> answer makes sense to me. For some reason I thought you could assign an imported package to a new package-level variable, which would have defeated this argument somewhat.</p></pre>Ploobers: <pre><p>I think you misinterpreted package aliasing. You can do something like:</p> <p><code>import mysuperfmt &#34;fmt&#34;</code></p> <p>You&#39;re not assigning it to a local variable, it&#39;s just available in the rare instances that you have a package naming collision or if you just want to shorten a long package name that you use often.</p></pre>MALE_SHOEGAZE: <pre><p>No, I was aware of aliasing. I thought you could also assign package &#39;references&#39; to package-level variables (thus allowing you to share packages between files without importing them). I&#39;d just never tried it because it doesn&#39;t sound like a good idea.</p></pre>

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

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