Code review on small project i started

xuanbao · · 380 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hello,</p> <p>i started on a little project which is supposed to help my workflow in the future. As a developer i&#39;m anything but experienced, hence i thought it might be smart to get some feedback before i continue.</p> <p>First a brief overview of what i want to achieve on the long run: </p> <p>i want to write a small CLI tool that automatically syncs my postgres database with my projects models and also auto-generates getters/setters.</p> <p>What it does right now: reads a model file, f.e. User.go and let&#39;s me generate functions for every struct field. So i get fieldnames, types and tags.</p> <p><a href="https://gist.github.com/anonymous/8e09e4715e08edbbb8d288b1ebf82f75" rel="nofollow">Code</a></p> <p>Any feedback or suggestions?</p> <p>Regards</p> <hr/>**评论:**<br/><br/>NeedsMoreTests: <pre><p>It&#39;s hard to do code review on Reddit so the very first thing I&#39;d suggest is to host your code somewhere or use a service list pastebin/gist/etc. That will give people line number and properly format your code too.</p> <p>Aside from that, here are some suggestions (sorry for the bold, I don&#39;t mean to yell it just makes formatting easier):</p> <p><strong>Structs should go outside of the function body</strong></p> <p><strong>Don&#39;t use else in an if statement unless you really need to. This is cleaner:</strong></p> <pre><code>dat, err := ioutil.ReadFile(&#34;./models/&#34; + modelfile + &#34;.go&#34;) if err != nil { panic(err) } [ ... ] </code></pre> <p><strong>Use <code>filepath.Join</code> to join paths, not <code>+</code></strong></p> <p><strong>This section</strong></p> <pre><code>if x.Name == &#34;models&#34; || x.Name == modelfile { } else { if property == 0 { newfield.name = x.Name property++ } else if property == 1 { newfield.fieldtype = x.Name source = source + &#34;\n//Get&#34; + newfield.name + &#34; function\nfunc Get&#34; + newfield.name + &#34;() &#34; + newfield.fieldtype + &#34; {\n fmt.Println(\&#34;test\&#34;) \nreturn 1\n}\n&#34; property++ } } </code></pre> <p><strong>...would be cleaner if it looked something like this</strong>:</p> <pre><code>if x.Name == &#34;models&#34; || x.Name == modelfile { break } switch property { case 0: [ ... ] case 1: [ ... ] } </code></pre> <p>Making code readable is nearly as important as it being functional. One because you read code more often than you write it and it also makes it harder to make silly mistakes. </p> <p>Based on your description and what you&#39;ve written you might consider a different approach. It would seem you&#39;re trying to automate the process of writing code which more times than not is the wrong approach to solving the problem. For example you could write an interface that would take field name and then use that in the model to return the information you need. You also write something that takes a struct and then does something based on how you&#39;ve annotated a field. Like <code>json.[Marshall|Unmarshall]</code> would do with this:</p> <pre><code>// instead of &#39;json&#39; you could have something else here. type Foo struct { Bar string `json:&#34;bar&#34;` } // Google&#39;s protobuf library for example makes extensive use of this approach. // 0 - field number // opt - means the field is not required // name - the name of the field in the protobuf type Foo struct { Bar string `protobuf:&#34;string,0,opt,name=bar&#34; json:&#34;bar,omitempty&#34;` } </code></pre> <p>Parsing syntax trees is certainly a useful skill to learn but I don&#39;t think it&#39;s the appropriate solution in this case. It&#39;s kind of like handling user input, don&#39;t trust it and expect that at some point it will break your code. While this might work for you short term you&#39;ll eventually have to refactor it as you want to add more which overall will probably take you more effort than figuring out a better way of handling this.</p></pre>FPSports: <pre><p>Hey, thanks a lot!</p> <p>Could you elaborate a bit further on why to use filepath.Join?</p> <p>I&#39;ve updated the code with your suggestions.</p> <p>Regarding the general approach: i figured writing getteres and setters automatically might not be the best use case, since creating an interface for this task that applies i can use for all models would be better. I kinda struggle with interfaces in general... </p> <p>The next bigger step would be to check if the according database table is still up-to-date based on the model or i need to alter it. So f.e. i want to do:</p> <p>iterate over the &#34;newfield&#34; my current code generated -&gt; check if table with name of the model exists -&gt; check if columns are still up to date based on newfield.name/type/tag.</p> <p>I don&#39;t think i can do this with interfaces, can i? </p> <p>Regards</p></pre>NeedsMoreTests: <pre><blockquote> <p>Could you elaborate a bit further on why to use filepath.Join?</p> </blockquote> <p>Sure, there&#39;s a few reasons</p> <ul> <li>It&#39;s cleaner. <code>filepath.Join(a, b, c)</code> vs <code>a + &#34;/&#34; + b + &#34;/&#34; + c</code></li> <li>It&#39;s more consistent. In the example above, you <code>a</code> could be something like <code>foo/</code> in which case <code>+ &#34;/&#34;</code> is unnecessary. There are other things it does too, I&#39;d read the docs for more information: <a href="https://golang.org/pkg/path/filepath/#Join" rel="nofollow">https://golang.org/pkg/path/filepath/#Join</a></li> <li>It&#39;s cross platform</li> </ul> <blockquote> <p>I kinda struggle with interfaces in general...</p> </blockquote> <p>To be honest, I did too until recently. Coming from a Python/C++/Java background interfaces were a bit odd. Here&#39;s a minimal example which might help with the understanding: <a href="https://play.golang.org/p/sXkiAW9ReY" rel="nofollow">https://play.golang.org/p/sXkiAW9ReY</a></p> <p>In the example code the <code>Bob</code> and <code>John</code> structs implement the <code>Name()</code> function. The <code>Person</code> interface stats that any struct which implements <code>Name()</code> is also a person. This is a simplified view of what&#39;s actually going on under the hood of course but hopefully it&#39;s enough to give you something to test/play around with.</p> <blockquote> <p>I don&#39;t think i can do this with interfaces, can i?</p> </blockquote> <p>I&#39;m sorry, I don&#39;t know. It&#39;s still a little difficult to tell what you&#39;re trying to implement without seeing the code you&#39;re thinking off. Interfaces may not be the right solution but there are probably better solutions than parsing syntax trees too. Here&#39;s my suggestion, write some tests for your existing code especially around edge cases which could break your existing design. If it&#39;s easy to test and your tests don&#39;t need to be modified or rewritten for every model you have then maybe the current approach is an ok solution. If you can&#39;t write tests then your current design may be incomplete or not be sufficiently testable in which case you might need another solution.</p></pre>FPSports: <pre><p>What&#39;s the benefit of using an interface there instead of just <a href="https://play.golang.org/p/Qn-kBK0bhT" rel="nofollow">this</a>?</p></pre>acdenisSK: <pre><p>edit: threw everything away and started from scratch because i started to get confused whether my comment actually answered your question.</p> <p>Because this approach allows for virtually anything to be a <code>Person</code> without the hassle of maintaining structs that are <em>supposed</em> to be people.</p></pre>NeedsMoreTests: <pre><p>It&#39;s a simple example, there&#39;s basically no advantage in this case. </p> <p>The main point behind the example was to show that you can use an interface to pass around a common type instead of a specific type in a function call. Basically interfaces allow you to provide abstractions: <a href="https://play.golang.org/p/GzNmf1M31C" rel="nofollow">https://play.golang.org/p/GzNmf1M31C</a>. So in your code you could have something like:</p> <pre><code>type Model interface { Set(name string, value interface{}) error Get(name string) (interface{}, error) } </code></pre> <p>So any struct which implements the above interface could figure out how to handle Set/Get internally. <code>interface{}</code> allows you to handle &#39;any type&#39; instead of having to be explicit. So you will have to do the type conversion yourself but I feel like this approach is cleaner than parsing the syntax tree and generating functions. On a side note about code generation, I neglected to mention that <code>go generate</code> is pretty standard. So if you do still want to go that route, that&#39;s probably how you should be generating the code.</p></pre>FPSports: <pre><p>Thanks for your answers man. Appreciated a lot. What you wrote above actually came to my head few hours later as well. So i basically wouldn&#39;t need to write getters and setters for every field in a struct ... </p> <p>Regarding the code generation .... i&#39;d rather avoid it in general for my purposes tbh. And for this little tool i won&#39;t need code generation if i implement getters/setters through an interface.</p></pre>NeedsMoreTests: <pre><p>Anytime, glad I could help! </p> <p>Code generation by itself can be a powerful tool and is sometimes necessary too, like when using Protobufs, but it certainly has some drawbacks to it too. Picking the right tool for the job just takes time and experience...especially when learning a new language.</p> <p>Best of luck!</p></pre>

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

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