Most of us have decided that code simplicity is a desirable quality. However, we have also observed that several things limit the simplicity of our code and even exaggerate our code's complexity. These things that make our code more complex than it should be include overuse/abuse of design patterns, overuse/abuse of favorite syntax/code features (such as implementation inheritance), resume-driven development, the Magpie Effect, Not Invented Here Syndrome, and other dysfunctional motivations.
Although many of us see the benefits of striving for simplicity and avoiding unnecessary complexity, I have also observed that in some cases we may overexert ourselves in an effort to achieve simplicity in our code that is not justified by the problem domain being modeled in the code. A general rule that seems obvious but which we often seem to forget is that the code cannot be simpler than the problem domain it describes. It is typically a vain effort to try to write very simple code for a very complex problem domain. We can strive our best to make the code as simple as possible, but the size and complexity of a problem cannot be completely ignored. At some point, the complex functionality must be implemented. As an example, the code for implementing addition should not be as complex as code for implement Fast Fourier Transforms. We can strive to make the FFT implementation as simple as possible, but it will always have a certain degree of complexity simply by the virtue of the problem's own complexity.
We can do some things that make the code appear to be more simple than the problem domain it describes. I describe each of these here, explain how each of them doesn't really make the code simpler than the problem domain, and briefly discuss the tactics we regularly employ to enjoy these perceived simplicity benefits.
In a particularly complex problem domain, one of the tactics we can use to make our code simpler is to leverage simplifying assumptions. Note that we've not really written code that is simpler than the problem itself by doing this. Instead, what we've really done is simplified the problem domain to a point where its code can be simpler. These simplifying assumptions not only make our design and code effort easier, but they often make for a more usable tool or software product for our end users.
Codifying the Problem
Code can sometimes appear to be simpler than the problem domain it models because of the fact that we are better at reading the code than we are at reading the other descriptions or design documents related to a particular problem. This does not necessarily mean that the code is simpler than the problem space; it only means that we are more comfortable reading code than we are reading our types of documents. Another person might be more comfortable with the written description. A central takeaway from this is the value of prototyping. Prototyping helps us to flesh out questions and risks in the problem space. The prototype also codifies our understanding of the problem space. Similarly, willingness to refactor is also important in this area because we learn as we codify the problem.
Leveraging the Work of Others
Today, many enterprise functions are provided by an underlying application server, library, toolkit, framework, or domain-specific language. It can seem to the developer using these that many of these enterprise functional benefits come very easily and simply. While they may be easy for the end developer, the overall code (including the code in the product being used) is far from simple. The use of an existing library, framework, toolkit, application server, or other pre-built piece of code does give the impression of greater simplicity thanks to previous work. What this tells us, of course, is the value of reusing good products and not reinventing the wheel unnecessarily.
The effort to achieve coding simplicity in the light of a complex problem domain reminds us of the value of prototyping, of refactoring, of using pre-built libraries and frameworks, and of using simplifying assumptions. It is also important to realize that some problems are so complex and so large that even the simplest, cleanest code may still be rather large and complex to adequately meet expectations. It is admirable to make the code as clean and simple as possible, but the unavoidable fact is that some of the things we must describe in code are themselves complex and their code implementation will have to be complex as well (unless we allow simplifying assumptions as described above).