How to structure a struct with many member functions?

polaris · · 384 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Given a struct with many member functions (let&#39;s say 30+), how would you structure the code to make it easy to read/understand/test/etc.?</p> <hr/>**评论:**<br/><br/>Femaref: <pre><p>logical units in different files. Are you sure that you need all those methods or maybe you can split up the struct into more meaningful types?</p></pre>spaztheannoyingkitty: <pre><p>This is a struct that is encapsulating the logic for querying our database. That struct is put into a map[string]DBStruct since we need to have multiple DB connections that&#39;s segregated by the string (sorry for being a bit elusive on the details of that string, but I can&#39;t give out a whole lot of info on that part of the design). Ultimately I need some way of querying the database in a bunch of different ways that is going to be constrained by that string.</p> <p>At the moment, we&#39;re thinking about just splitting the functions across multiple files to segregate different functional areas, but I&#39;m hoping that there is a better solution.</p></pre>hell_0n_wheel: <pre><p>Are you mashing together your functions to manage DB connections and the functions that do DB queries? There&#39;s one place you need to separate concerns: your data model shouldn&#39;t care about how you get the data. i.e. whether you use a single connection or a pool, you should be able to use the same methods for querying.</p></pre>spaztheannoyingkitty: <pre><p>The functions in question are ultimately wrappers around the DB queries. We&#39;ve written these functions such that they have a request/response interface (such as: func GetThing(ThingRequest) ThingResponse ) so at least that part of the interface is clean. The struct that these functions are on takes in a DB client (which is elasticsearch so there isn&#39;t a persistent conection) as a part of its construction. So where it creates the connection to elasticsearch is handled at the map layer. That map layer is another struct that is really just a glorified map[string]OurDBClient.</p></pre>dlsniper: <pre><p>No interface bigger than a few methods is clean.</p> <p>The best interfaces in Go have a single method (think of io.Writer or io.Reader).</p> <p>Please take the advice previously received and try to break your habit from previous languages and adopt a Go way to do it.</p> <p>You can pass the DB connection to the dependencies which need it and they can be decoupled from each-other so that you don&#39;t have a God-struct which holes so many methods. Split your struct to have a single responsibility for the functionality it takes care of, not n different ones.</p></pre>hell_0n_wheel: <pre><blockquote> <p>No interface bigger than a few methods is clean.</p> </blockquote> <p>This isn&#39;t helpful advice. This is just blind application of dogma.</p> <p>How would you suggest putting an interface in front of a DB that facilitates dozens of different queries? &#34;Selecting a record from the DB&#34; is a single responsibility...</p></pre>dlsniper: <pre><blockquote> <p>How would you suggest putting an interface in front of a DB that facilitates dozens of different queries?</p> </blockquote> <p>I could argue that if database/sql.*Db would need to be abstracted, which for some reason it seems to be the case, then one could create 3 interfaces with about 7-8 different methods between them? One would be open/close, one for queryctx, execctx and queryrowctx and one for the transaction part.</p> <p>But if I understand correctly, the author created this megastructre to hold a God-object for every DB interaction, such as SelectUse, UpdateCart, DeleteSomething. In that case, I would decouple the user from the cart from the something functionality.</p> <p>And finally, this could be also done as a collection of functions in different packages, because hopefully the functions are isolated and don&#39;t share state between each-other (other than the DB connection, ofc).</p> <p>More over, because the user needs an interface this leads me to the obvious conclusion that it&#39;s testing time and some mocking might become involved. And what would you prefer to mock? Something that has 2-3 functions or something that has 30+? If the response is the later one, please consider revisiting your response until you say the first one. Continuing with the assumption that testing is involved, it becomes apparent that rather than using the code in the application, the user chooses to trust the code in the mock. Instead of mocking the database (at most), a lot of pointless code will be created to hide code that&#39;s already there.</p> <blockquote> <p>This is just blind application of dogma.</p> </blockquote> <p>This all comes from years upon years of practice and seeing a lot of code bases and learning what works and what doesn&#39;t. It&#39;s nothing new nor Go specific. It also happily coincides with a lot of what Go gets good. </p></pre>hell_0n_wheel: <pre><blockquote> <p>open/close, one for queryctx, execctx</p> </blockquote> <p>That&#39;s coupling the interface a bit too closely to the underlying library IMO. What good would this abstraction serve?</p> <blockquote> <p>different packages</p> </blockquote> <p>How would that work, when everything works on one database connection? That&#39;s what doesn&#39;t make sense. Why different packages?</p> <blockquote> <p>Something that has 2-3 functions or something that has 30+? </p> </blockquote> <p>Either way you slice it, everything still needs mocking and testing. So why different packages?</p> <blockquote> <p>This all comes from</p> </blockquote> <p>From what exactly? In all my experience, I&#39;ve learned that there are times to follow dogma, and times where one must be practical... what are we gaining from your dogma here? I&#39;m still not seeing it.</p></pre>dlsniper: <pre><p>There&#39;s no point in a discussion with you as regardless of what arguments I bring you are just stuck in the opposition.</p></pre>hell_0n_wheel: <pre><p>You started the discussion. I&#39;m just asking for clarification.</p></pre>phxIsFarkinHot: <pre><p>I came from a c# background and made a few structs like this because I couldn&#39;t come up with a clean way to manage dependency injection and I was used to having classes with methods. </p> <p>My methodology has drastically changed once I got a good handle on closures and higher-order functions. Now when I need to manage a dependency, I create a func type for the end function I want to end up with, then create a function which takes the dependency as a parameter and returns an instance of the function type. This function instance will have access to the dependency via the closure, and the rest of the code where it&#39;s used is none the wiser.</p> <p>I can cook up a link on the playground, or send the link to a helpful article if that would be helpful.</p></pre>spaztheannoyingkitty: <pre><p>A playground example would be super useful. This sounds like it&#39;s a pure functional approach and I&#39;m intrigued how you expand on this approach for many functions.</p></pre>tv64738: <pre><p>&#34;How do I make a clean and neat big ball of mud?&#34; You don&#39;t.</p> <p><a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1" rel="nofollow">https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1</a></p></pre>Sythe2o0: <pre><p>If necessary, then I&#39;d make it a composition of multiple sub structs containing tightly coupled items.</p></pre>

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

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