[Beginner][Feedback] This path tracer is my first Go program. How would you improve it?

blov · · 477 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;m learning Go by writing a <a href="https://github.com/hunterloftis/pbr">physically-based path-tracing renderer</a>. This is the first thing I&#39;ve written in the language, and I&#39;m learning along the way, so I&#39;d appreciate any feedback more experienced gophers have.</p> <p>Cheers!</p> <hr/>**评论:**<br/><br/>akavel: <pre><p>At first glance through e.g. camera.go, for now I&#39;d say rather just fairly small stuff:</p> <ul> <li>&#34;Camera simulates a camera&#34; - hmh, personally don&#39;t like this much. Doesn&#39;t really say anything, not worth the letters in my opinion. Could try maybe dwelling a bit more on the properties it has, what is described about the camera, maybe why? why this and not something else?</li> <li>as mentioned by someone else, tests would be advisable (and esp. consider <em>table-driven tests</em>)</li> <li>Focus&#39;s godoc is non-understandable to me; I have no idea what the func really does (at first I thought maybe it&#39;s something similar to what LookAt does)</li> </ul> <p>Looking at <a href="https://godoc.org/github.com/hunterloftis/pbr/pbr:">https://godoc.org/github.com/hunterloftis/pbr/pbr:</a></p> <ul> <li>you don&#39;t seem to have a <em>package doc</em> for pbr/pbr. Especially, I don&#39;t really know how to start using the package without some intro guide.</li> <li>personally, I&#39;d rather avoid putting the package in <code>pbr/pbr/</code>, I&#39;d just put it in <code>pbr/</code></li> <li>by convention, consts should preferably be named same as normal variables (thus <code>Bias</code> or <code>bias</code>, not <code>BIAS</code>, etc.)</li> <li>looking at <code>Cli</code> in godoc, I have no idea how to use it and what it does</li> <li>&#34;Hit describes an intersection&#34; - of what?</li> <li>&#34;SampleFrame samples a frame&#34; - ok, this much I guessed already from the name, but I totally still don&#39;t understand what the function actually does (kinda like the infamous <code>i++; // increment i</code> comment) and how to use it</li> </ul> <p>etc.</p> <p><em>edit:</em></p> <ul> <li>ok, looking somewhat more at the API: I&#39;d highly recommend trying hard to hide as much internal stuff as possible from the public API. E.g. for <code>Sphere</code>, methods <code>Intersect</code>, <code>MaterialAt</code> and <code>NormalAt</code> don&#39;t seem of any sensible use to the package user, they seem purely internal details, so they just introduce noise in the API. It would be cleaner and easier to understand if you made them private. Unless they are required for some broader &#39;Shape&#39; interface or something, but I don&#39;t see one here (and anyway, in such case I&#39;d put them (i.e. the Shapes) in some subpackage to group them more explicitly).</li> <li>Renderer&#39;s <code>Rgb</code>, <code>Write</code>, <code>WriteRGB</code> seem partly redundant; also by convention it&#39;s good to try fitting some of the stdlib interfaces/func names, e.g. <code>WriteTo</code>, and avoiding clashes (e.g. I&#39;d usually expect <code>Write</code> to have a signature fitting <code>io.Writer</code>; otherwise I&#39;d try inventing a different name); also try using interface like io.Writer instead of taking name of a file - this way you give the user more possibilities.</li> </ul> <p><em>edit 2:</em></p> <p>ok, first heavier issue: <a href="https://github.com/hunterloftis/pbr/blob/62671fbab79ed75f0b04e2fdda0028a0dc052d73/pbr/scene.go#L67">never ignore errors</a>. And anyway, you should rather take an io.Reader instead of a <code>file string</code>. I see you may have copied this fragment of code from example code in rgbe package&#39;s readme, but that&#39;s very not good. Actually, that&#39;s the reason why ignoring errors even in examples is often frowned upon.</p> <p><em>edit 3:</em> in Glass etc., describe in godoc what&#39;s the meaning of the <em>gloss</em> parameter and what values one can put there. Other than that, it&#39;s cool you&#39;ve added such constructors.</p> <p>Ok, enough for now, that should give you some ideas for the direction to go with docs &amp; API cleanup.</p> <p><em>edit 4:</em> ouch, looking at Scene.Add() - the parameter is of type <code>surface</code>, which is unexported, so user won&#39;t know what he/she can actually put there. You should make the <code>surface</code> interface exported. Also, you could make Scene.Add() take variable number of arguments, this should remove some repetitiveness from the code in cmd/ subdir.</p></pre>hunterloftis: <pre><p>Thanks for the detailed feedback!</p> <p><em>GoDoc stuff</em>: I only wrote anything at all because my editor complains when I don&#39;t. I should go through and flesh it out.</p> <p><em>Table-driven tests</em>: Thanks for the keyword, I haven&#39;t seen this before.</p> <p><em>Package docs</em>: I see I can document a package by writing comments above it. Since lots of files compose a package, how do folks tend to choose the file in which to document the package?</p> <p><em>pbr/ vs pbr/pbr</em>: Since root dirs get filled with metadata (.gitignore, .gitattributes, readme.md, etc) I like to keep source files separate. The only way I could think of to do this with Go&#39;s &#34;package == dirname&#34; convention was to use <code>pbr/pbr</code>.</p> <p><em>Const naming</em>: Got it!</p> <p><em>Hiding Intersect etc methods</em>: You&#39;re right! I wrote those methods before I realized that lowercase is still package-wide (vs file-wide) scope.</p> <p><em>Renderer write interface</em>: Good suggestions; I&#39;m not really very familiar with the stdlib interfaces at the moment, but I&#39;ll see if I can make <code>Renderer</code> a <code>Writer</code>.</p> <p><em>Scene.Add</em>: Great suggestions, thanks!</p></pre>akavel: <pre><p>Quick reply:</p> <p><em>Package docs:</em> Personally I usually try to choose some kind of most important file in the package and put it there. But especially if you write a longer intro comment, the convention is to create a separate file named <code>doc.go</code> and put only the (long) package comment there. Search for such files in the stdlib (I suppose the longest is in package fmt, but I&#39;d expect most packages there have it).</p> <p><em>Hiding Intersect [...]:</em> Actually, after <em>edit 4</em>, I believe you should rather keep them public, and make the <code>surface</code> interface public instead (i.e. <code>Surface</code>). This would allow other people to add their own surfaces and have them handled correctly by the renderer (as long as you didn&#39;t cheat by downcasting in your implementation). Personally, I&#39;d then strongly consider putting them (i.e. <code>Sphere</code> etc., maybe also <code>Surface</code> itself but not necessarily) in a separate package, e.g. <code>pbr/pbr/shape</code> (shorter name than <code>surface</code>; also then <code>shape.Surface</code> does actually make quite much sense).</p> <p><em>Renderer vs. io.Write</em>: I meant rather making it an <a href="https://golang.org/pkg/io/#WriterTo" rel="nofollow"><code>io.WriterTo</code></a>, i.e.:</p> <pre><code>func (r *Renderer) WriteTo(w io.Writer) (int64, error) </code></pre> <p>or even better, make it return an <a href="https://golang.org/pkg/image/#Image" rel="nofollow"><code>image.Image</code></a>, so that a user can then decide whether to <a href="https://golang.org/pkg/image/png/" rel="nofollow">encode it as a PNG</a>, or maybe as a JPEG, or display on screen, or whatever else. The idea of interfaces in Go feels somewhat like pipes in Linux if used properly.</p> <p><em>Consts:</em> one additional question here is do you even actually need/want them exposed in the API, or are they again just a private implementation detail.</p></pre>faiface: <pre><p>This looks amazing! Code looks fine at the first glance, good job, man!</p></pre>hunterloftis: <pre><p>Thanks! It&#39;s been a great, fun way to dive into Go.</p></pre>plectid: <pre><p>I assume it&#39;s not your first renderer and you&#39;ve ported it from some other language? The code is clean, very readable and way shorter than I expected for a working path tracer. </p> <p>There are some minor nitpicks, such as why do materials return struct values, which you pass later as references, instead of returning pointers in the first place.</p> <p>Not seeing any tests in a go app is unusual. Other than that, good job, that doesn&#39;t look like the first thing you&#39;ve written in Go.</p></pre>hunterloftis: <pre><p>Thanks for checking it out.</p> <blockquote> <p>why do materials return struct values, which you pass later as references, instead of returning pointers in the first place.</p> </blockquote> <p>Do you mean Surfaces? I see what you mean there with the repetitive &#34;scene.Add(&amp;pbr.Cube&#34;</p> <p>Also, +1 to tests... I just want to sort of get the &#34;shape&#34; of it right before I start writing formal tests.</p> <p>Not my first renderer, but I&#39;m no expert. I play with a lot of graphics stuff in other languages (and now in Go!):</p> <ul> <li><a href="http://www.playfuljs.com/" rel="nofollow">http://www.playfuljs.com/</a></li> <li><a href="https://github.com/hunterloftis/pathtracer" rel="nofollow">https://github.com/hunterloftis/pathtracer</a></li> </ul></pre>plectid: <pre><p>What the performance difference between js and go? It would be interesting to compare the two in single-threaded and multithreaded environments and if golang performance scales linearly with the number of worker threads.</p></pre>hunterloftis: <pre><p>I regret not taking exact measurements when their behavior was 1:1. Roughly, though:</p> <ul> <li>1x JS =&gt; 5x Go =&gt; 15x Go + Goroutines</li> <li><a href="https://twitter.com/HunterLoftis/status/875783656765247488" rel="nofollow">https://twitter.com/HunterLoftis/status/875783656765247488</a></li> </ul></pre>allhatenocattle: <pre><p>Really neat project!</p> <p><a href="https://github.com/hunterloftis/pbr/blob/master/pbr/sampler.go#L36" rel="nofollow">https://github.com/hunterloftis/pbr/blob/master/pbr/sampler.go#L36</a> This rand variable is being passed down a couple of calling layers before being used. A simpler way is to seed the rand package in one file with</p> <pre><code>func init() { rand.Seed(time.Now().UnixNano()) } </code></pre> <p>and then when you need a random number, just use rand.Float64(), no need to create *rand.Rand and pass them around.</p> <p><a href="https://github.com/hunterloftis/pbr/blob/a14583bfcdbf2ce027caea1b517275cb316775ee/pbr/cli.go#L69-L71" rel="nofollow">https://github.com/hunterloftis/pbr/blob/a14583bfcdbf2ce027caea1b517275cb316775ee/pbr/cli.go#L69-L71</a> Since the only action is incrementing across multiple goroutines, take a look at the sync/atomic package, seems a more natural fit.</p></pre>hunterloftis: <pre><p>Will that make the various goroutines block?</p> <p>Initially I was just using the standard <code>rand</code>, and my concurrent version was actually slower (!) than the single-threaded version. After hours of debugging I discovered that the standard <code>rand</code> <a href="https://blog.sgmansfield.com/2016/01/the-hidden-dangers-of-default-rand/" rel="nofollow">uses mutexes to be thread-safe</a>, and since <code>rand.Float64()</code> is one of the most often-called functions in the renderer (called literally billions of times), that mutex was <em>destroying</em> performance. That&#39;s when I started instantiating a new <code>rand</code> instance for each Sampler and passing it down.</p> <p>I would love a method that doesn&#39;t involve so much argument-passing, but at the same time the explicitness is nice (and clear). Or for <a href="https://github.com/golang/go/issues/9221" rel="nofollow">default rand to have smarter concurrent behavior</a>.</p></pre>allhatenocattle: <pre><p>Ah, I didn&#39;t realize that. I&#39;ve never used rand anywhere enough that it caused a material slowdown. TIL. Cheers.</p></pre>hunterloftis: <pre><p>Thanks <a href="/u/akavel" rel="nofollow">/u/akavel</a>, <a href="/u/plectid" rel="nofollow">/u/plectid</a>, <a href="/u/allhatenocattle" rel="nofollow">/u/allhatenocattle</a>, <a href="/u/Sythe2o0" rel="nofollow">/u/Sythe2o0</a> for your code reviews. I&#39;ve updated the project based on your feedback:</p> <ul> <li>The <a href="https://github.com/hunterloftis/pbr/blob/master/cmd/cubes.go" rel="nofollow">top level API</a> is much nicer now.</li> <li>It has <a href="https://hunterloftis.github.io/pbr/docs/pbr.html" rel="nofollow">package docs and better godocs</a>.</li> <li>I&#39;ve updated the <a href="https://hunterloftis.github.io/pbr/" rel="nofollow">readme instructions</a> with build, test, CLI options.</li> <li>The Renderer returns an image.Image in <a href="https://github.com/hunterloftis/pbr/blob/gh-pages/pbr/renderer.go#L38" rel="nofollow">Rgb()</a> and <a href="https://github.com/hunterloftis/pbr/blob/gh-pages/pbr/renderer.go#L52" rel="nofollow">Heat()</a> for end users to use however they like.</li> <li>The Cli is now <a href="https://github.com/hunterloftis/pbr/blob/gh-pages/pbr/cli.go#L72" rel="nofollow">responsible for writing to pngs</a></li> <li>I also replaced some <a href="https://github.com/hunterloftis/pbr/blob/gh-pages/pbr/constants.go#L9" rel="nofollow">magic numbers with constants</a>.</li> </ul></pre>akavel: <pre><p>Cool, much better. Still rough around the edges, but you&#39;ll learn the finer aspects with time. I do especially like the package doc - brief, clear, and to the point; an awesome intro and first impression, makes a world of a difference in usability and approachability of the package. </p> <p>Two more quick notes:</p> <ul> <li>you totally don&#39;t need to setup your own hosting for godoc, there&#39;s <a href="https://godoc.org" rel="nofollow">https://godoc.org</a>, it&#39;s owned by Google and people are used to just going to <a href="https://godoc.org/github.com/hunterloftis/pbr/pbr" rel="nofollow">https://godoc.org/github.com/hunterloftis/pbr/pbr</a></li> <li>regarding your TODO: image.Image is an interface; as a general rule, you&#39;ll never really have a practical need to make a pointer to an interface.</li> </ul> <p>As a final tip, I very much recommend studying the stdlib, you will discover many cool idioms.</p></pre>hunterloftis: <pre><p>Wow! TIL. I&#39;m really impressed with Go&#39;s tooling and ecosystem, had no idea godoc.org auto-generated Github package docs. Just linked there instead.</p> <p>Thanks for all your suggestions!</p></pre>Sythe2o0: <pre><p>The test you do have isn&#39;t written in Go, so someone on Windows can&#39;t run it without using a non-standard terminal. </p></pre>hunterloftis: <pre><p>That&#39;s true; it&#39;s just a bash script that runs and times the go program with common options. I should document how to run it without bash though.</p></pre>Sythe2o0: <pre><p>Well, there is a standard for writing tests in Go that can time and benchmark things cross-platform, and wouldn&#39;t require you to point out how to run the tests at all with the testing package.</p></pre>

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

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