Graduate Program KB

Clean Code

Chapter 11

Separate Constructing and Using a System

  • Software systems should separate the startup process

    • When application objects are constructed and the dependencies are "wired" together
    • The runtime logic that takes over after startup
    • The following example has the startup process mixed in with the runtime logic
    public Service getService() {
        if (service == null) {
            service = new MyServiceImpl(...); // Lazy initialisation
        }
    
        return service;
    }
    
  • However, MyServiceImpl is now a hard-coded dependency

    • Can't compile without resolving these dependencies
    • Testing is an issue if MyServiceImpl is a heavyweight object
    • Need to ensure the test object is assigned to the service field before the method is called during unit testing
    • Violating SRP by testing all execution paths, since there's construction logic mixed with runtime processing (testing null and block paths)
  • A simple solution is to move all aspects of construction to the main function

  • Easy to identify flow of control, objects are constructed and passed to the application which creates a uni-directional dependency between the main and application barrier

Dependency Injection

  • Design pattern that facilitates loose coupling and improves testability
  • Dependencies are "injected" into a class rather than being created or managed internally
  • The application of Inversion of Control, moving secondary responsibilities from an object to other objects dedicated to the purpose, supporting SRP
  • DI containers manage instantiation and wiring of dependencies, reducing the need for explicit instantiation in client code
  • True DI is completely passive, the invoking object provides setter methods and/or constructor arguments that are used to inject dependencies

Cross-Cutting Concerns

  • Cross-cutting concerns are features that impact multiple parts of the system
  • Examples include logging, security and transactional management
  • We can handle these concerns using aspects, modules or other framework libraries providing support for managing concerns to prevent duplication and ensure consistency

Java Proxies

  • Java proxies help developers modularise cross-cutting concerns and separate them from the core application logic
  • Allow us to create objects that intercept method invocations and perform additional logic before or after forwarding the invocation to the target object
  • Suitable for simple scenarios, such as wrapping method calls in individual objects or classes
  • However, dynamic proxies in JDK only work with interfaces
    • Dynamic proxies are created at runtime and require the target object to implement at least one interface
    • Static proxies are created at compile time by explicitly defining a proxy class that implements the same interface as the target object

Pure Java AOP Frameworks

  • Most proxy boilerplate can be automated by tools
  • Proxies are used internally in several Java frameworks, such as Spring AOP and JBoss AOP to implement aspects
  • In Spring AOP, you write business logic as Plain-Old Java Objects which are purely focused on their domain
    • POJOs don't have dependencies on enterprise frameworks or any other domains
    • Conceptually, they are simpler and easier to test

AspectJ Aspects

  • The most full-featured tool for separating concerns through aspects
  • An extension of Java that provides "first-class" support for aspects as modularity constructs
  • The pure Java approaches provided by Spring AOP and JBoss AOP are sufficient for most cases where aspects are useful, but AspectJ has more diverse tools
  • The drawback of AspectJ is the need to adopt several new tools and learn new language constructs as well as usage idioms
    • Though, adoption issues are partially mitigated by introducing "annotation form"
    • Java 5 annotations are used to define aspects using pure Java code

Test Drive the System Architecture and Optimise Decision Making

  • An optimal system architecture consists of moduralised domains of concern, each of which is implemented with POJOs

    • The different domains are intergrated together with minimally invasive Aspects or tools
    • This architecture can be test-driven, just like the code
  • Modularity and separation of concerns make decentralised management and decision making possible

    • No one person can make all the decisions, whether in a city system or software project system
  • At all levels of abstraction, the intent should be clear

  • When designing systems or single modules, always use the simplest thing that can possibly work