Skip to main content

Command Palette

Search for a command to run...

Conquering Software Complexity with Simple Tactics

Updated
7 min read
Conquering Software Complexity with Simple Tactics
D

Senior Software Developer with over 10 years of performance-focused .NET development experience. I assist developers in solving architectural challenges and simplifying complex software projects. Writing as Dmitry Dezuk to share everyday productivity tips for developing faster and more reliable software.

We all write code to solve people’s problems, but we rarely think that every line or character of code adds to another class of problems that originate in our software. Before you know it, your solution to the original problem eventually becomes another problem with much more complexity. Do you know that joke about regular expressions that says that when you have a problem at hand and solve it with a regular expression, you are now dealing with two issues? You face consequences only when you neglect or don’t recognize the existence of that threat though.

Unexperienced developers may start feeling that monster in the room without fully recognizing it. They are given a seemingly easy task in the beginning that sounds simple but all of a sudden something gets in their way and deters from their direct responsibility to write code. They are already feeling the breath of that monster but they don’t know what that is. They initially gave optimistic promises and ETAs and now what? They just can’t deliver because they are facing a wall, and that wall has a name. Meet “accidental complexity” which is an added complexity of the original problem and it stems from all the tools, programming languages, and techniques we are using to model a solution in software. The domain itself is usually complex enough and it features “essential complexity”. Stakeholders summon us IT guys to help because their complexity can’t fit one head (or their team heads) anymore. They delegate all that complexity to us and we truly need to re-delegate that to software nicely otherwise we and our support team become an easy target for frustrated customers. To be effective developers need to know how to tame accidental complexity in their stack so that they have more time to work on more relevant tasks. Ideally, they should spend more time on clarifying problem domain requirements, and nuances, and how to model them adequately with programming language bricks as this is where the value is. Corporate stakeholders focus on real business goals and decisions and they don’t care how your project is organized, how much technical debt it obtained, what language you used, whether you rely on a garbage collector, and so on. You are to manage all that risk to be successful and there are ways and approaches to do that.

The software serves a particular purpose and every piece of it is the source of so-called entropy and combinatorial explosion. What is entropy? It is the natural tendency of a system to degrade or transition from an organized structure into a chaotic matter if there is no constant effort to get the order back. The same is happening with software design. Code entropy is the gradual degradation of code quality over time. Every code is subject to it and eventually becomes increasingly complex, hard to understand, and more prone to errors. That stems from rushed patchy development, unclear contradicting requirements, technical debt accumulating, and a lack of consistent refactoring. Eventually, it leads to project delays, increased costs, and failures. Software becomes so fragile that it operates predominately in undocumented and unforeseen states and code paths producing noisy outputs, crashing, throwing errors, causing data corruption, and many other undesirable outcomes.

Another enemy is the combinatorial explosion. It inevitably leads us to an explosion of edge cases that practically can’t be 100% covered with automated tests. For each new parameter, input field, or feature, the number of potential interactions and code paths grows exponentially, not linearly. Testing every possible combination becomes practically unachievable, especially as the application scales. With the growth of variables, states, and lines of code, some combinations of them are never foreseen by the developer and often manifest as bugs. They are particularly troublesome because they don’t show up during typical testing environments but can emerge in production under narrow conditions, causing reliability issues and complicating debugging.

The ideal situation is when a function or an action is served without a single line of code. It is useful to consider whether there is a good reason to go with software in the first place. Maybe there are alternative ways to solve a problem efficiently without coding. Many “no code” tools can be the way to proceed. Like in martial arts, the best way to win a battle is to avoid it. Try to solve the problem without software, but if the software is unavoidable, you have to strike first, and here are the martial arts to win this battle.

If we need software, you have to be sure your solution is minimal and simple enough for the given requirements. I would even consider whether having just one button is good enough. Because if you add a text box or any additional input, you have to start considering entropy and the explosion of internal states.

The most common techniques for me to combat all those issues are Object Oriented Programming (OOP) coupled with Domain Driven Design (DDD) and the “Fail Fast” principle. Why, because they are at the heart of fighting complexity, reducing possible states, good code quality, and readability. Of course, other principles of “clean“ code still apply but the two above are simple and effective weapons against complexity.

It is very important to employ those principles on day one to avoid facing the consequences of accidental complexity. You should fight it with all your force and power. Of course, they are huge topics by themselves, but I will cover them piece by piece in practical examples. Here are some excerpts.

  • Start with your domain model first

  • don’t think about logging, monitoring, performance, garbage collection, exception handling, or interacting with databases, emails, or external systems. You will get back to all that later when your domain model is satisfactorily mature.

  • Don’t let bad input at all cost. Check every public method parameter fiercely and throw and throw exceptions if their values are not from the expected range allowing only a single happy path.

  • Remove all possible methods and code paths that are not executed through declared requirements. We are not writing a flexible library here for numerous use cases.

  • Try to make all the method parameters (including return values) domain objects.

  • Throw errors mercilessly on every unclear or undocumented condition. Never try to fix, or implement a workaround for invalid input in core domain level. That is not happening here. Every step left or right off the trail causes harsh exceptions thrown from the core domain realm.

  • You should never allow any further execution at first traces of something going south. Don’t think about the client and how bad it would feel to crash. It is not your business here.

  • All properties should be part of the domain. No nulls or semi-valid objects are tolerated.

  • Use test-driven approach writing test first to overcome paralysis to start coding

  • Use no external or out-of-process impure dependencies in your core domain. Everything should be written there as if all objects are available in fast memory.

Rejoice in your core domain. You are the king there. Only in the core domain, do you have the luxury to fully fight essential complexity and not to deal with accidental complexity, and combat entropy with maximum chances to win. That is why you need to push as much code as possible in there and push a very thin layer to the boundary of your application where all the orchestration and integration with external actors takes place. You have all the tactical tools at your disposal to organize your domain following the golden standards of efficiency and minimalism. This is where you think of OOP, SOLID, singletons, visitors, factories, and so on.

Unlike the core domain, you are limited in all other layers.

  • Utility methods should be flexible to tolerate garbage input

  • The validation layer shouldn’t throw but rather inform about problems and not use exception throwing at all.

  • Logging should allow a gazillion parameters to log every possible combination of parameters of various types. Otherwise, the tool won’t be convenient.

  • You don’t create a diagram of classes to read/write from/to a file or a database.

  • Your view models or controllers should look more like a transaction script orchestrating and stitching everything else together.

Automated testing with DDD is bliss as you can achieve 100% coverage here without engaging test doubles, mocks, or spies as the core domain does not need much reliance on external dependencies.

Practicing those recommendations is doing a huge favor to your future self and everyone taking ownership of the code after you. You will see that following just a few primary principles from above lets you build software fast and almost flawlessly from day one.

Let’s consider a more or less realistic use case. Say you are developing a hotel booking system and you get the most basic requirement first. You need to implement a feature to register as a customer.

See the code in the next blog.

Stay connected!

T

I was looking for new casinos outside of the Gamstop network and Flash Dash Casino really caught my eye. Instant withdrawals, safe play and a huge £1000 bonus + 250 free spins? I'm in! Fast payouts and solid gameplay here. If you're curious to know what makes Flash Dash stand out, check out this detailed review https://gb.nongamstopbookiesuk.com/review/flashdash-casino/

D

Great article, dude! You nailed the struggle against combinatorial explosion—your analogy of software entropy as a messy teenager's room 🔥. I loved how you broke down DDD concepts. Keep these articles coming! Your writing makes complex topics so enjoyable. I can't wait for your next piece with examples. Every time you use DDD to fight entropy, a developer's code gets cleaner. You're a coding superhero!

More from this blog

R

Rapid Software Patterns

8 posts

Practical patterns that simplify and speed up software development in .NET and adaptable to any platform.