10 ms·
SOLID Go Design
- quickben 10y agoWhat's with all the Go pushing lately?
- virmundi 10y agoThings tend to come in waves. I can't prove it, but I think it's due to an, "Oh, yea. That exists." mentality. There were a few articles around social apps the other day that made me almost submit a "What happened to Yo!" article. Thus continuing the wave. I stopped because the article that I found was from 2014. However, it's still there. They're still enhancing it.
- bojo 10y agoWell, 1.7 just came out, and GolangUK just happened? Not exactly surprising that a lot of posts pop up around releases or events within a specific community.
- quickben 10y agoAh that makes sense then. Interesting, I missed that but of news completely it seems. Thanks
- deleted 10y ago[deleted]
- pkroll 10y agoAlong with the ones mentioned by bojo, GopherCon 2016 ended a few weeks back, and the videos from the conference just started getting released five days ago. (They've got 12 up now, they put a few more up yesterday.)
- akkartik 10y agoeyeroll I wonder how Robert Martin was implementing SOLID before the wonder that is Go was delivered unto us. And I wonder why all our buzzword-laden codebases are still knee deep in crap if SOLID is so amazingly awesome. We've been hearing about coupling and cohesion for over 50 years now. At what point does thinking in those terms become part of the problem rather than the solution? Is it possible that Go or any other language du jour just seems cleaner because it's newer and so the cruft hasn't had a chance to form yet? Come back in 10 years, and tell me how amazing it is then.
- aaronbwebber 10y agoIf you've got some great conceptual framework for thinking about code quality that doesn't include coupling and cohesion or some equivalent ideas, please, share it with us all. I'm not holding my breath thought. The reason we keep talking about coupling and cohesion is that these are useful concepts that can be fairly directly applied to most day-to-day software design problems. This was a nice, fairly short post about how to apply some generally accepted principles of good software design in a specific language. It's a worthwhile read for anyone relatively new to Go programming.
- akkartik 10y agoI do, but I don't want to shamelessly plug here. My point was not that these ideas are bad. My point was that they are incomplete, and pointing at them as reasons why a new language is so great is absurd -- when in fact Java supports them just as well. I think Go is great, but I think it's great because it gets a large number of details slightly more right than the languages of the previous decade. And it will make its mistakes that languages of the next decade will learn from. SOLID is just the bare minimum ante here. Bragging about SOLID is like bragging that your language supports all kinds of data structures.
- papaf 10y agoI am not against thinking in terms of coupling and cohesion but I found the following different and interesting ways to think about separating software: https://www.destroyallsoftware.com/talks/boundaries https://www.destroyallsoftware.com/talks/boundaries http://shaffner.us/cs/papers/tarpit.pdf http://shaffner.us/cs/papers/tarpit.pdf The common theme in both these viewpoints is that the biggest enemy is state, and who is responsible for it, rather than just responsibility on its own.
- jimjimjim 10y agoit's a nice language BECAUSE the authors push back when asked to add this and that and all the other latest language feature fads. They really don't get enough credit for constantly dealing with the negative hordes. Add features = language crufty. Don't add features = where's my generics (or whatever).
- kasey_junk 10y agoI am fairly consistent in my criticism of Go. I am also fairly consistent in my insistence that the Dependency Inversion Principle is one of the harder concepts to wrap your head around of the SOLID principles (not least because several other concepts use terms similar). But golang's structural typing actually allows for some of the easiest demonstrations of the implementation of the DIP of any of the strongly typed languages I've used. I find Martin's definition of the DIP counterintuitive. A description that makes more sense to me is "interfaces should be defined by the 'users' of the interface, not by the implementers". With a language like C++ or Java, to achieve this requires either lots of adapter layers or compiler tricks. With golang it is simple. As a for instance, let's say I'm writing a restful client library for a specific application. It is tempting to to hard code a dependency on the std lib http.Client.Do method. If instead with 3 lines of code, I define my own interface for Do that matches the std library definition. I can use the std library, or any other library that matches it (one that is faster say or implements back off). That is one of the few areas where the golang type system is pretty cool in comparison to its peers.
- pcwalton 10y agoBut with Go's system you can't provide an implementation of an interface for a type unless you defined that type in your package. You have to use an adapter layer for that. (For example, say I want to make an interface with a function "func Show() string" that prints debug information about a type--I can't implement that function directly on http.Client, only on an e.g. "type MyClient = http.Client" wrapper.) So that makes it harder to achieve loose coupling, because you can't make your own interfaces that unify standard library types and your types unless you make your types take exactly the same methods with exactly the same signatures (problematic if you want your types to support slightly different functionality, etc.) In a system that allows you to implement interfaces for types that you didn't define in your package, there is a little more boilerplate necessary compared to Go, but only a few lines necessary to achieve the exact same effect. In pseudo-Go: type Do interface { func Do(req *Request) (*Response, error) } // boilerplate implement Do for http.Client { func (c *Client) Do(req *Request) (*Response, error) { return c.Do(req) // call stdlib implementation } } // custom implementation of Do implement Do for MockClient { ... } So Go makes a tradeoff with its structural typing: it doesn't require the small amount of boilerplate code to achieve the pattern you describe, but it prevents you from providing new implementations for types you didn't define. Reasonable people can differ as to whether the side of the tradeoff Go makes encourages or discourages loose coupling.
- tunesmith 10y agoFor middle of the road companies with middle of the road developers, I find SOLID is not really a great set of principles to focus on. 'S' is pretty good because it helps encourage developers to get away from huge classes that have dozens of methods. 'O' is basically useless when applied to a single codebase, because one of the worst parts of middle-of-the-road developers is when they start to realize they can extend classes. You end up with horrendously complicated and deep class hierarchies. I find that when it's time to add functionality, it's far better to just refactor and create new classes for composition. 'L' is deeply confusing, and the difficulty in understanding it is what leads to the deep and brittle class structures above. 'I' drives intermediate developers to create more and more interfaces that have 1-2 methods, which when combined with their difficulties with 'O' and 'L', just leads to confusion and high cognitive load when trying to maintain the codebase. 'D' is good.
- qyv 10y agoCould not agree more, I find SOLID often creates more harm than good with a lot of devs. For example: S: Each class can only do one thing, ever! Gotta do something else? Lets add more classes that all inherit from each other! O: Heh, no one even remembers this one, like, ever. L: Ya, thats how inheritance works! Right? Right... I: Hey, I know how to create interfaces! Interface all the classes! D: OK, just stick the DB connection into the constructer and we've got the D covered, yesssssh! Really though, IMO SOLID is somewhat overblown in many cases. It is just a set of guidelines for good OO design not any kind of iron-clad rules.
- rimantas 10y agoYour comment just shows that SOLID is not understood, not overblown.
- raverbashing 10y ago"No True Scotsman" indeed And I want to slap the "S" people whenever I see a private method inserted merely to call a library So it's "single responsability" except for a set of arbitrary cases pulled out of who knows where.
- praptak 10y agoThere's something strange about application of the Liskov Substitution Principle in the article. Original formulation of LSP was: subtypes should not break supertypes, i.e. caller expecting supertype should not be able to tell the difference when getting an instance of subtype. That was expressed in terms of an explicit subtype/supertype relation, like inheritance in OOP. When there's no such thing in the language, the LSP becomes (quote article): "Two types are substitutable if they exhibit behaviour such that the caller is unable to tell the difference." This sounds almost like a tautology. I understand that it isn't, since "substitutable" is "you substitute class X for interface Y in your code" and "observably same behaviour" is "X behaves like all implementors of Y are expected to". In practice, X doesn't declare it implements Y. If I review a change to X I might miss that fact. If that change breaks behavior expected from Y, it's bad. I'm not sure if this scenario is a real problem, I have written like 100 lines in Go.
- adamlett 10y agoIn Go you might have an interface Rectangle": type Rectangle interface { SetHeight(int) SetWidth(int) GetHeight() int GetWidth() int } Then you could conceivably have a type Square which implements this interface. The problem is, that clients that use the Rectangle interface make certain reasonable assumptions that turns out not to hold when the Rectangle in question is a Square. Case in point: If you call SetHeight(x) and SetWidth(y), you would assume that GetHeight() and GetWidth() return x and y. But that obviously violates the invariant of a Square if x and y are not equal to each other. This problem is what LSP aims to address.
- masklinn 10y ago> The problem is, that clients that use the Rectangle interface make certain reasonable assumptions that turns out not to hold when the Rectangle in question is a Square. Case in point: If you call SetHeight(x) and SetWidth(y), you would assume that GetHeight() and GetWidth() return x and y. I don't know that that's a reasonable assumption, it doesn't work with any fixed-aspect-ratio rectangle (of which square is but one case). Basically, the issue here is that the interface is terrible and essentially mandates a single concrete type.
- skywhopper 10y agoDefinitely good stuff to think about in the piece, but even as a Go fan, I found the rhetoric to be insufferable. "Go obviously doesn’t have classes—instead we have the far more powerful notion of composition". Go's syntax may frame things differently than Java or Ruby or C++, and I don't discount the impact of syntax and abstractions on code design, _but_ just because you don't call them "classes" doesn't mean they are something entirely different. Go types can have public and private fields, a set of associated functions which can also be public or private, and ways to extend other structs or pose as other types. So you've got encapsulation, inheritance, and polymorphism. Pretending you don't have "classes" is just silly.
- dilap 10y agoIt's perhaps subtle, but there is a real difference -- traditional inheritance allows the base class to call methods in the derived class; Go embedded fields don't have this property, which is a huge simplification. (You also don't have all the complexities of constructors.)