Hello all, long time no see. As my projects grow and start to use more and more dependencies, like classes from other projects and so on. How can I keep my projects more structured and modular?
I keep finding myself revisiting old classes to incorporate new behaviour and breaking old behaviour that other projects dependent on.
Any sage advice on how to structure my data so that I can make my classes more self contained and universally useable
Good question! These are my personal values when it comes to structuring code
When you build a class make sure you clearly define what API is “public” (i.e., to be consumed by other projects) vs internal (used by the library itself). One way to clearly separate public vs internal API is to create two files in your dependency for the class:
MyClassInternal — you can define your internal functions in the other file
By doing this you can make it easier to read and understand your dependency’s code because the public API will not be interspersed with unrelated code. This may make it more apparent when you go to make a change that it could possibly impact other projects
Consider the single responsibility principle. Try to keep your classes (and possibly your entire dependencies) focused on a singular task. If they are doing too many things, consider breaking them into separate, clearly defined modules. By doing this you minimise the amount of interdependencies you have between unrelated code, you make it easier to maintain the dependencies independently of one another, and they can be used separately in your projects (meaning you will have to update fewer projects when you update the public API in one of your modules)
While tests won’t stop you from making breaking changes, they will catch them early. If you have a test project for each dependency that you can run after making changes, that will allow you to see how things have broken and update your code accordingly. You might even include this in the
Main tab for the dependency itself
State and Side Effects
State: does your class hold onto some data? Does it need to? If it doesn’t need to (i.e., the data can be computed on access) get rid of it — you basically have a cache otherwise. And you should only cache something when you have measured the performance impact of not having a cache and it is causing a significant issue
Side effects: try to keep your functions focused on taking their input and producing output. A pure function, a function which does not manipulate external state, is much easier to test and reason about
I’m a fan of long names. I prefer a function called
updateStateAndNotifyListeners to something like
update. I like to know at a glance what the entire purpose of a function is, and if it will impact anything beyond the lifetime of its execution
Does anyone else have strategies they adopt to keep things maintainable?