<p>I've seen some posts describing peoples go workflows but this question is a bit more basic then those. I'm not really interested in your editor, OS, or CI pipeline and am more interested in your writing process from line 1.</p>
<p>Background:</p>
<ul>
<li><p>I'm new to go but come from a scripting background with powershell, some bash and python, some c#. I generally can read through docs and hammer something basic out within a day or so. I love go but have been struggling and I feel like maybe it's because I don't yet have a good development workflow. </p></li>
<li><p>currently using windows with Vscode and the Vscode-go plugin.</p></li>
</ul>
<p>Questions: </p>
<p>1.</p>
<ul>
<li>it feels like it's taking me forever to implement the smallest features and I'm thinking it's maybe my workflow. </li>
</ul>
<p>In other languages I usually create an entry point (something like main.go in Go) and then outline all these things I'm trying to accomplish in this project by writing in placeholder functions and stuff. </p>
<p>From there it's easy to fill in the outline piece by piece. For each of the functions or pieces of logic I've defined in my entry file, I create a new file with preferable only one function in it. I can then build. Test and debug all the logic in that one file isolated from the rest of the project. Once it's ready, I move on to the next one, etc. </p>
<p>In Go I don't think I can do this because you can't call arbitrary files. Everything starts with main.go. Also when running main.Go you cant reference files or functions that don't exist yet. </p>
<p>The question here, is anyone else developing like this? Is there any way to test and debug just one file or feature of your go code without having to comment everything else out? Any recommendations for alternative workflows? </p>
<ol>
<li></li>
</ol>
<ul>
<li>Does anyone else split their program into fully separate projects and then just import them in their primary project? I've been mulling this idea because it would let me focus on building a specific feature and then when it's ready I'd just import it into my main project and call it. </li>
</ul>
<p>3.
- I typically only write tests after I've completed a function and after I've run it a few times supplying varying parameters. Then the tests are mainly there to ensure the state doesn't change if I add more features or modify things. I don't use TDD because, for me, it's too tedious writing the tests first. How are you real go programmets doing testing?</p>
<p>Thanks for any advice or thoughts! </p>
<hr/>**评论:**<br/><br/>shovelpost: <pre><ol>
<li><p>A good way to start is to get a working prototype asap. That might mean to have all your code in <code>main.go</code>. Once it takes shape, you can start identifying things you can separate in packages. That's when you can start writing tests too (for the packages).</p></li>
<li><p>Just separate your code into packages. It's theory it's the same as importing a whole different project. If a particular package ends up being so useful that could be used by other projects, then sure you can move it to a different repo.</p></li>
<li><p>If you are writing something you've written many times such as a web API, you can certainly do TDD from the start. If it's unknown territory you can just write tests as you need them. See 1.</p></li>
</ol></pre>proctoban: <pre><p>Thanks! This is very helpful! So for point 1, the prototyping phase, do you typically just make a note of imports you need and comment them until you've implemented them? How big does this prototype get before you start breaking things out ( I'm sure that depends a lot on the project in question but if you have an idea....) ? I have a preference for working lots of with small concise files rather than larger files. </p></pre>shovelpost: <pre><blockquote>
<p>do you typically just make a note of imports you need and comment them until you've implemented them?</p>
</blockquote>
<p>I don't usually mess with imports and I don't think you should either. Just use <code>goimports</code> on save.</p>
<blockquote>
<p>How big does this prototype get before you start breaking things out ( I'm sure that depends a lot on the project in question but if you have an idea....) ?</p>
</blockquote>
<p>Yes indeed it depends on the project but as a rule of a thumb, if the <code>main.go</code> file starts getting too large to manage before you have realized which parts need to be broken into packages, then maybe you should start separating your code into different files in <code>package main</code>.</p>
<p>I'd also say that the prototyping phase ends when you have your proof of concept or maybe a minimum viable product. By that time it usually becomes quite obvious the parts of your code that can be reusable and can be put into packages.</p>
<p>I think this workflow is quite effective when you explore uncharted territory. The way I see it, it's much better to have something minimal that works even if the code is a little ugly, than to stress about architecture and details early on. It's also much cheaper to throw away your prototype and start from scratch even, in the worst case.</p>
<p>Of course if you are working on a concept you are familiar with or you are building a complex system, then you better start with a paper and <a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&t=18m09s" rel="nofollow">Design the architecture, name the components, document the details.</a></p></pre>epiris: <pre><blockquote>
<ol>
<li>- I typically only write tests after I've completed a function and after I've run it a few times supplying varying parameters. Then the tests are mainly there to ensure the state doesn't change if I add more features or modify things. I don't use TDD because, for me, it's too tedious writing the tests first. How are you real go programmets doing testing?</li>
</ol>
</blockquote>
<p>This is creating all your problems. I very rarely run my main while writing Go code. For example my current project which is over 100K lines of library code has two mains containing only 3 lines of code, both look like this:</p>
<pre><code>import ( "repo/pkg/cmd/withheld" )
func main() { os.Exit(withheld.Run(os.Args...)) }
</code></pre>
<p>This allows cmd/withheld to be a package which I can run <code>go test</code> on for easily testing flags. It also keeps the main pkg very clean and minimal. This emphasizes how the main package is really meant to do one thing: start your program.</p>
<pre><code>// Run will run the withheld application and return an appropriate exit code.
func Run(args ...string) (exitCode int) {
ctx := withSignals(context.Background(), os.Interrupt, syscall.SIGTERM)
return RunContext(ctx, args...)
}
</code></pre>
<p>The RunContext will take the given flags and configuration structures for the primary application repo/pkg/withheld.Withheld which glues together many subpackages to form a full working application with test files that serve as end-to-end tests. The subpackages it imports all have their own test files that tests only the functionality they provide. </p>
<p>The take-away here is to break down your apps into smaller problems (packages), test and solve each problem exporting only the parts you need. For smaller applications you can glue them all together in your main package, but it is often useful to glue them together in a "application" package instead. Most of all use <code>go test</code> to "run" your programs. You don't have to follow strict TDD, just instead of modifying your main to run your program do:</p>
<pre><code>func TestPkgname(t *testing.T) {
fmt.Println(DoesThisWork())
}
</code></pre>
<p>Once it works, simply:</p>
<pre><code>func TestPkgname(t *testing.T) {
if exp, got := "worked", DoesThisWork(); exp != got {
t.Fatalf("exp DoesThisWork() to return %v; got %v", exp, got)
}
}
</code></pre>
<p>So you know when it stops working :-) Also see <a href="https://blog.golang.org/cover" rel="nofollow">test coverage</a>, <a href="https://blog.golang.org/subtests" rel="nofollow">sub tests</a>, <a href="https://blog.golang.org/examples" rel="nofollow">testable examples</a> and the #1 most important out of these given your statement of "I typically only write tests after I've completed a function and after I've run it a few times supplying varying parameters" <a href="https://github.com/golang/go/wiki/TableDrivenTests" rel="nofollow">table driven tests</a> as I bet that will make your world much much easier.</p></pre>proctoban: <pre><p>This is an excellent write up! Thank you. With my previous line of thinking I wasnt really focusing on the go testing infrastructure but Im seeing the benefits and will def take another look at the doc</p>
<p>One point I want to make sure I understand from your example, are these subpackages truly go subpackages nested in the project directory or are they separate projects all together located in gopath/src/</p></pre>epiris: <pre><blockquote>
<p>One point I want to make sure I understand from your example, are these subpackages truly go subpackages nested in the project directory or are they separate projects all together located in gopath/src/</p>
</blockquote>
<p>For a really simple program that does't need more any support packages you could do everything in your main. When you need just 1 or two support packages that may be general purpose and you see a potential for reuse it may be worth giving them their own repository. For a more complex application that may need many supporting domain specific packages I tend to do something like:</p>
<pre><code>$GOPATH/src/repo/project/ # repo root
cmd/name/name.go # contains the main pkg that calls Run
# Contains the name pkg that defines Run. For really large projects
# this will construct a valid name/config.Config from flags, env,
# service discovery, may handle some CLI version compat
# as well. It will then start the service using the api in pkg/name.
pkg/cmd/name
# pkg/name glues together all of the support packages to bring up a
# working system. It usually has a simple API / Entry point such as
# pkg/name.New(cfg) or for projects which start a service it will
# be pkg/name.Serve(ctx, cfg)
pkg/name
# As mentioned above, a small note here is I typically have a rule that
# it does not reference ANY external structures. It can be tempting to
# embed structures for configuration, but I've found it can be really
# distracting to have to change all your API call sites that take cfg
# anytime you want to add fields.
pkg/name/config
# If the project has a public API, any types needed for its use go here.
# If a package seems useful but isn't related to the project its
# probably better to make a new repo for it so you don't get tempted to
# import your into application in the future for access to a few general
# purpose utilities.
pkg/<public facing support packages>
# As a general rule of thumb you could put ALL other packages that do
# not fit the public support packages criteria in here.
pkg/internal
</code></pre>
<p>If you are solving small chunks of your problem at a time it doesn't ever hurt to start at the simplest form in a main pkg. Splitting off from that is usually as simple as cd ..; cp -a prog subpkg; cd subpkg. Good luck hope things get easier.</p></pre>hell_0n_wheel: <pre><blockquote>
<p>Is there any way to test and debug just one file or feature of your go code without having to comment everything else out?</p>
</blockquote>
<p>Yes. Write tests for each feature (you're doing this already, of course!) and use tags to differentiate the tests. Or use the <code>-run</code> flag to select a subset of tests.</p></pre>proctoban: <pre><p>Good point, will revisit go test docs. Thanks! </p></pre>brianketelsen: <pre><p>if you're writing command-line apps/tools/servers I highly recommend starting with github.com/spf13/cobra. It's got a nice generator that builds the skeleton of a commandline app that's easy to understand and extend. I use it for nearly everything now. </p></pre>proctoban: <pre><p>Thanks! Will check it out. One issue I've had with generators when learning a new language or framework is that, via the scaffolding process, certain important concepts get abstracted away, which isn't necessarily bad but, if there's an issue, I find that it's harder to figure out where something broke.</p>
<p>Haven't looked at cobra but I'm just wondering how much is being abstracted away by this tool or if you could see this as a valid concern? </p></pre>shovelpost: <pre><p>In my opinion, just use the standard library <code>flag</code> package to get started with making a command line app. There's no need to use such a huge dependency as cobra unless the complexity of your command line app is really huge and/or you keep making many non trivial command line apps.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传