The design (sometimes architecture) of a non-trivial program's source code and build is replete with problems. Multiple books are written on the matter every year. The code goes into many different files which are stashed in multiple folders. If you're paying attention the folders represent elements of a modular build. Mostly.
Architecture level problems come in lots of kinds, but the ones I want to focus on today are excessive interconnectedness of logical units, and excessive interconnectedness of build-time units. The former makes the code hard to reason about because when I'm look at a bit of code here I may not know if some bit of code in a different folder is going to reach in and change something when I least expect it. The latter driving up build times. Both can make changes that looked initially confined to a single file cause adjustments in many other files.
So now we have two things to keep in mind:
- The organization of files in the file-system should parallel the build organization.
- File should know as little as possible about other code units and especially about units in other build modules.
So, when one bit of code needs to know a lot about another bit of code it goes nearby. Maybe in the same file, and almost certainly in the same build module. Fine.
Now consider the "extract function" feature of your programming environment.1 It's a shining example of good tooling as it encourages and helps with refactoring. But it usually crashes me right out of the zone. Because if I'm creating a new "piece" of code and I have to find it a home. And that can be hard.
Some cases are easy.
- If it's coming out of object's instance function (AKA method), and it
needs to access instance state then is must live in the object.
- If it does not respect the class invariants then it must be private. Except that if it is virtual then you may want it to be protected2
- Otherwise it can be public. But start with it private until you know of a use case: every API you expose is one you have to maintain.
- If it is reaching into another class's object, then consider moving it to that class.
But after that it starts getting tricky. I mean, it can be a free function or a class static, but where should it live?!? If it is highly specific, perhaps keep it in the file it's in (and perhaps even private), just with a name now. If it represents a specific behavior within the domain modeled by this module, make it it accessible throughout the module (new files, class static, who knows?). If it's very general purpose, first look to see if you missed it in the standard library or any frameworks you're using, and if not put it in your project wide utility code. Maybe?
In any case, having to make these decisions often knocks me out of my flow. And since it doesn't have a name that I know of I haven't been able to google advice.
1 Obviously you don't actually need a tool for this. I got on fine with cut-n-paste for decades. Still do when certain IDEs (no names, Qt Creator you .... you ... wonderful program) just refuse to have it enabled for some mysterious reason. But I will sure as heck use the tool if present.
2 I'm largely using the C++ nomenclature here because that's the sea I swim in daily, but I think the considerations apply more broadly.
No comments:
Post a Comment