|
I've been working in Clojure for the last few years, and what I learned is that the trick is to reverse the data dependencies, so that instead of your function asking: "What is a "person" and what attributes does it have if an object?". You have your function declaring: "I take a person as a map of keys :name and :age". And it is the caller who needs to ask itself: "What am I supposed to provide to this function?" This is a very different mindset, but once you adopt this style, the lack of static types isn't as big an issue. The reason you can do this in a dynamic language is that you can very easily adapt one structure to another, so its okay if not all your functions work directly on the same shared structures. It also has the advantage that this style really favors making modular independent granular components that can be reused easily, because they aren't coupled to an application's shared domain structures, but to their own set of structures, creating a natural sub-domain. There are other aspects to make this style work well, like keeping call-stacks shallow, and having a well defined domain model at the edge of your app with good querying capabilities for it. Concretely it means say you need to add some feature X to the code, you might think, ok this existing function is one place where I could add the behavior, but for my new feature I need to have :age of "person", but I don't know if the "person" argument of this existing function would contain :age or not. Dammit, I wish I had static types to tell me. Well, in this scenario, instead, what you do is that you don't add the behavior to that function. Instead, in my style you would have: A -> B
A -> C
instead of: A -> B -> C
That means if after B is the right place for your logic, you don't do: A -> B -> B' -> C
And hope that the "person" passed to B had the :age key which is needed by B'.Instead you would do: A -> B
A -> B'
A -> C
And when you implement B', you don't even care about "person", you can just say you need person-age, or that you need a Person object with key :age (which you don't care if it is the Person object shared in other places or not).Finally, you modify A, where A was the function that creates the Person object in the first place, it has direct access to your actual database/payload and so finding whatever data you need is trivial in it. |