HACKER Q&A
📣 krm01

How do you structure your code


As my projects grow and I add more features, I've been facing challenges in maintaining a clear and well-organized codebase. It becomes increasingly difficult to navigate through the code, especially when it comes to debugging, refactoring, or adding new features.

As a solo builder, it’s pretty easy to hack something together and choose speed over structure. With some of my larger projects however I start to feel the problem of all the duck tape I placed left and right.

I am curious to learn from your experiences, best practices, and strategies that you follow to structure your code in a way that keeps it clean and easy to navigate as it evolves. Some specific points I am looking for:

1. How do you organize your directories and files? Do you follow any design patterns or principles (SOLID, DRY, etc.) to maintain code clarity and modularity? How do you manage dependencies between different components or modules?

2.How do you balance abstraction and simplicity while writing your code?

3. Are there any specific tools or techniques you use to visualize and understand the structure of your codebase?

4. Any examples, resources, or personal anecdotes would be greatly appreciated.

Thanks in advance for sharing your insights!


  👤 aristofun Accepted Answer ✓
Sorry to disappoint but there is no silver bullet here.

Each project is unique to some degree depending on the project itself.

And sources of wisdom to learn from are the same old experience, colleagues, books and again experience.


👤 hayst4ck
http://misko.hevery.com/code-reviewers-guide/

This guy was a google test-ability engineer. His assertion is that good code (code that is decoupled/easy to replace) is code that it is easy to write unit tests for because all the things that make unit testing hard is bad engineering, like coupling of components or poorly scoped data.

If I summarized 80% of his the value of his work in one sentence it would be "Don't abuse global scope." It is a surprisingly simple restriction that results in orders of magnitude better code. Calling a function inside of a constructor is using that function from the global scope and therefore it is an abuse of global scope. If you mine his website, which is admittedly outdated, there are many many examples of the consequences of code patterns that will hurt later.


👤 mattbgates
Root

-> Scripts

--> Template

---> Pages

-> Stripe

-> CDN

Although my methods have improved greatly over the years, in which I like to try and stick to no more than a single page or two nowadays, I aim for the KISS method. In every file, I have a header:

/*

NAME: file.php

DATE: 4/10/2023

PURPOSE: this file processes the data that was passed to it

NOTES: leaving any notes for my future self

*/

Nice to keep track of everything with Git too but the main file has it all.

Best advice: the less you have to guess, the more you can stay ahead.

Pretend as if other people are reviewing your code and you don't want them to get lost. In your case, its your future self: if I need to come into this code at a later time to either debug a feature or add a new feature, how long will it take me to figure out what my code is doing? It's basically like leaving yourself easy-to-follow breadcrumbs.

Fill your code with an important header. Try to add a comment to every section of code, if not every line. Be descriptive. The little extra time you take explaining what your code is doing and maybe even why will save you heaps of time in the future. The other good thing is that if you ever sell your code or hire someone to manage your code, you have left a detailed paper trail for them, so they don't have to just follow your code.

I picked this up from my first job. I was hired to fix bugs in an existing program by a half dozen programmers that never left comments for each other. So I upgraded the code as much as I could to leave comments along the way for future devs. Didn't really matter in the long run, as a few years after I left the company, the boss had sold the windows application to another company, who in turn destroyed it, so they could promote theirs.

But I've always kept to this documentation mentality for my best practices.


👤 sreeramvenkat
I am finding many things to learn on this from the book 'Philosophy of Software Design' by John Ousterhout. There is a video in YouTube where John Ousterhout outlines the core principles present in his book.

👤 JonChesterfield
My side projects are built _way_ more carefully than production code. Clearer structure, more refactoring, more testing. Near zero documentation though.

The cost/time tradeoffs are different. When someone is paying for it, it has to ship asap. When other people are working on the same code you can't go too far off the beaten path. So you may end up trapped in the world of cmake for example.

3. is a slightly confusing question. You built it, therefore you understand it, no? It's the stuff built by other people that needs visualisation tooling, especially if it keeps changing.


👤 austin-cheney
I am playing around with a file system tool that’s rapidly becoming mor of an operating system. I am pretty happy with the organization. Please feel free to play around with it: https://github.com/prettydiff/share-file-systems

👤 japhyr
What language are you using? Maybe we can point you to some example projects.

Do you have a decent test suite set up? With a reasonable test suite and version control, you're free to start restructuring your project.


👤 TechBro8615
I try to optimize for my future self understanding wtf I was trying to do.

👤 george87
I tend to have the opposite problem. I'm a bit of a perfectionist and tend to optimize prematurely. And it's sometimes difficult to convince clients that a little bit of extra work upfront will pay off and save them a lot of time and money in the long run, especially when building something that is expected to scale and evolve over time.

I usually do dev work solo or with a small team of 2-3 people, so I've had to deal with these things in a very mindful and deliberate manner. To get more into specifics, here are some things I've learned:

- I think about scope and complexity at all times, before, during, and after development. I try to anticipate issues that might arise and act preemptively, never letting things get out of hand. Writing code and setting up the infrastructure around it is sort of like building a house. You need a blueprint and a well thought out plan, but you also need to monitor the process to make sure everything is done properly and leading to the desired outcome (no matter how variable that outcome may be).

- I use every technique I know and tool I have to make my code as clean and modular as possible. From IDE shortcuts and syntax formatters, to SOLID principles and design patterns. With the latter, you need to know when to apply them and how to do it in order to extract the most value without over-engineering stuff. Needless to say, duct tape has never been in my toolbox, and if I see someone else using it, I have an urge to peel it off and throw it away.

- Understanding different techniques for structuring your code is important. I've found that a variation of the package-by-feature (or package-by-component) approach works best for me in most scenarios. And no, this is not only useful for backend OOP stuff, but for frontend JS code (e.g., React) as well. For web backends, I usually have a package for each domain (e.g. user, product, order, etc.) and keep them self-contained and independent from each other. A package contains everything that belong to a domain entity/event or feature; all related data access logic, business logic, data transfer objects, routing/controllers, etc. are in one place. At the top level, I sometimes have a package for each architectural layer (e.g., infrastructure, application, domain, etc.) with minimal or no dependency on other packages from the same layer. Throw in dependency injection, and I can easily swap out entire modules and test every single thing in isolation.

- Speaking of testing, when time and resources allow, using a combination of unit, integration, and end-to-end tests to cover the most essential features and functionality can be useful in large projects, preventing bugs and breaking changes.

- Identifying the need for architectural change, e.g., breaking things up into microservices, or using a specific architectural pattern (e.g., event-driven architecture) is also important if you're dealing with large and/or data-heavy systems.

- To visualize the structure and dependencies of your code, you can use a variety of tools, either external or built into your IDE. I've never had to use anything beyond the built-in ones — these diagrams are much more useful to have before you start writing code.


👤 slater
struc...ture...?