I have come across my share of beautifully written, over-engineered code... one moment I am thinking “That is an interesting way to do that” and the next minute you are wondering “what the hell is going on here!”. I have also suffered from writing over-engineered code, where you are thinking so far into the future that you compromise NOW. The YAGNI (You Aren’t Gonna Need It) principle is a good way to counter over-engineering: functionalities should only be implemented when you need them, not on the possibility that you will need them. I have mentioned over-engineering a couple of times now, and some of you reading this are probably thinking What is over-engineering? NOW NOW YAGNI In very simple terms, over-engineering is making software/system design more complex than necessary; this usually happens because you want to add extra functionality to your component to simplify the implementation of A, only to add the functionality for B later. We have this codebase for managing invitations; it’s an inherited, legacy codebase with technical debt that accrues interest. The more you try to work on the legacy code, the more something somewhere else breaks. You have to revert in most cases and start all over. Working around the legacy code ends up accruing interest on the technical debt; it’s a standoff between risking breaking features all over the app or adding more technical debt to the codebase. The third solution we came up with was abstraction. We had to figure out ways to modularise new features or improvements to the application, and to expose and share data where necessary. At first, this seemed like a Hail Mary. Finally, we can work on this mangled codebase with minimal side effects. Were we wrong!! The abstraction spiraled, and the codebase ended up overabstracted, bloating the solution to the problem and bringing the supposed salvaged side of the codebase into its own hell. Along the line, we forgone the rules for the abstraction and now have different parts of the application dependent on each other, looping back to where we started. I suppose this was bound to happen in a codebase with a couple of developers, each with fire up their asses to ship features; we ended up shipping maintenance and more technical debt. When collaborating with multiple people on a codebase (which is pretty much always the case), and for corporates who are more interested in shipping features than code quality, code reviews always suffer. For problems like over-engineering and overabstraction, Traditional line-by-line code reviews often miss these systematic issues. You check a component created a couple of weeks ago to support the integration of a feature, following the DRY principle, and realize there are now 10 interconnected/similar features that depend on the component. Code reviews will need to be elevated to catch these architectural issues and ensure that dependency requirements between components are met. Spaghetti DRY Code We live by the DRY (Don’t Repeat Yourself) gospel because it simplifies work, and developers are inherently lazy (in a good way). The DRY principle works well with orthogonal systems: small, self-contained components combined to form a system. Systems should be composed of a set of cooperating modules, each of which implements functionality independent of each other. Systems should be composed of a set of cooperating modules, each of which implements functionality independent of each other Alıntılar - The Pragmatic Programmer: From Journeyman to Master | Kitap.Guru. Alıntılar - The Pragmatic Programmer: From Journeyman to Master | Kitap.Guru. There should be more emphasis on orthogonal systems and on DRY code; it’s easier to combine the reusable functions you create with each other and scale them as the repository progresses when they are not bloated and overabstracted, at which point you end up with a rigid system interwoven so much with each other that it will be complicated to connect which aspects of the code that does not meet an exact rule, you will find yourself duplicating the code because making it work for a new connection breaks an old system. Congratulations, you have achieved Spaghetti code that cannot bend. To use a truly orthogonal system and avoid spaghetti DRY code, every code module change should affect only the module that is updated. Complex component Your components should not focus solely on avoiding repetition, but also on being small abstractions of the overall system; otherwise, you will end up with components so complex that they can easily break with a single change to a connected component. When creating reusable code, the approach should be writing code that does not depend on any other code block to function a certain way. Reusability should be used as a tool and not the goal; when you have reusable UI components with business or API logic in the same component, you are on a highway to over-abstraction. It starts small, and before you realize what is happening, the disease has festered throughout your repository. Now you cannot reuse the component on a different page with the same UI functions and different logic without adding additional external logic/context to the component. Patterns to avoid over-abstraction and over-engineering Modularity Modularity Modularity Modularity involves breaking your system down into smaller, independent codes/components called modules. Please pay attention to the word ‘smaller’; it’s possible to have a bloated module with more code than necessary, which creates over-abstraction and should be avoided. Over-abstraction is really just an unsuccessful attempt at modularity. Your modules should be able to function independently of other modules, only exposing required data. Changes to good, modular, structured code should affect only the modules, without any cascading effects. Functionality-First A good approach to building orthogonal systems and easily avoiding over-abstraction is to build with functionality first, then features; this aligns well with Component-Based architecture(separating UI components from stateful components). The functionality stage will focus on the smallest reusable units of code that are assembled to implement the feature. A Login feature will consist of the following functions: collect username and password (UI), validate user data, redirect to the user profile/reject collected user data. Each functionality should operate independently, relying only on necessary data. No medals for over-sophisticated code After writing every code implementation, ask yourself whether there is an easier or simpler way to achieve the same result. Most of us have heard stories about code that can be edited or worked on by only one person in the company, which is not a feat to be proud of. Writing code that can only be maintained by you most likely means it’s over-sophisticated or employs unorthodox procedures. A great example of coding using unorthodox procedures is Gilfoyle’s Hard Drives from the article: “We ran out of columns” - The best, worst codebase by Jimmy Miller. A former employee of a company is known for not checking in code and for having programs and parts of the codebase on his hard drive (imagine the overhead for maintaining that product!). “We ran out of columns” - The best, worst codebase by Jimmy Miller I have found myself writing overly sophisticated code because I want to use a new technology/package/library I just learned about, without considering whether it is the simplest tool for the job. The excitement for learning something new is great, but attention to when and where it should be applied is probably more important. Finale In the quest for writing perfect codes that account for all possible future and time-travelling cases, you end up with an over-engineered codebase. You should give it up because it is not possible to write perfect code, aim for good enough that meets all your immediate requirements. The DRY principle is fundamental; repetition is still a sin in software development. The DRY principle should be applied to an orthogonal system to create a codebase that is decoupled, with each module independent and exchanging data at a separate meeting point(feature module). These will help you create systems that are easy to maintain and debug. Remember that simple is always better in software development.