Live data from Hacker News

An Experiment on Code Structure

pboyd.io

11–20 of 62 posts

Re: An Experiment on Code Structure

#11
post #10

Earlier quoted context omitted.

Splitting things up into multiple independent translation units enables incremental compilation. One function per file is the most extreme version of this. For example: https://git.musl-libc.org/cgit/musl/tree/src/stdio

That seems like a problem for compiler optimizers to solve, not programmers.

Multiple files also seems like a problem for IDEs to solve, not programmers.

Re: An Experiment on Code Structure

#12

According to GitHub, the totals are: backendA: 11 files, 1 directory, 799 lines (676 sloc), 23.56KB backendB: 23 files, 5 directories, 1578 lines (1306 sloc), 42.26KB It's approximately twice as big for the same functionality, and I had to spend a lot more time "digging" through the second one to get an overall idea of how everything works. Jumping around between lots of tiny files is a big waste of time and overhead…

> The excessive bureaucracy of Enterprise Java (and to a lesser extent, C#) leads to even simple changes requiring lots of "threading the data" through many layers. I've worked with codebases like that before, many years ago, and don't ever wish to do it again.

Yeah I tend to like something like a semantic compression approach: I'll start in a single file, and then split it into separate files organized by domain as the length of the file starts to get unwieldy. And so on into more files and later subdirectories as the program grows.

In my opinion it's much better to let the "needs of the program" dictate code and filesystem structure rather than some academic ideas about how a program should be organized. As you say, when I've worked on projects which are very strict about adopting a particular structure, a lot of time ends up being wasted figuring out how to map my intent to that structure rather than just writing the damn code.

Re: An Experiment on Code Structure

#13

According to GitHub, the totals are: backendA: 11 files, 1 directory, 799 lines (676 sloc), 23.56KB backendB: 23 files, 5 directories, 1578 lines (1306 sloc), 42.26KB It's approximately twice as big for the same functionality, and I had to spend a lot more time "digging" through the second one to get an overall idea of how everything works. Jumping around between lots of tiny files is a big waste of time and overhead…

When we are talking about 500-1500 sloc I completely agree this kind of structure is overkill. But when dealing with medium to large codebases (anything beyond, say, 100kloc) I much prefer the second approach, bonus points if you can get a fractal-like hierarchy.

Digging through files manually (I.e. Using a mouse) is painful, but your IDE is your friend. It takes me less than 3 seconds to search and open any file of the codebase I currently work in (it has a bit more than 2k files). And having a sane hierarchy means I type the folder / file name as I remember it, and filter the search results on-demand.

Re: An Experiment on Code Structure

#14
“I’d really like to get away from the opinions and be able to say with confidence that one design is better than another. Or, at the very least, understand the trade-offs being made.” As I’ve taken more leadership in architectural decisions, this is one of the skills thats helped the most. Having most of the data regarding tradeoffs before making a commitment has steered projects from disaster.

Re: An Experiment on Code Structure

#15
post #6

Earlier quoted context omitted.

I’ve come to the conclusion that half or more of the rules we have about “clean” are about avoiding merge conflicts. Few things have been consistently disappointing to me as the inability of coworkers and myself to reason about merges correctly. There are three hard things in software and merges are #3. If anyone ever figures out how to make merges Just Work, then I expect a lot of pressure toward decomposition over…

Can you elaborate a little please? It's unclear to me if you are taking about merging data, code changes, or something else

I’m assuming it was a reference to merging in source control. A lot of “noise” in diffs, and by extension in merges and the sometimes awkward job of resolving merge conflicts, comes from little details like whitespace and punctuation rather than substantial semantic changes in the code. Many a coding standard, and even a language change from time to time, has been made with this in mind, sometimes to the point of putting punctuation in odd places or avoiding aligning items using extra whitespace just to minimise the number and/or size of diffs to check.

Re: An Experiment on Code Structure

#16

According to GitHub, the totals are: backendA: 11 files, 1 directory, 799 lines (676 sloc), 23.56KB backendB: 23 files, 5 directories, 1578 lines (1306 sloc), 42.26KB It's approximately twice as big for the same functionality, and I had to spend a lot more time "digging" through the second one to get an overall idea of how everything works. Jumping around between lots of tiny files is a big waste of time and overhead…

I really don't get this fetish for lots of tiny files and nested directories, which seems to be a recent trend;

I suspect it is the same kind of thinking that says all functions should be very small (without reference to whether each function provides a single meaningful behaviour). Locally, this keeps things relatively simple, but it ignores the global issue that now there are potentially many more connections to follow around and everything becomes less cohesive. As far as I’m aware, such research as we have available on this still tends to show worse results (in particular, higher bug frequencies) in very short and very long functions, but that doesn’t stop a lot of people from making an intuitive argument for keeping individual elements very small.

A similar issue comes up once again in designing APIs: do you go for minimal but complete, or do you also provide extra help in common cases even if it is technically redundant? The former is “cleaner”, but in practice the latter is often easier to use for those writing a client for that API. Smaller isn’t automatically better.

Re: An Experiment on Code Structure

#17
post #6

Earlier quoted context omitted.

I’ve come to the conclusion that half or more of the rules we have about “clean” are about avoiding merge conflicts. Few things have been consistently disappointing to me as the inability of coworkers and myself to reason about merges correctly. There are three hard things in software and merges are #3. If anyone ever figures out how to make merges Just Work, then I expect a lot of pressure toward decomposition over…

Can you elaborate a little please? It's unclear to me if you are taking about merging data, code changes, or something else

Code changes are adding or correcting behavior. A lot of coding practices tend to help two things: reading comprehension and keeping developers from bumping into each other. Adding code to the same areas and then having to handle merge conflicts without introducing regressions. It’s much simpler to segregate the code into separate concerns so that new features do not intersect.

But too much decomposition also hurts reading comprehension. So if the specter of merge conflicts went away you’re left with readability, which will settle out to somewhere between the extremes of decomposition. I’m suggesting that would result in somewhat larger methods. Especially where crosscutting concerns intersect each other.

Re: An Experiment on Code Structure

#18
How does your code structure help you against these situations:

- Version control conflicts: if developers are editing the same files all the time, there will be more conflicts and therefore more tasks related to resolve them, such as merging, re-testing, fixing bugs related to a bad merge, re-attempting the merge, etc.

- Code so complicated that becomes easy to misunderstand, and a source of an unusually large amount of bugs.

- Code so complicated that cannot be reliably tested without spending an unreasonable amount of time or relying on opaque testing methods.

- Code so complicated that increases the dependency on specific team members, usually the authors, so that the team cannot function optimally if they're unavailable or unwilling to collaborate.

- Code so complex that is impossible for an engineer to determine if the system is in a healthy state, diagnose a problem, obtain a reproduction step from a bug report...

- Code so poorly organized that developers fail to find implementations for a particular problem, causing them to implement the same thing again.

- Having multiple variations of the same code, so when a bug is found you may have to refactor multiple versions of the same code to fix the problem, if you manage to find them all.

And the list goes on and on. And a solution to these problems can have to do with how code is structured, and conventions/good practices.

If I see a piece of code that needs to know about 40 classes and 50 methods to produce a result, I know that it is likely going to be a pain to maintain. It's not subjective.

If I see a function with 1000 lines of code and a cyclomatic complexity of 500, I know that it may take at least 500 test cases to test it and will be a pain to maintain in a way that doesn't break. That is not subjective.

Re: An Experiment on Code Structure

#19
These days, when I start a new project, I think of my code as a tree. I start at the trunk and write the branches.

Each kind of state change needs to flow through the code in a consistent direction to avoid unexpected state mutations (like sap flows through a tree).

Another developer should be able to understand all the main parts of my program just by looking at the main entry point/file (the trunk of the tree).

Also, no dependency injection should be used; all dependencies need to be listed explicitly and be trackable to its source file. Dependencies need to either be explicitly imported where they are used or passed down through the branches explicitly via method or constructor arguments. Traceability is very important.

About classes/abstractions, they should be easy to explain to a non-technical person. If you can't explain a class or module to a non-technical person, it shouldn't exist because it is a poor abstraction.

Re: An Experiment on Code Structure

#20

These days, when I start a new project, I think of my code as a tree. I start at the trunk and write the branches. Each kind of state change needs to flow through the code in a consistent direction to avoid unexpected state mutations (like sap flows through a tree). Another developer should be able to understand all the main parts of my program just by looking at the main entry point/file (the trunk of the tree). Als…

> Also, no dependency injection should be used; all dependencies need to be listed explicitly and be trackable to its source file. Dependencies need to either be explicitly imported where they are used or passed down through the branches explicitly via method or constructor arguments.

Isn't the latter precisely dependency injection?

https://en.wikipedia.org/wiki/Dependency_injection#Construct...

Post reply on HN