Subproblems in Software Design (aka the power of abstracting the problems)
Tags: softwaredesign, softwaredevelopment
Published: 2024-03-03 14:58:57

We study common realms of algorithms and data structures in order to use them in a more concrete way when solving problems (or to pass technical interviews.) For example, we study dynamic programming, graphs, sorting, etc.

And there are common subproblems that usually fit certain realm of algorithms. By which I mean if you zoom out of a problem and dissect it in a more abstract way, what types of algorithms does the context of your problem fit into? Common subproblems are not algorithms per se, they are contexts in which you can pick some algorithms to use for. 

For example, a fairly easy-to-digest example is a social network 
application that contains users that follow each other. If you have studied graph algorithms and data structures, you can map them to this problem's setting. Graphs are abstract and applicable to many different problem domains. For example they are also used in map and routing applications too. Whether it be for computer networks, or for actual real-life transit systems.

This post aims to talk about the merit of thinking abstractly in the software design and architecture level, and provide examples on how they can be helpful. I hope to encourage you to think more about abstractions. The term abstract here is used as opposed to "concrete." Something that is more abstract, is less domain-dependent and/or application-dependent.

For starters, if you have experience of working with design patterns, then you have already done it! Design patterns give you some boilerplate solutions to common design subproblems, regardless of the environment.

Here is an example: Let's say you have an entity that wants to communicate with another entity. A common design pattern that can solve this problem is the "Client-Server" design pattern. It's one of the most fundamental patterns used in creation of computer networks and the whole internet. 

However, you may think that it is used only in the web context. But it's not just used in the web. Two other examples for its use-case are in LSPs (Language Server Protocol) and the Godot game engine. Let me briefly explain their concepts:
- LSP is the tool that runs the autocomplete in your modern code editor. The client is your editor and the server is the LSP. Client sends the code text to the server, and the server returns auto-complete suggestions or possible syntax errors.
- Godot is a very cool game engine in which many sub-parts of the engine communicate with the engine through a client-server manner. In this context, Godot is the client and it uses 2d and 3d physics servers, a sound server, etc. E.g. the sound server "serves" sounds for the core engine.

You can see the domains of internet, code editors, and game engines are vastly different in the domain of computers. 

However, through abstracting the problem we can boil the problem down to this fundamental subproblem: **I need to provide connection between two entities.**

The stated sentence is okay, but I think it's not thorough enough based on those 3 examples. Those 3 examples have something more special to them: the fact that the two entities that need to be connected aren't necessarily in the **same program**:
- In the internet, clients and servers are literally on different machines.
- In LSPs, each programming language has its own LSP server and then we have different code editors that use LSPs such as Neovim, Visual Stuido, IntelliJs. Each of these things are developed by a different companies with different languages.
- In a game engine, the constructing systems might be different processes of different languages. For example the physics engine might be a C library, the main engine core might be in C++, and the runtime scripting might be for Lua.

So, the specific and tuned subproblem that we are wanting to design for is this: **I need to provide connection between to entities that aren't necessarily residing in the same environment.** 

Stating my core problem more accurately helped me to filter out some possible solutions that doesn't fit the exact criteria. I didn't know that detail of being in the different environment in the beginning. How I realized that was by revisiting the concrete problem again in order to extract the abstract idea more clearly.

Is the server-client idea the only solution for that problem? The answer is no. Here is an alternative solution: Server-client offers direct connection between the two entities. What if instead, we make a third entity that holds the data to be used between the two entities? Essentially a data container entity that can be a file, a database, a 3rd-party server, a process mutex, based on the domain of our application.

I argue that it even works on the paper for designing internet too. But it would be really bad and inefficient and inconvenient. It would mean that instead of my PC asking a server for the website content, I would submit my request on another 3rd-party server, and then the server that holds the website recognizes that request and puts the content on the 3rd-party server. And then my pc recognizes that there is the content there. This means that the trip counts are increased to more than 50% for each packet compared to the client-server model. This solution sucks, but it is still a solution.

Funnily enough, my latter solution is actually feasible if I change my initial question: let's say that I want my client to not always contact the server for acquiring repetitive information, so that  the server load is reduced for unnecessary connections. Aka the concept of Cache in networks. With a few tweaks in details, the solution of a 3rd party server containing a cache table fits this problem really nicely. But it didn't fit the other question. 

The fun point of software design is that upon hearing the problem statement, we can design many different solutions, and sometimes you can't prove one is better than the other. Algorithms are less free in this sense because a lot of times you have calculatable time and space values and limitations. An algorithm with the time order of O(logn), is just better than an algorithm with the order of O(n^2). But for design, it's less provable. There are measurement systems of course, but in design is more free by nature. (Also, just look at the JS Node packages dependency trees... It's clear that we don't care...)

In software design (and also algorithms), you can sample any realm of computers and apply it to another realm. Because the core skill that you use is the skill of abstracting problems and the solutions. You can change the skin, the scale, the programming language, etc. But the core is the same. 

Heck, even the idea of orchestration tools such as Kubernetes come from the music orchestras. If you think about it, it is a really liberating perk that just by becoming a better analyzer of anything that you face around you, you can literally turn it to a possible design idea for a computer system.

I think the first time that I heard of this way of thinking was from a professor in the first week of college (eventually I grew to dislike his attitude, but initially liked them). He was talking about how computer scientists got inspired by birds, bees, ants, and fishes and created the swarm intelligence algorithms to simulate collective decision making behavior. 

What I have observed is that many times, the core idea of the issue that I'm facing is nothing new. There are "common subproblems" that analyzing them can help improve solutions in other domains. So although my area of work is in games, it's still helpful and fun to study about other fields. Even fields that aren't about computers. 

I think it would be fun to write more about some common subproblems in software design. Maybe I'll do that. For now, I hope this post is helpful to someone out there!

[BONUS PARAGRAPH] On other fields of science, art, literature, and philosophy, this approach is also common. Of the works that I'm a bit familiar with, Jacques Derrida's theory is exactly aligned with what I proposed here. He proposed the idea of "Deconstruction" in the subject of human languages. He argued that the meanings that are conveyed through language are deferred and context-dependent. They have ambiguities and contradictions. Derrida proposed that to properly analyze human language and the meaning conveyed through it, a nicer way would be break down the meaning into its constructing elements. 
Computer languages, on the other hand, are context-free. They aren't ambiguous. The thing that makes the programs unpredictable is the way humans write the code using the languages, not the languages themself. The codes are complex because the writer of them did it in a complex way. (Then the Chat Jippities learned from those complex codes and kept rewriting them.)