The debate over classes vs functions is not just about keywords but more about constructing clear boundaries around behavior and dependencies.
Defining constructors, either as functions or classes, helps in creating clearer boundaries around configuration and collaborators for testable and maintainable systems.
Dependency injection, through constructors or factory functions, allows for more composable and testable code with explicit construction and injection of dependencies.
By constructing boundaries explicitly, both class-based and factory-based loggers provide clean, testable APIs and flexibility in managing instances.
Explicit construction and dependency injection enable cleaner separation, environment-specific wiring of dependencies, and easier testing in modular architectures.
The upfront cost of setting up patterns like constructors and injectable utilities is low and pays off in flexibility, clarity, and ease of change as the system scales.
Java and Go also emphasize constructing with dependencies and wiring behavior explicitly, avoiding ambient state and promoting composable code.
The focus should be on constructing boundaries deliberately, whether using classes, factory functions, or other patterns, to improve adaptability and maintainability.
The key is not the class keyword but the intentional construction of boundaries and clear separation of concerns to make the codebase adaptable and easier to reason about.
Whether using classes, factories, or other constructs, the essence lies in constructing boundaries effectively to enable composability and maintainable code.
Designing code for flexibility and composability, rather than overengineering for uncertain future features, leads to more manageable and adaptable systems.