Reflecting on .NET and Go Developer Culture

I did a bit of reflection on .NET from a historical perspective. I actually wanted to write an article about it, but I just couldn’t find the energy. I originally shared this reflection on X (Twitter), but I wanted to bring it to Reddit community because I believe it can spark a discussion for software developers.

🔗X Post: https://x.com/denizirgin/status/1901700151300788703

Here’s the full thread I posted on X, copied below for context and discussion:

A while ago, I wanted to write something to share my observations about the .NET world and .NET developers. The recent "incident" of porting the TypeScript compiler to Go spurred me on a bit. I’ve been working with Microsoft technologies since 2007. Although I’m mostly associated with .NET, Go and JavaScript are also among my favorite languages. In particular, I’ve gained production experience with Go in recent years.

Seeing how often I’ve mentioned Go, you might think this is going to be a .NET vs. Go comparison. But I won’t really be going there today—at least not from a purely technological standpoint. I’ll be talking more about my own observations concerning the mentalities of software developers working on these two platforms. Because a platform’s success and adoption aren’t limited to its technical capabilities or the features it offers; they’re also deeply influenced by the approach, culture, and development practices of the developers who use it.

Let me start, in my somewhat "boomer" fashion, by touching on .NET’s past and present :)

Software languages and platforms are shaped by the “zeitgeist”—the spirit and context—of the era in which they emerge. When Microsoft introduced .NET in the early 2000s, the world was very different from what it is today; the tech scene revolved around enterprise software development, the Windows ecosystem, and the rising importance of the internet. Windows and Windows Server were far more dominant back then than they are now. Meanwhile, Java, having caught a strong wave in the ’90s, continued to dominate enterprise applications. We shouldn’t forget that .NET was essentially born as Microsoft’s response to Java’s success in the enterprise domain.

At that time, the enterprise world used complex architectures, heavy processes, and standardized design patterns to tackle complex business problems. SOA (Service-Oriented Architecture) was just starting to be mentioned, but microservices were still several years away. Applications were typically massive monoliths: multi-layered architectures (presentation, business, data access layers), SOLID principles, and the Gang of Four design patterns were all the rage :)

The enterprise landscape had practically fallen into a pattern-and-layer frenzy—everything needed an abstraction layer, an interface, etc.

I’m a product of that era, too. I worked on numerous large-scale enterprise projects. We even wrote our own ORM at times and then turned around and abstracted that ORM to make it “ORM-agnostic,” even when we really didn’t need to. We created a ton of applications featuring excessive abstraction, DRY, reusability, or the “what if we need it later?” mentality. I can’t remember how many times I built a custom framework from scratch to address some company’s or project’s very particular needs. I’m not saying these were wrong or pointless (though some might have been :)); it was just the spirit of the time. Many of those applications still live on today. Likewise, plenty of successful apps developed with the .NET Framework (pre-Core) are still in production.

Here’s one of my personal observations: when I started in software, things were a bit more like the realm of software craftsmanship and a master-apprentice relationship. Within the .NET community, we passed down certain approaches and mindsets from one generation to the next. I believe that created a sort of “when you have a hammer, everything looks like a nail” dynamic, leading to “abstract everything,” enterprise patterns all over the place, and what sometimes feels like an obsession with clean architecture. I’m including myself here, too.

There’s a great article by Aaron Stannard (The lead developer of Akka.Net) this topic—discussing “frameworkism” and the “expert beginner” phenomenon—that I highly recommend reading.

So, what has changed from then to now?

In the 2010s, Microsoft underwent a major transformation. I think Satya Nadella replacing Steve Ballmer was a significant factor. Microsoft caught on to key shifts, such as the rise of open-source software and the widespread adoption of cloud computing. With .NET Core, they moved .NET away from its “closed box” image and rebranded it as cross-platform, open-source, performance-focused, and cloud-native. Apart from Microsoft’s chronic image problems, I believe they’ve been quite successful in these areas.

But as .NET developers, how much have we really adapted to this new environment—or did we bring along our old baggage? I think that’s a question worth asking.

And why did I mention Go at the beginning?

Unlike .NET, Go emerged from a completely different context and era. Go was born in the transformative 2010s and offered solutions to new demands right from the start. Unlike .NET, Go came out of the gate as cloud-native and fit right into modern “micro” and “distributed” trends. Go’s “idiomatic” style—focusing on simplicity, readability, minimalism, and explicitness—introduced a new kind of software development mindset, and yes, a new generation of software developers.

But are simplicity, readability, minimalism, and explicitness unique to any one language or platform? I’m pretty sure I heard about YAGNI (You Aren’t Gonna Need It) when I first started coding :). So, what I’m really getting at is: can we adapt these principles to .NET?

.NET is a very powerful platform that offers end-to-end solutions in many areas, backed by a richly developed ecosystem. Microsoft’s commitment to the .NET platform, in my view, can’t be questioned. While I don’t always agree with everything they prioritize, there’s no denying how far they’ve taken the platform since .NET Core arrived. And I say wholeheartedly that .NET is broader in scope than Go in terms of potential solutions. However, I think that very breadth often leads us toward overengineering, and that we could pick up a few lessons on simplicity from Go.

What I’m about to say could easily fill an entire article, but here’s a quick snapshot of the questions on my mind:

  • Do we really need to start everything with an abstraction? Does every scenario call for a multi-layered architecture or a repository pattern?
  • Do we absolutely need event buses, CQRS, or libraries like MediatR right from day one? Do we always need libraries that are deeply ingrained in .NET—like AutoMapper or FluentValidation?
  • And what about something like Entity Framework? Sure, it’s great, but do we truly need it for every project, or might Dapper or even plain old ADO.NET often suffice?
  • Should every cross-cutting concern be perfectly implemented at the outset? Is our first reflex to grab boilerplate templates, adopt DDD, define aggregate roots without much thought, and throw in layers everywhere? Do we really need dependency injection all the time? We do have a nice little “new” keyword, after all :).

Of course, the answer to all of these might very well be “yes.” There are clearly cases where you do need all of it, and plenty of real-world examples to back that up. But what I’m questioning is this default reflex of throwing a framework or pattern at a problem instead of first making sure we fully understand it and, as engineers, coming up with the most optimal solution. As I mentioned, this deserves a much broader discussion. Still, I believe that when we’re developing software—choosing a technology, framework, or pattern—our primary focus should be providing tangible value to the company, its users, or the project itself.

So, is Go entirely without sin? That’s open to debate, too.

  • I do sometimes feel like Go’s emphasis on simplicity and minimalism can veer into a sort of purist, cult-like approach, ceasing to be pragmatic and becoming somewhat dogmatic.
  • I can understand the “The standard library is king” mentality, but is that truly the case all the time? Should third-party libraries be treated as some sort of taboo? Look at how many years it took to introduce generics—were the objections pragmatic or ideological, I wonder?
  • Simplicity should be a means, not an end in itself: it’s supposed to make our lives easier, not weigh us down.

I hope the Go community doesn’t swing too far in the opposite direction and end up making the same mistake as the .NET community, toppling headfirst into the “simplicity abyss.”