Skip to footer content
Iron Academy Logo
C# Common Problems

5 C# Common Mistakes That Make Your Software Unmaintainable — Explained by Derek Comartin

Derek Comartin
13m 46s

Writing clean, maintainable, and efficient code is the hallmark of professional C# developers. Yet, many common mistakes in the C# programming language make codebases a nightmare to work with over time. In this article, we’ll explore these mistakes by summarizing insights from Derek Comartin's video titled "5 Mistakes That Make Your Code Unmaintainable."

Derek shares his insights from building large business systems and points out five key software design mistakes that developers — especially in C# — often make. Let’s dive in and take a deeper look at these issues using Derek’s video as a guide.

1. Lack of Ownership Over State

Derek starts by identifying the mistake of allowing multiple boundaries or services to update shared data without clear ownership. He uses an example where the billing system reaches into another part of the application and changes state. This leads to data consistency issues, particularly when that object resides in a different location within the system.

This kind of free-for-all approach creates errors where developers ask: "Why is this data incorrect?" or "Who changed it?" Derek emphasizes that you must define ownership. Each part of the system should expose a well-defined API or method — responsible for managing the state.

Instead of allowing any part of the application to modify shared data, Derek suggests creating explicit commands and queries. For example, when you want to update a shipment, you issue a command through a dedicated interface. This provides structure and avoids resource leaks from untraceable changes.

2. Implicit Code vs. Explicit Workflows

According to Derek, many systems rely heavily on CRUD operations (Create, Read, Update, Delete), but this results in implicit workflows. The code is technically functional but lacks clarity about what it does. If your class only supports generic operations, the actual business workflow is hidden.

Take this following example: a driver picks up a package, and a Bill of Lading is generated. If the system just runs UpdateShipment, it's unclear whether the string change (like the BOL number) is due to a pickup or a correction. Derek points out that we should replace vague updates with explicit operations like PickupStopLoaded.

This leads to more readable code. It also aids in exception handling because when an exception ex occurs, the stack trace will clearly indicate which operation failed. Explicit methods also support better coding standards, as each function has a single responsibility.

3. Adding Useless Indirection

Derek moves on to indirection — inserting unnecessary layers between your caller and target method. He illustrates this with database connections. A controller might call a service, which calls a helper, which calls another service, which finally executes the query via Entity Framework.

This pyramid of abstractions makes it harder to trace issues and improve performance. While creating abstractions can be useful, such as wrapping IDisposable interfaces for better resource management, Derek warns against overdoing it. Ask yourself if your abstraction simplifies the API, or if it's just hiding a third-party dependency that only exists in one place.

Instead of layering for the sake of layering, Derek suggests managing the coupling directly. Excessive indirection not only clutters your code, but it also increases the potential for memory leaks and weakens garbage collection benefits.

4. Playing the "What-If" Game

The next mistake Derek identifies is preparing for hypothetical use cases that may never happen — what he calls the "What-If Game." Many C# developers write flexible classes and functions to account for future needs. For example: "What if we need to support two languages?" or "What if we need to switch technologies?"

Derek warns that this mindset leads to bloated frameworks and overly generic code. He mentions encountering string concatenation logic and reference type wrappers that nobody understands because they only serve one real use case.

Instead of preparing for the unknown, Derek advises focusing on actual requirements. Each method and variable should serve a current, justified purpose. Unused features only add maintenance cost. As Derek says, it's not just the development time — it's the cost of ownership. If your public bool Equals implementation, for example, covers every edge case you could imagine, but none that actually happen — you’ve wasted valuable time.

5. Not Managing Workflows Correctly

Finally, Derek discusses the mistake of treating workflows as procedural blocks instead of modular steps. He uses a real-world example: placing an order online. After the user completes the checkout, the system charges the credit card, then sends a confirmation email.

If one step fails — for example, the payment process — how does your code react? Do you roll back the order? Show an error? Send a failure email? Derek explains that lumping this into one block creates unmanageable complexity.

He recommends designing workflows as small, isolated units that communicate through messaging. Using async Task operations and yield return makes these steps easier to manage. Moreover, employing the using statement and using block around external resources like file access or database connections can help prevent memory leaks.

For instance, a using block around a stream ensures it gets disposed of correctly — a vital point when working with IDisposable interfaces. When workflows grow complex, these best practices ensure exceptions are caught and handled effectively, preserving performance and maintainability.

Wrapping Up: Write Clean, Maintainable Code

As Derek concludes in his video at 12:45, he reflects on these mistakes not only as things he has seen but also as things he’s personally made while building large business systems. These are lessons learned through experience, and he encourages viewers to share their own mistakes in the comments.

Derek’s advice applies not only to C#, but to many other languages as well. Whether you're working with string comparison, Equals() methods, or designing new features, the key is clarity, intent, and keeping your code maintainable.

If you’re interested in improving your C# skills and avoiding these common pitfalls, Derek’s channel offers many free resources on system architecture, design patterns, and real-world programming language advice. Avoiding just one of these mistakes can significantly improve your project’s quality.

So, the next time you start to write code, remember Derek’s words and ask: "Am I making this more complex than it needs to be?"

For more content like this, check out Derek Martin’s CodeOpinion YouTube Channel.

Hero Worlddot related to 5 C# Common Mistakes That Make Your Software Unmaintainable — Explained by Derek Comartin
Hero Affiliate related to 5 C# Common Mistakes That Make Your Software Unmaintainable — Explained by Derek Comartin

Earn More by Sharing What You Love

Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!