Quick takeaways
- Simplicity isn’t enough for complex applications - while Go’s syntax is simple, complex applications still need proper design patterns; primitive code easily becomes spaghetti code in large projects.
- Reading the standard library isn’t the best way to learn Go - it’s optimized for different goals than typical applications and might be confusing for beginners.
- Router libraries are better than the standard HTTP package - libraries like Chi or Echo come with a nice high-level API.
- Struct-based configuration is better than the “optional pattern” - structs are easier to document, discover, and maintain than the popular With-options approach.
- There’s no one best project structure - starting small and evolving your structure as needed is better than following a dogmatic approach like the unofficial “Go project layout.”
- Writing stubs by hand is better than using mocking libraries - manually written stubs are easier to debug and encourage better interfaces than reflection-based mocking libraries.
- Code generation is better than reflect - for ORMs or dependency injection, it gives you compile-time checks and better performance.
- Generics are mostly useful for libraries, not application code - while everyone waited for them, they’re rarely needed in typical service-level code.
- Channels and goroutines can be overused - they add complexity and should only be used when concurrency is actually needed, not as a default approach.
- Go’s error handling is fine for most projects - explicit checks make code easier to read, though built-in stack traces would be helpful.
- Memory optimizations are often premature - micro-optimizations waste time for typical API services where network latency is the bottleneck.
Introduction
In this episode of No Silver Bullet, we share some of our unpopular takes on the Go programming language. After working with Go for eight years on all kinds of projects, we’ve seen many discussions about what idiomatic Go means. We talk about what worked for us, but we keep in mind that different projects have different needs. We question some common Go beliefs and share tips we’ve picked up along the way.
Notes
- Wild Workouts - Our example Go project that shows a more complex application structure
- Watermill - Our Event-driven application library for Go
- HTTP Routers we recommend: Chi and Echo
- Go in One Evening - Our hands-on Training for learning Go quickly
- Clean Architecture episode - Previous episode that goes deeper into project organization
- Go Developer Survey Results - Shows that ~75% of Go developers build API/RPC services
- Google’s Go Style Guide - Many useful ideas, but be careful about being too dogmatic about it
Quotes
Go is simple, but sometimes it will end up as a disaster if you write too simple code for complex applications. You can write simple code, as long as we are building a simple application. But for something more complex, it starts to be more tough.
The common question in the community is ‘how to learn Go the best?’ One common suggestion is to read the standard library because that’s where the idiomatic Go code lives. But the standard library has completely different goals than a web application or API server or CLI application.
The biggest risk about having strong opinions is starting to be dogmatic and hard to be convinced by somebody else. We’ve seen people that were very strongly opinionated, and when you try to convince those people, it was super hard, even if your arguments were pretty logical.
This podcast is called No Silver Bullet because we believe there are no easy answers. To grow as an engineer, you need to keep an open mind. You can’t get stuck on a single fixed point of view because things get updated very fast What works in one context may not work in another.
When you create your application code, you definitely should not start with something generic. When you start with something generic, it will be over-abstracted and much more complicated than it needs to be. After a year, you’ll find out it didn’t need to be generic because you just have one use case.
Universal patterns exist for some reason. Probably someone wanted to solve some issue before. Design patterns are especially problematic - many people have this allergic reaction when you create a strategy pattern in Go and some people might react, “Oh no, this is not Java, please.”
Is structuring your project important? Ask a question: Will it help? Maybe it will not help. Maybe it doesn’t matter.
If the network roundtrip takes 100 milliseconds, why would optimizing the nanoseconds matter? Most of the time it’s a waste of time.
Timestamps
Transcript
Miłosz [0:00]: Do you know what is the best project structure? Should everyone read the standard library? And will error handling in Go ever improve?
Miłosz [0:07]: Go may be a simple programming language, but there’s a never-ending debate about what idiomatic Go means. Today we discuss our takes on some of the most controversial topics. I am Miłosz.
Robert [0:19]: And I’m Robert. And this is No Silver Bullet Live podcast, where we discuss mindful backend engineering. We spent almost 20 years working together across different projects and teams. We have seen Go evolve from its early days to what it is now. We learned that following advice like always do X or never do Y doesn’t work and it can limit your growth. In this show, we share multiple perspectives that will help you to make smart choices and grow into principal engineer level.
Miłosz [0:48]: And depending on your background and experiences, some of the opinions we share might seem as popular or unpopular. and this is fine. We share what worked for us before. So keep in mind, it’s our opinion, but let us know in the comments what you think, what’s your take on the topics and share your questions for the end when we will have Q&A and if you have any controversial opinions of your own, that’s great. We can discuss them together.
Robert [1:24]: Yeah, totally. would love to hear your unpopular opinions and include it to this episode.
Robert [1:31]: And yeah, probably it’s worth starting why this episode is about Go. So if you don’t know us for a longer time, Go is our language of the choice, let’s say. So I think it’s the first language where we stayed for longer and we feel that it’s, we feel productive with that and we’re quite happy about this language. But as every language, our technology it’s not perfect and there are some things that are not that
Miłosz [1:58]: Perfect it’s probably quite common story I’ve heard it a few times before but someone was moving from one language to the other and they stumbled on Go at some point and just stayed and maybe the reason is it’s a nice, get stuff done language so you know you can focus on what’s important and not about and the language specifics or features and so on.
Robert [2:26]: Yeah, but it’s definitely the best one from the worst. If we assume that every language has some downsides. But I think also probably many things that we’ll cover today should apply to other programming languages. So if you’re not writing in Go, don’t leave us, because you may also learn something that maybe can apply to your other programming languages.
Miłosz [2:51]: And we’ve been working with Go for about eight years now on production professionally. So everything we talk about, we base on this experience. And have we seen it all? Probably not. But we’ve seen different kinds of projects and teams and companies. So basically, the rule is simple. If we say that something works for us, It means we’ve seen it work in a few different contexts. But it doesn’t mean it is the same in every other context. This podcast is called No Silver Bullet because we believe there are no easy answers like this. Once again, show your perspective. Maybe there’s something we consider as a good practice and you’ve seen it doesn’t work. So that would be great to hear. Especially to grow as an engineer, I think you need to keep an open mind. You can’t get stuck on a single fixed point of view because things get updated very fast. So what works in one context in one project may not work in another.
Robert [4:13]: Yes, I think it’s important to be open-minded and not be dogmatic because I think the biggest risk about having strong opinions is starting to be dogmatic and hard to be convinced from somebody else because often we’ve seen people that were very, very strongly opinionated about something and when you try to convince those people it was super hard, even if your arguments were pretty logical.
Miłosz [4:40]: And on the other hand, there are people who start writing in Go and they worry that they are not doing it in the idiomatic way, which can be also a trap because it’s hard to tell what’s idiomatic without the full context. So it may be also helpful to hear different perspectives today.
Robert [5:02]: Yeah, and if you would like to, we already covered this topic pretty deeply in, I think it was first episode about low quality code. So please check this one. So we discussed more about this, what’s worth doing
Robert [5:15]: and when it’s worth doing shortcuts. Okay, I think we can start with the first unpopular opinion about Go, and it’s that simplicity is not enough. So one of the selling points of Go is that it’s a simple language, so some people are going pretty far with that and saying that, okay, Go is simple, you should write simple code.
Robert [5:38]: I kind of agree that I think we should write as simple code as possible, but no simpler, because usually it will end up as a disaster. So some examples that we’ve often seen was trying to build some complex applications with some complex domain logic, like a financial application, like a crowd. And okay, maybe it will work for a while, but after adding more and more logic, it will suffer a lot. And it’s not that language specific, I think, because, okay, we’re now talking in context of Go. Again, a lot of people are saying that it’s simple language, but I think it’s something that is happening in every language. So Go have pretty simple syntax, that’s correct, but if you are writing complex logic, it doesn’t matter if you are doing it in Go, in PHP, or Java. But if you approach it in a too simple way, as a crud, it will just start to be overcomplicated.
Miłosz [6:37]: Maybe the reason Go is more prone to this is people come to it from other languages, maybe some very complex projects where someone went too far with patterns and it was all overcomplicated. So when you start to write this simple syntax, it feels great, like a fresh air. So that’s pretty cool. But you can also go too far and try to do it. Trivialize it, write primitive code, let’s say.
Robert [7:10]: Yes, like none of design patterns are useful, just write simple code and it will be simple and nice. And that’s true, as long as we are building simple application. Because for simple application it will totally work. But for something more complex, it’s starting to be more tough.
Miłosz [7:28]: Design patterns are especially problematic. I think many people have this allergic reaction when you create, I don’t know, a strategy pattern in Go and some people might react. Oh no, no, this is not Java, please.
Robert [7:47]: Don’t make Java in Go.
Miłosz [7:51]: You probably don’t want to do it if it’s too early, but if you have an else-if structure that goes to 20 cases, maybe you could refactor it into a pattern that’s easier to read and maintain and so on.
Robert [8:06]: Yeah, totally. I would say, again, it’s important here to not be dogmatic. It’s important to be mindful, let’s say, know those patterns, not say like those patterns are stupid and obsolete. It’s not the case. I mean try to go with simple code, but know what to do if you have some more complicated cases to tackle.
Miłosz [8:27]: The universal patterns exist for some reason. Probably someone wanted to solve some issue before.
Robert [8:34]: And they were doing it like 50 years ago.
Miłosz [8:37]: In a language that’s not so far from on-go looks like right now exactly it’s still the same paradigm i think if you compare java a few years ago we’ll go it probably it’s not that far with syntax so yeah it’s a similar case, Maybe besides patterns, what comes to my mind is constructors. So I know some people also don’t like creating them in Go because, yeah, you can create just a type with proper zero value. And it often works well, sure. But sometimes you want validation. In web applications, very often you want the most entities.
Robert [9:22]: Yeah, and if you’re building, again, simple application like your team, if it’s three people, five people, okay, it’s totally fine. You can live without constructors. You can watch out to not break things. But if your team is 10 people, 20 people, 30 people, it’s starting to be critical because anybody can actually touch your code. And you need to have some way to ensure that this person will not break this code.
Miłosz [9:49]: And this validation will be in some place. The question is, do you want it in one place behind a constructor or do you want it scattered somewhere in your handler?
Robert [9:58]: And this is a good example. I mean, simple code to… People that love simple code may say that no, no, no constructors. And I know that it may sound crazy, but unfortunately it’s what sometimes we’ve seen.
Miłosz [10:15]: So simplicity won’t save you without the proper design. It’s possible to create spaghetti code in Go as well.
Robert [10:26]: Exactly. I think it’s an important point that Go is not different in this case. You can create spaghetti code in every language. Maybe in some languages it’s a bit easier because you can write obscure code. In Go it’s a bit harder to do. But again, you can still have code with very big cyclomatic complexity like City, and it will be also unreadable.
Miłosz [10:54]: Okay my next take is about ways to learn go and it is a common question in the community how to learn go the best and there are many many options but one of the common suggestions i’ve seen is, you should read the standard library because this is where the idiomatic go code lives, And I guess it might be because in the early days, there were not many open source libraries. It was hard to see some real life examples of Go. But I think for learning, it is probably not the best way to learn it. Because the standard library has completely different goals than a web application or API server or CLI application. or whatever you create.
Robert [11:49]: Yeah, probably you can compare it to advice like if you would like to learn driving a car, you should go to Car Factory and learn how to drive a car.
Miłosz [12:00]: Yeah, I would. That’s a good analogy. Unless you want to create a standard library for your own language, that is a great choice.
Robert [12:08]: Or your own car.
Miłosz [12:10]: And there are many great ideas in standard library, of course. So it makes sense to check how something is done. But I think if you are just starting out, it might be confusing because some of the code is not that readable. It’s optimized to be fast, to be generic, to cover all possible use cases. It has to be forward compatible because Go will never break the API, I promise. And, yeah, it’s probably not what you want to learn first. And one example might be that, speaking about constructors, I think the standard library quite rarely uses them, which might be fine because the zero value has some same defaults. For example, the default HTTP client works like this. You don’t need an instructor because the zero value is fine, which is a cool design. But sometimes you need to validate the inputs, basically, and it’s something you want in your application, probably. So I will be careful with this.
Robert [13:31]: I think the thing that you mentioned a bit earlier, the promise of backward compatibility, it’s making it a bit harder. So, if you’re making constructor, it’s harder to add another parameter because it will be a breaking change. So, we know it from the library that we are building Watermill, and sometimes it’s actually pretty tough to avoid some breaking changes. And yeah, if you’re doing a standard constructor, it’s just hard to add one parameter. But if you’re building web services or any kind of web applications, you basically can You can do breaking changes in your internal code because compiler will say that it’s a breaking change, it doesn’t compile, you can update all the code, but it’s not a standard library.
Miłosz [14:20]: So what to do instead? There are tons of resources and books, two of Go, documentation, videos and so on. We are a bit biased because we like learning hands-on, which we mentioned in the previous episode. So if you want to learn or check it.
Miłosz [14:38]: We also have the going one evening training where you can use hands-on approach to learn by writing code.
Robert [14:47]: And we know that a lot of people don’t believe that Go, you can learn Go in an evening, no way. But yeah, you can do that.
Miłosz [14:54]: Yeah, but if you like to just read instead, there are tons of good resources. And there are many, many open source projects right now that you can take a look at how they work.
Robert [15:04]: We actually have R1s also, Wild Workouts. It’s a pretty popular Y, I think, on GitHub. I don’t know how many stars it has now, like 5,000, 6,000, something like that. Also worth checking because you can learn from a project that looks like a project that you will do in your job probably as long as you’re not building a language or libraries and you can just see how to do it properly and not learn from your mistakes but from our
Miłosz [15:36]: Staying with standard library there is this opinion that the standard HTTP package which NetHttp is quite powerful and good to go compared to other languages, which I agree with. But I would also add that it is not that great to be used by default in most projects when you start. Because the API is not that great to work with compared to router libraries, and it doesn’t give you much to stick to the defaults, unless you care about dependencies size or something like this. So what we like the most is either GoChi or Echo, which gives you a very lightweight router out of the box. And Qi is compatible with the standard library. Echo is not, but gives you more sane error handling.
Robert [16:41]: It’s actually pretty compatible. I mean, it’s not using totally different types. You still have access to the standard library types. Yes, the HTTP handlers are not fully compatible, but you can have some magic adapters, let’s say.
Miłosz [16:54]: Yeah, so in the very beginning, we stick to 3 because we wanted to be compatible with the standard library for some reason. I’m not sure why. Probably because it felt idiomatic to do it. And then we switched to echo because the error handling was promising.
Robert [17:11]: In other words, I think it’s pretty cool that you can return error from HTTP handlers. Because from a practical point of view, it’s a very common thing that you’re returning error from your HTTP handler. And it’s just easy to mess it out if you not return from HTTP handler. So if you have checking if the error happened, but you will forget to return. It happened probably to any person and it’s not perfect.
Miłosz [17:37]: The error handling gets verbose otherwise. And I think once we try to do it ourselves, like create our own wrapper on top of G to have this error handling. But Echo does it for you, so that’s pretty cool. And maybe Barton to mention that those are just routing libraries, not full frameworks as some other. And we prefer it this way because it’s easier to replace or doesn’t interfere with the rest of your code. Just HTTP handlers.
Robert [18:09]: So we covered it a bit in the episode about frameworks. But TLDR is that you can pretty easily replace that. So if you decide that, okay, I don’t like Qi or I don’t like Echo. You can switch to any of this and it will take you probably less than a day for a pretty big project. So that’s cool. And I will be not afraid to be vendor locked in.
Miłosz [18:32]: Unless you couple it with your application. Hopefully not.
Robert [18:36]: Yeah, but you can also do it with the standard library. You spend some time.
Robert [18:42]: Alright, so next unpopular opinion from me is using the optional pattern for configuring things. So you know those with XYZ configuration instead of using struct. And there are a couple of reasons why we don’t like this pattern. So the first reason is it’s hard to find what options you can pass. So you need to go to file and…
Miłosz [19:07]: Sometimes it has the same package as the constructor. Sometimes it’s a completely different one called options so you need to look it up sometimes.
Robert [19:15]: You have two packages for example in cmp library for comparing stuff you have two packages where options are stored
Miłosz [19:24]: Many libraries also use options package for keeping those options which is cool but we have a big project with many dependencies and in your ide when you type options it gives you 10 different libraries.
Robert [19:40]: Yes, and I actually don’t have a good idea why this pattern is so popular.
Miłosz [19:45]: Yeah, I’m the same. I think I’m missing something. Why is it that much used?
Robert [19:52]: But long story short, what we are doing in, for example, Watermill, we are just using structs because it’s super simple. So you are just navigating to the struct. You see all the options that you can pass. You can add a method for set defaults, for example, so you can see what are the defaults. That’s it. It’s deadly simple, just works great. I’m not sure why it’s not the standard. I hope it will be, but…
Miłosz [20:16]: It’s easy to find the documentation. You just jump to the structure. You have all the fields there. What I also like to do is an IDE. You can have this command, fill all. So you just create empty struct, fill all and delete what you don’t need. Exactly. It’s very easy to configure then. and zero values can have the same defaults anyway. So it’s not that different from using the with options. It’s also easier to conditionally change some configuration. For example, if you have some input parameter, depending on which you want to use different options or not. If you use the options, you need to create a slice of options first, and then append to it. And with config, you just set the value and that’s it. And so if you’re just some fun of using this pattern in the chat, please let us know. What are we missing? Because maybe there’s some use cases that we didn’t see yet.
Robert [21:19]: I think we can just recommend using struct because again in Watermill we are using it with zero problems, zero backward compatibility issues. I think standard library is also not using this optional configuration pattern.
Miłosz [21:34]: A good example where simpler is actually the simpler option. I mean, when it’s useful to use a simpler one, just a struct.
Robert [21:44]: Yep, definitely. All right, so the next unpopular opinion is about structuring your project. And I think…
Miłosz [21:52]: Oh, most common discussion.
Robert [21:55]: Yeah, it’s a controversial thing. So I think… It’s the thing that structuring your project is the thing that a lot of people are often very dogmatic and blindly following some patterns that they don’t have idea why they are doing that. So I have one good example, the Golang standards project layout that a lot of people are following.
Miłosz [22:22]: Renamed.
Robert [22:24]: Yeah, yeah. And it’s quite funny because a lot of people are following that thinking that it’s official guideline, but it’s not. If you go to issues, there is one closed issue that Golang by trainers are saying that it’s not official. Maybe you should rename it or something because it’s misleading so this is one thing that i often found the other thing that is sometimes controversial is about using some more complex structure like clean architecture or hexagonal architecture because many people are saying no no it’s making java from go we touched it very deeply in the previous episode, I think, about architecture.
Miłosz [23:10]: Two episodes ago, yeah.
Robert [23:12]: OK. If you are interested, you can just search for it.
Miłosz [23:17]: And this is also a common question. I think maybe because of this empty page syndrome, when you start a project, you have nothing in your editor. So you feel like you need some foundation in place to start. And many people try to find the best way to start the project, to organize it. And you don’t really need to do it yet before you know how the project will look like in the future. So we recommend starting small and not sticking to any particular project structure if you don’t need it yet. Because it’s easy to go too far and overcomplicate it before it’s already needed. There are some nice, maybe not patterns, but some tips people use, like the cmd package for keeping all the binaries. I think that one is pretty cool, because it’s very easy to find the entry points to your application. Unless you create a library, maybe. I would probably use it in most projects.
Robert [24:28]: Yeah, it’s also good to remember that you can use internal package, So the package that you can only import from the same or deeper level of your application. So if you are building a library or some services that are shared in the codebase, for example, you have modular or monolith, it’s pretty useful to use this package.
Miłosz [24:49]: Some people call it PKG or SRC. I don’t really care that much, but I like to have this separate directory in the project, Because very often you have other stuff like Docker or Dev or Tools or API definitions. Basically some other files that are no Go files in your project.
Robert [25:16]: Yeah, and it’s starting to be pretty messy if you’re just keeping each of the directory.
Miłosz [25:20]: Yeah, especially for someone who is just joining the project, it might be very hard for them to understand where is the Go code, There is some helpers, some not ready to go files. So whatever you call this internal package, I think is a pretty cool idea. And it helps to find your way around the project. Also depending on the project type it might be a bit different right for applications for cli apps for libraries you probably will have a bit different structure, so it doesn’t make sense to stick to one everywhere you don’t need to look for one perfect solution and.
Robert [26:06]: I think it’s probably also not that important that as people are
Miłosz [26:10]: Exactly going.
Robert [26:12]: Into very deep discussions i mean it’s just structure it just directories you can change it pretty quickly that’s it
Miłosz [26:23]: But there is one package structure i dislike and it is the flood structure and, maybe it is also because of how the standard library looks like So I’ve seen many projects use one level of packages, and it’s supposed to be simple. But I think this is exactly this issue where trying to be simple tends to be something that’s hard to grasp. And I recall projects that have the same level, have packages like app, user models, database, handler, all kinds of names. And all those packages import each other in different configurations. So it’s quite hard to understand what’s going on. And again, especially for a newcomer, you just land on this project and you have to basically read it end-to-end to understand what’s going on. So that’s one structure I would not recommend, probably.
Robert [27:41]: And yeah, I remember that there are some people thinking that, okay, if you’re, I mean, that project structure can work for really simple services, and it’s fine. If you have really, really simple service, and it for some reason needs to be a separate service, it’s fine. But I think it can also lead to some kind of pathology when if you see that, okay, my service is too complicated to be in one package, I need to split another microservice. And it’s a trap because you start to have really granular microservices and will start to have more microservices than people in the team.
Miłosz [28:20]: Jim in the chat mentions that maybe we should read Robert Martin’s packaging principles. So I think this is clean architecture, basically what we covered in the previous episode. And yeah, this is what we usually suggest for more complex applications. And in short, it’s just grouping the packages in a few layers. So it’s easier to understand where’s your logic code, where’s your database implementation, and where’s your HTTP handler. And even if you look at standard library, there are some grouping packages like encoding for JSON and other encoders and HTTP in net. So it’s a similar idea.
Robert [29:04]: And I know that people are sometimes thinking that, okay, it’s actually not like that in standard library. In standard library, you have package per one thing. But yeah, I agree. that it’s totally compatible and it can be idiomatic in Go. It’s just about not going too far.
Miłosz [29:27]: Another package that’s often called unnecessary or forbidden is the common package. Which is an interesting idea because it’s hard to imagine a project where there isn’t common code. So I guess the standard recommendation here is to just move your code from common to where it belongs. Sometimes you can but sometimes it doesn’t make much sense and I think it’s pretty fine to keep this common package for stuff that don’t belong anywhere and I don’t remember it being a problem except for where you keep your entities and logic there that is pretty much it’s.
Robert [30:15]: Just you know this hard discussion what domain logic is and if it should be there or not. But I think what’s pretty useful here is thinking in terms like if you are building your common package, is it something you could publish and other people can use in totally different projects?
Miłosz [30:38]: Yeah, that’s a good idea.
Robert [30:39]: Yeah, this filter can be pretty useful for filtering out things that are your business logic of your application and are not. And sometimes also some people are recommending that instead of creating some shared code, you can copy it. And okay, sometimes it works, but it doesn’t work for things that should change together. Because there is sometimes some logic that should always work the same, and it will be not a problem when it will be replicated in multiple places. But sometimes you have common logic that you need to be sure that it’s working the same way across entire codebase. And if you forget to update it in other places, you may have a problem.
Miłosz [31:26]: Yeah, so maybe the issue here is not having the common package in itself, but deciding what goes there. If you just put too much in it and too many domain-rated stuff, then it becomes a problem. Similarly, you will create a common library that all services use. So I prefer to use the smaller libraries. And similarly in a package, you don’t need to put everything inside the common package directly. You can use sub packages. But I think this grouping is quite handy.
Robert [31:59]: And there is a related question. So if you have considered packaging by feature, not a technology layer. So I would say that it’s a different layer, it’s a different level of packaging. So usually in a more complex project, we have, let’s say, two levels of modularization. So one is by, let’s say, technology or layer that you have like application, interfaces, adapters, etc. And above that, you have like users, orders, okay, it’s probably a very bad example.
Miłosz [32:32]: More like modules, maybe?
Robert [32:34]: Yeah, yeah.
Miłosz [32:34]: I’m not sure if this is not about vertical slice architecture where you slice the project by each feature. Which I tried to play with it i didn’t come up with anything that i liked yet so I can’t really tell if it’s a great idea maybe in the future but.
Robert [32:57]: Uh you know also separating by um so i said that you know users orders etc it’s basically example because it’s actually in practice it often doesn’t work.
Miłosz [33:09]: It sounds too easy, too naive.
Robert [33:12]: Unfortunately, but I think the proper modularization is really close to separating by feature. You should separate your project by independent things that you can modify to add some functionality. So for example, you are modifying some functionality, you should not need to modify 10 modules for example for that. You should be able to do it in one module and it means that your modularization is proper. So I think that it can actually be close to that. Unfortunately, often it’s not the case. But from the other side, you don’t need to also have very granular modularization for simpler projects because it can just overcomplicate stuff. And I think I’ve… Okay, it’s hard to say because I’ve seen multiple projects where they’re too granular or they were… Well, they could be more granular because it was just building with the time with more features in one module. So, unfortunately, it’s sometimes hard to get balance here. Well, yeah, I think it’s something around that. So even if you have modules, probably it should be pretty close to features.
Miłosz [34:19]: Yeah, probably. I will try vertical slides at some point.
Robert [34:24]: Easier to say than do. Unfortunately.
Robert [34:29]: Alright, so time to next Antipolar Opinion, if you don’t have anything to add to the previous one.
Miłosz [34:35]: Let’s go.
Robert [34:36]: So, if you are with us for a longer time, you may know that we are not big fans of mocking libraries in Go. And there are multiple reasons for that. The first reason is that most of them, or I would say all of them that we tried are just hard to use because of a lot of empty interfaces, a lot of reflection. So because of that it’s pretty hard to work with that because you can basically pass anything to the assert or to the implementation and it’s really hard to debug that. And our solution for that is quite simple and I know that it’s a bit controversial, but our solution for that is actually writing stops by hand. And some people are saying that, but okay, it will be super hard, sometimes interfaces are super big, it will take ages to write those stops. I think I would think about that in another direction. Like if you have big interfaces, it’s not a problem of your stops. It’s a problem of your big interfaces.
Miłosz [35:39]: Small interfaces make it much easier.
Robert [35:41]: Yeah, yeah. And we already in some episodes were covering that it’s a similar problem with DI. So if your DI is complicated, it’s not a problem of your DI. It’s a problem of your dependency graph, basically. So the similar situation is with mocks. and okay sometimes in some projects where you are adding some tests it’s just hard to write those stuff by hand okay it it may be fine to write the use some library that can generate it for you it’s quite fine but i would recommend to try to write them by hand and i know that for some people it’s not obvious but in this case you can just keep this stub implementation next to the real implementation so thanks to that you don’t need to copy this implementation over multiple places so it doesn’t require much more effort to use that than using real mocks and at the end it’s giving you much more flexibility and it’s much easier to debug and that’s nice and
Miłosz [36:47]: I think it also encourages you to test your your code and not not the mocks What I don’t like about mocking is that many libraries give you those methods that you can test what method has been called and with what arguments and so on. And I think it kind of misses the point of testing because you don’t really care what some method, what some repository or client has been called with. You care about the end result. So I like to design those fakes like in-memory repository. That works kind of similar to the original one. It’s much simpler. And what’s nice about it is you can replace the implementation and then run the tests on a very high level and everything should work as a production. And you don’t need to care about what has been called, what hasn’t been called. If the end result is correct, then it’s fine.
Robert [37:47]: Yeah, it’s the worst if you are changing some logic and you are running your unit tests and everything explodes because you called one method that were not called earlier and you need to fix 100 tests just because you called something extra. It’s a disaster. And it’s even worse if you’re using your mocking library in the wrong way and it’s not thread safe. So if you’re running some tests in parallel, it explodes and you have no idea which test is failing. So yeah, if you wrote your stubs, well, the problem doesn’t exist. And if you are looking for some inspiration how to do that, so on our blog you can check article named the Go libraries that never failed us, 22 libraries you need to know. I know that it doesn’t sound like it covers how you should write stubs, but it’s actually There’s one example there that you don’t need HF library for mocking.
Robert [38:51]: Okay, another thing that I think it’s sometimes a bit controversial, especially for some people that are newcomers to the language. So this is the fact that in Go code generation is pretty popular, but I think it works pretty well. If you will take example of ORMs or dependency injection libraries. So it’s similar case like with mocks. So if you are not using code generations in many things like your ORM, you end up with a lot of empty interfaces. You will have no compile type check format.
Miłosz [39:32]: You have no strong types in a language that has static types, so you lose some of the benefits of using Go in the first place.
Robert [39:39]: Exactly. And for ORMs, it has also one important thing that you probably care about performance pretty much. And if your ORM is pretty heavy on reflection, it will just slow. You cannot overcome that. And if it’s generated, it’s just static and it’s fast. So it’s quite cool.
Miłosz [39:58]: Also easier to understand. If you really like to know how it works under the hood, you can open the generated file and very often it’s quite readable.
Robert [40:08]: Yeah. And a good example is dependency ejection. So for example, we quite like wire. So in our projects, we don’t use a library for dependency injection. We are just doing it by hand. But if you really need to use any of it, we recommend wire because it’s code generation based and you can just go to the generated file and see how everything is injected. So debugging of that is pretty simple. With reflection, good luck. No way.
Miłosz [40:34]: Yeah, it’s quite hard to understand how Reflect works, especially in a library you don’t know. So it’s quite hard to debug. And one other thing, libraries using reflect tend to overuse i’ve seen is struct tags so struct tags are pretty cool idea for marshalling it doesn’t get in your way and you know it’s easy to specify, but for other things i think it’s questionable because it gives you this this magic, behavior that’s not compile time checked one example is validation libraries i’ve seen some quite popular ones that let you specify so many things there, like how long you expect a string to be. Basically, if you look at the syntax they let you specify in a strike tag, it’s like a completely different language.
Robert [41:34]: I think when the fun starts, when you are using this kind of library and you are just not checking how long the string should be, It’s a fun start begins when you are checking something if some field is set.
Miłosz [41:48]: Yes, they also let you do it. So you need to write tests for it, which you probably want to do anyway. But I think if you use Go for its static types and compilation and all this good stuff, It kind of misses the point to overuse Reflect like this. And ORMs are similar with how they let you specify field types and relations in struct tags and so on. Feels like a cool idea because you have this one model that describes everything, but it can be overused. One of the things I like Go for is that it’s easy to read. You can take a look at the code and you understand what’s going on. There is no magic behind the scenes like in dynamic languages, where a function call can trigger something you don’t understand because there’s some decorator used somewhere or something like that.
Robert [42:57]: Yeah i have ptsd when you are saying about it
Miłosz [43:00]: Yeah i remember such stuff from python um or.
Robert [43:05]: Php you remember magic methods
Miłosz [43:07]: Yeah so yeah so if you use go i would stick to the, classic code as much as you can and avoid to reflect entirely unless you really want to do it. I remember one project when we tried to create our custom tags and be smart and be smart and avoid writing code by hand and yeah it wasn’t a good idea in the end, because just digging through the reflection code is not fun at all.
Robert [43:43]: Yeah so alternative if just write those ifs. It’s not that hard. Maybe it looks super explicit, but it’s not bad.
Miłosz [43:53]: Or generate your code, which is also quite easy to do with Go.
Robert [43:57]: But probably not for validation. I think for validation, I think it’s just a matter of a couple ifs that, again, it will look boring, but don’t be afraid of boring code. Boring code is not something bad it’s something good actually
Miłosz [44:13]: Yeah and with you know with good ideas and with things like copilot becomes much easier to to write boilerplate so it’s one less argument it shouldn’t take you much time to do it anyway definitely all.
Robert [44:31]: Right another thing that i hope it will be controversial because you may remember that one of the most anticipated features on Go were generics.
Robert [44:41]: And you’re here. We have generics for, I don’t remember how many years.
Miłosz [44:47]: Two years, probably.
Robert [44:48]: Yeah, and well, how often you are using generics in your service code? I don’t remember when I last did that.
Miłosz [44:59]: Yeah, not much. For some for some more internal libraries. I’ve used them. We use them in Watermill for CQRS handlers. We also use them in event sourcing library. That was cool. But I think for service level code, Probably not that much.
Robert [45:23]: And I think the secret may be in the name. Generics. So like the name states, it’s for some things that are generic. So for example, libraries that we mentioned. But usually when you are creating your application code, you definitely should not start with something generic. Because often when you start with something generic, it will be over abstracted and it will be just much more complicated at the end than it needs to be. And probably after a year you will find out that it didn’t need to be generic because you just have one use case that works in exactly one way.
Robert [45:58]: And even if you have two cases just copying this part of code and not having it generic it will be much much easier and it will make your code much nicer. But for libraries definitely it’s useful but I wouldn’t say that it’s that big game changer compared to how much people wanted to have generics in Go. And yeah it’s cool that we have it because the secure component in WaterMule, it’s cool because it’s making the syntax much nicer. I love it. But I would say that I don’t see that many more cases when it helps. And I’ve seen a lot of cases when it just makes code unreadable. Really, if you know some generic quirks, you can do such ugly code that nobody will have an idea how it works and how to make it compiling. It’s sometimes like when I play with TypeScript. I mean, I have no idea how to make it compiled, so I need to use compiled or whatever. But I’ve seen such kind of code with GoGenerics sometimes, so watch out for that.
Miłosz [47:03]: So it’s a similar rabbit hole as Reflect. It can be useful, but it can be overdone. So you can find yourself trying for a few days to make it work instead of just creating something straightforward, but for stuff where you want, It’s something universal, that might be a good idea. I remember one thing on the service level we had was decorators for application level commands. So we had stuff like metrics, logging and tracing decorator. And it actually worked pretty cool with generics.
Robert [47:40]: Yeah, but it’s also something that we’re able to do with code generation. True that it was a bit tough because if you are doing your own code generation, Unfortunately, it’s a bit hard to do, because there is some tooling, but it’s not that great, so…
Miłosz [47:58]: Yeah. One more feature that’s not always needed and can be overused are Tunnels and GoRoutines, which are one more great part of Go that everyone loves, I guess.
Miłosz [48:15]: And for running concurrent code, yeah, that’s a great idea. But sometimes I’ve seen projects where they were used for some unknown reason. Like instead of calling a function, you would have some worker in the background that would communicate with channels with some other function. And it may sound like a cool design, but remember that it also has lots of complexity, especially for stuff like synchronizing routines, closing channels, accessing some common memory and so on. So it can be also painful. And also the concurrent code is not always faster than the segmental code.
Robert [49:05]: Yeah remember that running a routine it also does some allocations
Miłosz [49:11]: And if your code is CPU-bound then you are limited by the cores anyway so it might not be the best idea.
Robert [49:18]: But i think it’s similar case like with generics so when you are building libraries channels may be pretty cool to simplify your life and build pretty complex stuff. But in typical application code, maybe it’s not that often useful. Sometimes it is, but often you can use, for example, error groups to do some synchronization. It also works nicely.
Miłosz [49:44]: And it has more high-level API. It’s harder to misuse. And with channels, it’s easy to have some unbuffered channel that makes you stuck at some point because no one is reading from it and so on. So you need more tests, you need to be more careful with design overall.
Robert [50:07]: That’s true, but it can simplify live with libraries. like an example of WaterMule. So I think it’s a nice example because we’ve been able to use a lot of patterns from Go that make implementation much easier. But it’s a library. I think we should do an episode about WaterMule.
Miłosz [50:24]: Let’s make it the next one.
Robert [50:26]: Sounds like a good idea. And one more thing about channels is that if you are using it in your application, you need to also remember that they are not persistent. So, for example, if you are triggering some workflow from your HTTP handler to channels and your service may die, for example, now you’re using Kubernetes, you never know. The service may die in the middle of processing your request. Your operation will be just lost. So remember that channels are nice, as long as losing this operation because it’s in memory will be not a problem. And if it will be a problem, remember that something like Message Broker exists, like PubSub, like Redis,
Miłosz [51:12]: RabbitMQ, Kafka. Or cloud-based PubSub. I often see people ask about Watermill and the GoChannel PubSub we have. So we have this PubSub implemented in a water mill where it communicates through Go channels, so we don’t need a separate infrastructure. And it seems like a good idea because you can just have it running with no other infra. But it’s also quite limited. It’s not the same as most PubSub are. We use it mostly for testing purposes or stuff like that. It might be tricky if you try to use it for some production-grade system, because there are some… Persistence is probably important, or another thing that comes to my mind is buffering. So if you use unbuffered channels or the buffer overflows, then your handler will hang. There’s many complexities you have to think about.
Robert [52:17]: Yeah, and if you are not building that big application, like you are building a smaller application and you have persistent file system, you can also consider PubSub based on SQLite, because we will very soon have it in Watermill. Thanks, Dima, for implementing that. And I think it will be a pretty cool alternative for channels, because you can persist your PubSub with SQLite, that will be super fast, and you can just store it in your file system. And it’s a nice alternative. If you are going into a more complex system, just use some production grade.
Miłosz [52:58]: Or use SQL-based PubSub, if you already have the database, you probably have.
Robert [53:02]: If you’re in the middle, let’s say, of your pop-up journey.
Miłosz [53:08]: Okay, my next one is will be either unpopular or super popular, depending on who you are. So my take is that error handling is completely fine. In go. The checks are boring to write, but they are great for reading the code, because you know what’s happening. And you can just use a compiler ID shortcuts to generate them. But one thing we miss is stack traces by default. That would be cool.
Robert [53:43]: That’s true. Because you always have this code that has no raps, and you’re later spending minutes, hopefully not hours, to try to find from where this error went to where it went. It’s tricky part. for stackraces, well, it could be helpful here.
Miłosz [54:02]: Yeah, I wonder if the next proposal will make it to the spec, because I’ve seen there’s another one for using some syntactic sugar. So, let’s see.
Robert [54:16]: Okay, in general, for example, I’m super happy about the error handling in Go because it’s explicit. It’s not like with exception, you’re quite not sure what can be drawn from the bottom of the stack. And in Go, you’re sure that, okay, this returns error. Should I do something with that? Shouldn’t I do something with that? And it’s super strict, and I think it’s making implementations much more robust.
Miłosz [54:43]: Yeah people complain about handling the errors but the other side is if you have function that returns no errors that’s actually super cool because you know it is you know pure function, and it doesn’t have side effects probably.
Robert [54:59]: Or for example have some function that you’d know that you don’t care that it returns error so for example sometimes you have cases when you are converting string to boolean but for example you don’t care if it’s drawing error if it’s just false. Okay, it’s false. Whatever, I’m just ignoring this error. But you’re doing it explicitly. With exceptions, it will be a big chance that some exception will be thrown. It will go up to the stack to your handler and it will throw to person 500 just because you were not able to parse bullying.
Miłosz [55:28]: Like a panic. What do you think about panics?
Robert [55:31]: I love panics. So I know that a lot of people are like, don’t panic. Yes, yes, I know. But I think it’s not that easy. So there are many cases when you shouldn’t panic. You definitely shouldn’t panic in goroutine because i’m not sure if all of you know but if you’ll panic in your goroutine and you will not recover it your entire process will exit with non-zero code not the best thing in http servers not gracefully
Miłosz [55:57]: Shut down but just exit.
Robert [55:59]: Yeah yeah so it’s if you are using channels for your asynchronous operations it will be lost so it’s not the best thing. In general, if you’re writing libraries, for this reason, probably you should watch out when you’re panicking because you can just accidentally do this favor of killing service for somebody. And it’s also pretty hard to debug. So not bad. But there are some cases when I like to use panic. So obviously in some dirty code, it’s nice. So for example, in my IDE, I have shortcuts to do if error is not nil, panic. It’s pretty useful. But there’s one case when I really like panics and it’s a production code and it’s related to dependency injection. So if you’re injecting things and when running some constructor in your main, return some error. And I think it’s totally fine to panic in this case because it’s a non-recoverable situation. So if you’re not able to inject dependency, your service will not work. So it doesn’t matter what you will do at the end. And panic is nice because it will give you the stack trace and it’s making it a bit shorter. And I think in general, this is the situation when panics are good. So when you have some non-recoverable situation, basically.
Miłosz [57:19]: Yeah, sometimes also for a better API. We have a function that’s very often called and handling the error there would be annoying. And there’s just one error you need to check inside it, but it’s basically a sanity check. It never happens, but just for completeness, you need to do this entity check, then I think it’s pretty fine to…
Robert [57:48]: I no longer make errors like it should not happen because it always happens.
Miłosz [57:54]: What comes to mind from our project is about getting some config value inside the function.
Robert [58:03]: Yeah, and without this config, it will totally not work.
Miłosz [58:06]: So in theory, the config library should handle this for you, but you never know. So why not just throw a panic in this case instead of making your API harder to use in this case?
Robert [58:20]: Yeah, exactly.
Miłosz [58:22]: But it’s an exception, not a rule.
Robert [58:26]: Always having exceptions in Go. Yeah, but again, I think what’s important here, again, not be dogmatic, because I think a lot of people are dogmatic that don’t panic, never ever panics are evil. But I think it’s not like that. There are some cases when it’s fine but just be mindful when you are doing it.
Miłosz [58:48]: And remember about recovers on many levels.
Robert [58:52]: Yeah, definitely.
Miłosz [58:56]: Cool. My next one is about memory optimizations or optimizations in general. So So, I fancy discussions like this, like knowing what makes more sense, what’s a better trick. One example is struct field ordering. So if you order fields in your struct in a certain way, then they fit better into memory and it takes less memory allocation.
Robert [59:26]: And you feel so smart. I just ordered my struct field to use a bit less of memory.
Miłosz [59:33]: Yeah, and there are many such tiny improvements you can make, like pre-allocating slices instead of appending in a loop, let’s say, if you copy a slice. Or using a buffer builder instead of appending to a string. like our, yeah, my other ideas. No. Oh, yeah, I know. The string with map that maps to an empty struct instead of a boolean.
Robert [1:00:10]: I actually remember one more. So concatenation of strings with this function. I don’t remember the name. But to not concatenate strings with plus, but instead using this…
Miłosz [1:00:23]: The builder. Yeah. And, you know, GoTest has this benchmark feature that lets you write two functions and compare them and you can easily see, like, wow, this is five times faster. But, you know, if you take a look at the Go survey, I think 75% of people write API services in Go or web servers or something similar.
Robert [1:00:50]: And what’s the bottleneck in this kind of servers?
Miłosz [1:00:53]: Yeah. So, you know, if the network roundtrip takes 100 milliseconds or 50 or something like that.
Robert [1:01:01]: Let’s take one millisecond even.
Miłosz [1:01:04]: Yeah. So why would optimizing the nanoseconds matter in this case? So most of the time it’s a waste of time. And of course, a huge disclaimer depends on what your application does. If you write some, I don’t know.
Robert [1:01:20]: CPU intensive or memory intensive application.
Miłosz [1:01:23]: It’s tough for microcontrollers or whatever, of course, it matters in many cases. But I’m talking about when you create an HTTP handler and then you spend time discussing this in a PR, whether this should be pre-allocated, sliced or not. And it’s probably a waste of time.
Robert [1:01:41]: Or maybe you are also writing a service that is handling really, really huge traffic and your servers’ costs are important, it may be also the case. Because sometimes it’s the case. But again, from our experience, it’s a smaller percent.
Miłosz [1:02:02]: But the difference is you optimize once you feel the pain. Instead of trying to predict that one year later we will have such huge scale and that we will need this optimization.
Robert [1:02:12]: And I think it’s useful to actually use this benchmark in opposite ways. So if somebody is suggesting you like, oh, no, you’re wasting such much of memory or CPU. You can use benchmark and show that it’s just that small difference and compared to, for example, SQL query, it’s like nothing.
Miłosz [1:02:34]: Yeah, maybe just add an index in your table and it will be much, much faster.
Robert [1:02:39]: Undo one less SQL query. Or maybe if you’re calling some external service, maybe do it in Go routine if you can. and just do things in parallel. It will give you probably a couple orders of magnitude more and then optimizing some allocations.
Miłosz [1:03:01]: A similar concern is about garbage collection often. And I’ve seen it most often in regard to games. Like this opinion that you can’t make games in Go because of garbage collection. But chances are you’re not creating a game or even matter. And it turns out it’s actually pretty fine. There are many games created in Go that are all fine. And most of the time you don’t need to care about it that much. Unless, of course, you build something that’s very specialized. But I’m talking about a very common scenario of creating API or HTTP servers.
Robert [1:03:39]: So again, be mindful. Just not apply some dogmas blindly.
Miłosz [1:03:48]: Yep.
Robert [1:03:52]: So maybe one more thing about testing again. So, the BDD style testing libraries are not helpful, or I would say even more, they are harmful, for multiple reasons. So the first reason, I think all of them are not compatible with standard libraries. So they are kind of using some testing from standard libraries, but in general they are not compatible. So you need to learn some new tools, how it works, and your assets will be not compatible. The new people in the team will need to learn something new, and it will be just less readable.
Miłosz [1:04:31]: They have nice outputs, though. That’s a selling point most of the time, I think.
Robert [1:04:37]: That’s true. But I think one of the biggest arguments for using this kind of frameworks is that it’s making a nice structure of your tests and I agree. I think it’s nice that you have this given, when, then, and it’s nicely separated. But from the other side, you can achieve the exactly same concept in standard library. Because nobody is saying to you that you cannot split every of your tests to three parts. With given, when you are doing some prerequisites, in when you’re doing some operation and at the end you’re ensuring that everything, what should happen. And I think it’s, I’m trying to do that because it’s making tests much more readable. But again, I don’t need to have special library for that. I can just do it with the standard library testing. So I think the most important thing is mindset. And it may be actually a good idea to maybe try to use this kind of libraries to try to understand this mindset. But I would avoid to use those kind of libraries in your services.
Miłosz [1:05:46]: The big promise is using Garukin syntax to collaborate with your product owner or maybe some stakeholders or whoever. Yeah. We tried it before.
Robert [1:05:59]: Yeah.
Miłosz [1:05:59]: But we never made it to work really. So the idea is that you can create this framework for someone non-technical, I guess, to create the test cases for you in this given window scenario. And then you can just run those tests.
Miłosz [1:06:19]: But I have never seen it work out in practice. Usually it’s developers who write those scenarios anyway.
Robert [1:06:27]: Yeah, and you’re just wasting a lot of time on doing the mapping because you need to have parsers for this text, you need to do a lot of mapping. And at the end, when developers are adding those I would say now three times more time because you need to support that many things. Again, you can have very similar concepts in your standard Go library code tests. And again, maybe somebody like your product owner will be not able to edit that, or probably your product owner will anyway not write this. And if you follow those principles, you can even sit with your product person and if you did a good job with writing nice helpers, it can be even readable with your help to this person. So I think this actually worked for us a couple of times, so you can see with this.
Miłosz [1:07:20]: If it reads like in English, you can probably just show it to someone who’s not technical and they will be able to read it and suggest what’s missing.
Robert [1:07:29]: So again, I recommend to play with that, to maybe understand those principles and see how it can look like and give you some inspiration. But from our experience, it never fulfilled both promises that it gave.
Miłosz [1:07:45]: It was usually easy to start. And I think most examples in Garek can show you this very promising idea. You have this table with quite simple use case. We have something like, when I do this and do this, then something happens. But once you try to do something more complex with it, you need to add more cases and it starts to be kind of bloated. Hard to understand.
Robert [1:08:15]: Yeah, because Gherkin works nicely for some really high level tests, like end-to-end tests. But often it’s not enough to have some HTML selectors in Gherkin because you need to match the element that you need to click to do something. And it doesn’t make sense at the end. So you just
Miłosz [1:08:31]: Keep switching between the scenario and the test code to make it work together instead of just doing it in your tests.
Robert [1:08:40]: And parsing is harder and harder when you need to use some special characters.
Miłosz [1:08:46]: Yeah, well, maybe it’s worth to take a look and try to at least to inspire yourself to write tests in this given one then approach. That’s actually a pretty good idea.
Robert [1:08:57]: Yeah, and I think I would be happy to see it much more often because it’s actually not as often used as it could be.
Miłosz [1:09:09]: Okay, maybe the same with testing libraries. I see a few people quoting Google’s style guide which mentions that you shouldn’t use assertion libraries. But we actually like Testify for asserts and it’s pretty easy to use, doesn’t get in your way. Most it does what you want.
Robert [1:09:34]: And I think we can probably also say that it’s industry standard. In most companies it’s just used, in many libraries it’s also used.
Miłosz [1:09:46]: Yeah, and that’s the thing about following those style guides or official guidelines. I think they are helpful, there are lots of useful ideas there, but it’s also easy to take them too seriously and dogmatic. And you need to remember you are not Google. And the best example probably is how Go started with GoPath, which worked well for Google because they used the monorepo and so on but for everyone else probably almost it was painful and weird to work with Dependency management?
Robert [1:10:20]: Why you need that? This is everything on our monorepo
Miłosz [1:10:24]: It was this vendor, yeah I’ve never seen why using Testify would be a problem it gives you nice outputs mostly out of the box you can add some extra context if it’s missing.
Robert [1:10:43]: I think of an argument is that it’s implicit, you need to learn how it works under the hood and I agree but from other side you will do it once But for something like equals,
Miłosz [1:10:55]: We don’t really need to understand how it works.
Robert [1:10:57]: It’s just equals, basically. And you have to remember that sometimes you need to use equal values, for example, if you’re not asserting exact types, for example.
Miłosz [1:11:06]: Yes, there are some caveats, but maybe it’s one library where actually Reflect is fine. But it’s a testing library, so that’s a bit different.
Robert [1:11:16]: It’s a library.
Miłosz [1:11:18]: Yeah, you don’t need to care about compile time checks in your tests because it’s just testing your code.
Robert [1:11:24]: That’s true, that’s true. So again, I think it’s good to have exceptions from…
Miłosz [1:11:31]: Guidelines, yeah, and maybe make it work for your project, your team. And not be dogmatic. Yeah, agree on something together, which is also a nice idea to have some internal guidelines instead of just committing 100% to something external that maybe is generic and works fine in many contexts, but for your specific use case, maybe not.
Robert [1:11:57]: And I think that there is one thing in Testify that I don’t like. So those are testutes. I’m not sure if it’s still valid, but last time when I tried to use that, testutes have one big bad limitation. so they were not able to run tests in parallel. So basically, your tests needed to run sequentially. And from my perspective, it’s a deal breaker because many of the tests just need to be able to run in parallel when there’s any I.O. or any slow operation. And without that, your tests will be just extremely slow.
Miłosz [1:12:32]: Right, so we had to use the test main pattern.
Robert [1:12:36]: Exactly.
Miłosz [1:12:37]: But some years passed, so maybe we should take a look at it again.
Robert [1:12:40]: Yeah, and again, sometimes it’s just a matter of running some helper function before or at the beginning of your test, and it’s fine, and using subtests from the standard library. And I think our last unpopular opinion, so it’s about naming. So you may read in, I think, Effective Go probably, that you should name your interfaces with suffix like error, like reader, writer, follower, user finder.
Miłosz [1:13:21]: It makes sense most of the time, but sometimes not. yeah.
Robert [1:13:25]: So for example if you have interface that have four methods it can lead to something strange
Miłosz [1:13:33]: And i’ve seen people overuse it sometimes like you have a method called user by id and then the interface is user by idr come on can you just call it user finder or.
Robert [1:13:46]: Repository i mean it’s just user repository.
Miłosz [1:13:50]: That’s probably a very minor thing, but I’ve seen it overused sometimes.
Robert [1:13:56]: That’s true. I think it’s again about this being mindful when you are writing software, and if you see that you have an interface named user by IDFIR, it’s something wrong there because it’s just hard to read it.
Miłosz [1:14:12]: Yeah, exactly. It won’t tell you much about what it does. If you just stick to this pattern everywhere. Maybe just come up with something that describes it better, even if it doesn’t follow this idea. And my final one about naming is, I don’t care if you say Go or Golang, which for some reason is controversial. I don’t know. It’s like the common discussion. Someone says, how to do X in Golang? Someone comes in and says, first of all, it’s not Golang, it’s Go. I don’t know why. I don’t care. Everyone understands what it is.
Robert [1:14:59]: That’s true. But I think it doesn’t help that language name is not that unique around everything.
Miłosz [1:15:07]: Hey, it’s not the worst name. It could be called JavaScript. Does it make sense? No. Naming is hard. Everyone knows it. Just live with it.
Robert [1:15:24]: All right, cool. So let’s see what questions we have on the chat. All right, so Jim mentioned that obscure is not spaghetti, and spaghetti is not cyclomatic complexity. And yeah, that’s right. I think maybe we mentioned that it’s close to be equal, but no, it’s not, I would say that it’s maybe some shortcut, but it’s often connected in some magical way that when you have obscure code and cyclomatic complexity is big, there is a big chance that it may spaghetti code but yeah it’s
Miłosz [1:15:59]: Yeah i guess those are separate things obscure will be hard to understand spaghetti would be many side effects that you don’t see something similar but maybe jim you can tell us more what do you mean what do you mean by this uh but yeah there are many aspects for sure and this comes to this discussion about simplicity right so it’s it’s hard to, to decide what’s simple because of it, because there are many aspects.
Robert [1:16:32]: Another comment from Jim is that dry is tension with loose coupling. Any removal of the application implicitly creates coupling that didn’t exist and may be a source of future problems. And it’s true, but sometimes things are coherent. So basically if things are coherent you would like to have them coupled because it’s kind of expected. So if you have one logic that is expected to work everywhere in some way in every place. Well, unfortunately, it’s coupling, but you cannot close your eyes and say, oh no, there is no coupling there and just copy it everywhere because we’ll have a lot of problems with keeping it up to date everywhere. Sometimes maybe it shouldn’t be some library code. Maybe it should be in a service. It’s hard to say because there are many examples, but I would say that I agree with that, but there’s sometimes also one more complexity Sometimes things are just coupled because they need to be coupled and it’s making your life easier. So I would say that coupling is not always bad. Coupling is sometimes coupling and it exists and we cannot ignore that.
Miłosz [1:17:43]: Yeah, but if you overuse dry, you can end up with too much coupling. I think that might be the point here. And yeah, duplication is the best way to lose the coupling as a tool. We have a blog post about this as well, on our blog. And yeah, we’ve also seen that overusing dry is usually a bad idea, unless you know what you’re doing.
Robert [1:18:10]: Yeah. And I think it’s also one tricky part here that we’ve seen a couple of times, that it’s not silver bullet that you can always get common logic in library and just use it everywhere, or you can move it to some services and service can handle that. So in other words, sometimes it’s better to have a service that is keeping this common logic and it’s good, and sometimes it’s better to keep it in the library that is shared across. Because sometimes we’ve seen migrations in 2A. So for example, there was some library, some common code, it was moved to the service, just to figure out that actually having a service handling that is the worst idea, it’s better to have a library that keeps this logic common somewhere. But again, it unfortunately depends. Another comment from Jim. So yes, the focus on testing should be almost always on results, not with internal functions or code.
Miłosz [1:19:19]: Yeah, yeah, that’s why be careful with mocks and at least we prefer stabs or fakes instead of mocks for this. And black box testing.
Robert [1:19:34]: Right. Strange combination of opinions. Love panics, but hate exceptions. Love explicit error returns, but one stuck traces.
Miłosz [1:19:43]: I wouldn’t say we love panics. They have some uses. We don’t use them for normal errors.
Robert [1:19:52]: I hope it didn’t sound like that.
Miłosz [1:19:54]: That’s maybe the difference. Exceptions are just used everywhere in some languages instead of errors. And panics are just for the very exceptional for the exceptional cases not for errors yeah.
Robert [1:20:11]: And fact races so I would say it’s a matter of pragmatism that well, error wrapping is cool but what you’ll do if somebody forgot to do that and it’s also not perfect that you need to do that again, co-pilot helps with that sometimes those messages are super stupid but the reality is that But spending hours on figuring out the wrap strings, it doesn’t make sense.
Miłosz [1:20:38]: Maybe one of the issues where it shows is if you use a library that doesn’t wrap errors and you receive some obscure error from it, and then you have to look at the sources and find what it is and understand what’s going on. And what stack trace could help you. And it doesn’t need to be stack trace of all the levels, just of the most inner level, where the error originated. So you see the previous function calls.
Robert [1:21:14]: Yeah, and I think I talked to somebody from Go team about the problem of stack traces with errors, and I think one of the problems was performance.
Miłosz [1:21:27]: And that’s again the issue of standard library it needs to be universal yeah yeah probably in some projects it wouldn’t make sense to allocate memory for it so yeah that’s but.
Robert [1:21:40]: Yeah maybe someday because from other side we have the runtime tracing that it has it and it’s super fast. The overhead is like 5%, 1%, I don’t remember. Maybe someday. Please, please, please. Mate of Clive saying that I feel the least thing dev use Go is building web services. I always find jobs for DevOps engineers.
Miłosz [1:22:17]: So I mentioned the results of the Go developer survey in the official one. And if you take a look there, the top one is API servers, about 75%, and some other high percent for servers returning HTML and such. So maybe this means everyone writes web services, but they have no jobs.
Robert [1:22:45]: Yeah, but probably it depends.
Miłosz [1:22:47]: Yeah, I think this is an accurate observation that there are not that many jobs where you can see Go used for web apps.
Robert [1:22:56]: For other words, how many Kubernetes operators you can write? Come on so I think it may depend a lot on the market but yeah our observation is kind of consistent with the survey that we mentioned that I think most of the jobs were around services but again I think it may be also depending a lot on your bubble so if you are more into DevOps bubble you may see more jobs postings actually
Miłosz [1:23:26]: It made me think because it’s true that But I don’t see many jobs about using Go for web apps in general. So what do all those people write that reply in the survey about web services? What do they do? Maybe just some open source apps? I don’t know. But at least, yeah, maybe we are biased, but we mostly write to web apps in Go and API services. We don’t do any DevOps stuff.
Robert [1:24:05]: And we didn’t struggle to find any web services, probably it’s better words. Again, but it depends on the bubble a lot. Probably it depends a lot on your local market. If you’re looking in the local market remotely, remote is different. Okay, and the last question so far. So if anybody has another question, we’re waiting for a while. And the last question so far is saying I joined kind of late to this podcast and I’m not sure if you mentioned about naming convention in Go using name variables with one letter or three as maximum I mean code should be readable I think it’s actually a pretty good one we’ll cover this one what do you think?
Miłosz [1:24:50]: Yeah, code should be readable I agree, it’s hard to give some rule here here. Of course, short variables are nice if it’s used in a small loop or something like that. Sometimes it’s easier to read one word than four words together, but I wouldn’t have any strong role here like three letters max. That’s a bit crazy.
Robert [1:25:24]: Yeah, but I think sometimes it may improve, my opinion, readability a bit, but I think it’s all about not overusing that. So sometimes when you are… obviously, if you have struct receivers, it makes sense because you know that you are within some struct and you have receiver of the struct, so you know that S is, for example, service or something like that and it will just make a lot of code much shorter but if you are using all variables that are at most three characters length it may be not readable so i think it’s all about the context so if you see that in this context you know this small shortcut what it means it’s fine but it’s all about not going to extreme i
Miłosz [1:26:09]: Think there are some guidelines like this in effective go maybe or some other document where it’s like this recommended exactly as you said. Depending on the context, just longer or shorter variables. If it’s a long function, and you use the variable across it, many times maybe longer is better, so it’s clear what it does.
Robert [1:26:29]: Yeah, so I think it’s about some common sense. So if you see that it’s not readable, just use the entire name. And I would say that in most cases, we’re using the entire name to make code readable, but we’re also not afraid of using shorter ones.
Miłosz [1:26:42]: I don’t like rules for stuff like this, and a similar one is what I actually like from Google style guide is there is no definitive rule for maximum line length. It just says, you know, try to refactor if it’s too long, but it’s not something like you should use 80 characters per line. Because then you have this fictional, artificial rule that everyone tries to apply but doesn’t help anyone. It seems similar with short names.
Robert [1:27:19]: I think we covered it in an episode about writing low-quality code. At the end of the day, ask a question. Will it help? Maybe it will not help. Maybe it doesn’t matter. Safety doesn’t matter. Whatever. if it’s good if you have enough time to think about such things you have a lot of time if you
Miłosz [1:27:42]: See variables like A, B and C probably something is wrong we can probably improve it, you can also go to the other extreme and call everything by too long names which is also not readable that’s true, I agree that code should be readable And everyone has their own opinion about it, so of course that’s hard to tell.
Robert [1:28:09]: Yeah, so I think this readable, it’s something in the middle. So you’re just not going like writing off your state code with one character length of variables. But sometimes you can also just use those short ones and it can really, for example, for receivers.
Miłosz [1:28:32]: Yeah makes sense.
Robert [1:28:33]: Okay so jim is saying that he appreciate that balance in your commentary too much of go community ime in my experience oh makes sense quite simple with simplistic thank you thank you to jim for your comments thank you
Miłosz [1:28:49]: Jim i think that yeah the simple with simplistic comparison is a good one like simplistic might be too much simply is fine.
Robert [1:28:56]: Yeah yeah definitely cool so we have no more comments so a couple more seconds for last comments if you have any yeah
Miłosz [1:29:07]: So maybe we can mention if you know if someone’s listening to this and doesn’t know go yet or anyone would like to try we have going on evening training on our platform where you can learn by writing code.
Robert [1:29:19]: And it’s not our opinion but some people are saying that it’s the best thing that they did online for learning stuff, so I don’t know. Hopefully. And yeah, it can be one evening because Go has simple syntax and we did really a lot of work to compress the most important knowledge that you need. So you don’t need to read the entire Go standard library to learn to Go code because it’s not needed usually. There are some things that are most useful. That we know from the teams that we’re running because a lot of people actually that joined those teams never worked in the Go earlier and they needed like one week to be totally proficient in Go. Yeah, yeah. So it’s cool with Go.
Miłosz [1:30:08]: Yeah.
Robert [1:30:09]: It’s not perfect language, but no language is perfect. And I think Go is closest to this perfect one. So I recommend it if you don’t know Go yet. Cool. OK, so no more questions.
Miłosz [1:30:26]: Yeah, what should people do if they want to not miss the next episode?
Robert [1:30:31]: So we recommend to join our newsletter for one reason. So Deadly Algorithm may not send you a notification when a new episode is going out. So yeah, our newsletter is the best way to be notified about that. But we’re still under the rule of Deadly Algorithm, so it would be cool If you can leave thumbs up, leave a five-star review in podcast applications, leave some comments if you have any questions who are answering and reading all the comments. So if you have some extra questions, we’ll be more than happy to help you. What else?
Miłosz [1:31:14]: There’s one more comment. Let me read it. So is it too disrespectful to say that Go feels like Node.js?
Robert [1:31:24]: It is.
Miłosz [1:31:26]: When you start a project with them you start from scratch one index.js or main go and there is no structure from the beginning, yeah guess on how you approach starting out like there are some scripts in node i think that set up this scaffolding for you yeah that’s quite common but the approach yeah i think it’s pretty a sane approach. Start with one file and then see what happens. Start creating packages as the main guy becomes too big to handle.
Robert [1:32:04]: If you probably compare it to other languages, like, I don’t know, there are some languages that you need to use some kind of framework to build application and you have a lot of portal plates to start with. In Go, it’s like with You can start with something super simple and extend. So in this case, it’s totally true, but yeah, I think Go is totally different in terms of typing system. So it’s much more strict, let’s say. And while you are maybe building some smaller projects, it’s not a big deal, but when you are working on much more complex projects, when more people are working in, it’s much, much, much easier. Not mentioning about concurrency primitives that, in my opinion, in Node.js and JavaScript are awful. Sorry, in my opinion, promises and actually running everything on one thread is a deal breaker. In Go, it’s better. So you have an abstraction of GoRoutines that is very easy to understand and think about. And it’s also super fast because it can utilize multiple cores. Yeah, it has more threads. Hurray! And it has also nice primitives to talk between threads, let’s say. There are no threads because this is something special in GoRoutines, but communication between them is super nice, especially compared to Python.
Miłosz [1:33:33]: Easy to grasp, easy to read and understand.
Robert [1:33:37]: Yeah, I think it’s also interesting to actually compare to Python because, again, communication between threads, I have PTSD and I’ve asked about that, not mentioning global interpreter log. Come on.
Miłosz [1:33:50]: Yeah, so it’s hard to beat Go’s combination of easy of use and also how powerful it is.
Robert [1:33:58]: Yeah, and what’s also nice is how lightweight it is, because under the hood, one goroutine, so it’s not one thread. Without going to the details, it’s using one thread for multiple goroutines, so thanks to that, it’s super lightweight. So, for example, you can spawn like 1 million, 10 million goroutines, and it’s very lightweight, and you can run it easily on laptop.
Miłosz [1:34:22]: So there’s a follow-up. Yes, I meant the lack of structure. For example, in .NET Core, Spring Boot, you have an initial structure. So that’s about frameworks, I guess, more than languages. And we had an episode on frameworks as well, where we talked about this. I guess you can find some frameworks in Go too, where it will create some project structure for you. And maybe that’s why people are used to those structures, they start the project in Go, start with a single main Go and they are lost and they know what to do next.
Robert [1:35:00]: Okay, if you are looking for some inspiration, so on our blog we have articles about clean architecture, how to dig pragmatically, and there is also this wild workouts project that we created. We’ll add link to this project in the episode description and in the episode summary on our blog.
Miłosz [1:35:22]: All right, thank you for the questions and thank you for joining us.
Robert [1:35:27]: Thank you Miłosz.
Miłosz [1:35:27]: Leave a comment, let us know about your controversial opinions.
Robert [1:35:33]: In two weeks we’ll talk about Watermill as we promised a bit earlier. So Watermill, we mentioned this library already a couple times in this episode. So this is an event-driven, not only event-driven, but mostly event-driven library for building event-driven applications in Go. It seems that this is the most popular library for building this kind of architecture in Go, and I think it has now more than 8000 stars on GitHub, and I think it’s pretty popular and pretty successful and we’ll share some magic stories about watermills and why we believe that it’s successful.
Miłosz [1:36:14]: So if you plan to start an open source project, it might be interesting for you.
Robert [1:36:20]: And after that we’ll talk about sync versus async architecture. So connected to watermills.
Miłosz [1:36:30]: Where one even different architecture shines and when it’s better to keep to the standard synchronous operations.
Robert [1:36:38]: And it will be over-engineering. See you in two weeks, see you in four weeks. Thank you very much.
Miłosz [1:36:43]: See you then. Thanks, everyone. Bye-bye.
Robert [1:36:45]: Bye.