On composability in software design
Some short notes on composability to clear my thoughts.
This is not related to composable infrastructure.
- Code should either:
1) Organise composable parts; or
2) Be a composable part1 - Functions are composable when they can be combined freely
foo(bar()) foo(bar() + bar()) foo() - bar(baz())
- Classes are composable when they are interchangeable, using interfaces
new Foo(new Bar()) new Foo(new Baz()) foo(new Bar() + new Baz())
- It’s OK to not be composable if you’re at the top of the stack trace, e.g.
commandObject.run()
or a controller methodfunction run() { foo() bar(baz()) }
- Being composable means having a clear relation between input and output, preferably stated as a contract
/** Returns an order object with recalculated vat based on country */ function recalculateOrder(order, country): order { // ... }
- Side-effects2 decrease composability, move them up in the stack trace when possible
// Not good function processOrders(orderIds) { orders = getOrders(orderIds) // ... } // Better, fetch at call site instead, like processOrders(getOrders(orderIds)) function processOrders(orders) { // ... }
- Implicit dependencies decrease composability, make them explicit either with injection or as function arguments
// Not good function foo() { event = new Event("bar") // ... } // Better, like foo(new Event("bar")) function foo(event) { // ... } // With an injected factory function foo() { event = this.eventFactory.make("bar") }
- Composable parts are easier to unit test
- Developing composable parts is like constructing a language3
- Code consisting of composable and interchangeable parts are easier to change, both for bug fixes and feature requests
- Composability is domain specific, no one would expect a string library to be composable with mathematical functions
- Writing to a class property decreases composability, prefer returning a value when possible
- You can never have 100% composability due to cross-cutting concerns
- There’s no established code metric to measure composability
- When you have code clones, there are composable parts to factor out (excluding false positives)
- The expressiveness of a programming language sets limits on what you can combine and compose
- There’s little or no empirical evidence related to composability and changeability.
-
Writing/reading to/from IO, file, database, etc ↩