This episode talks about more in-depth on how to keep test clean.
- Anatomy of a Test
- Four Phases of test function. 4 As of Test: Arrange, Act, Assert, Annihilate
- Arrange:Act:Assert, Build:Operate:Check, Given:When:Then
- Arrange: Set the test to the environment to run the test. Create test fixture.
- Act: Call the function we want to act upon.
- Assert: Check the expectation.
- Annihilate: Put the test environment back to the original state.
- The Arrange
- Drive the test to the state (test fixture) it needs for testing
- Transient Fresh – create and destroy around every test
- Persistent Fresh – allow to exist between test, initial around every test
- Persistent Shared – allow to exist between test, allow some states to carry over to other test
- Setup Struggle
- What happen if setup grows?
- Test Hierarchy
- Test group into Hierarchy so setup only specified in those test needed
- Clean Composition
- Action is the thing that you are testing
- It is a good idea to put two or more actions into an utility function so the test looks like testing one actions
- The Assertion
- Test is a boolean operation
- Single Action follow by Single Assertion
- More than one assertion, then composition it into one well name assertion.
- xUnit Test Patterns, Gerard Meszaros
This episode is a two parts serie on a deep dive into the test-drive development.
- TDD Review of the Three Laws
- Before production code, must write unit test to fail
- Stop writing the test as soon as you got a failure
- Don’t write more production code than you need to pass the test
- Red Green Refactor
- The Single Assert Rule
- Every unit test should only have one assert
- Every action should have one assert.
- Incremental Algorithmic
- As the test gets more specific, the code gets more generic.
- Getting Stuck
- Should create degenerate test case first. Approach from outside in.
- Getting Unstuck
- First, make most degenerate test case.
- Solve the problem using test case with incremental complexity
- For every failed test, we make it pass by generalize the production code
- Don’t make specific with the code
- Test can only prove the program wrong and never prove the program right
This episode reviews the component principles.
- Payroll Review
- Controller use Builder to request data structure that they pass to interactor that they acquire from Factory. The interactor manipulate entity and database which create response data structure. The response data structure pass into Presenter which create view model data structure, which driven into view.
- Ad Hoc Packaging
- High level abstraction should not depend on low level concrete details
- No cycle
- Concrete components depends on abstract components
- Unstable components depends on stable components
- Families and Metrics
- Separating use cases and component that doesn’t belong together
- Agile Software Development, Robert Martin
This episode talks about how components should depend on each other.
- Acyclic Dependencies Principle
- Stable Dependencies Principle
- Stable Abstractions Principle
- The Acyclic Dependencies Principle
- Cyclic module cause those module to force to release at the same time
- Solution is either split the component out or use dependency inversion
- The Stable Dependencies Principle
- Stability is something that is hard to change.
- A component should depend on something more stable
- Stability correlates with number of incoming dependency and inversely correlates to number of outgoing dependency.
- The I metrics
- I metrics = Fan-out / ( Fan-in + Fan-out )
- All dependency arrow point toward decreasing I.
- The Stable Abstractions Principle
- Use open-close principle for the module at the bottom of the dependency.
- The more stable a component, the more abstract it should be.
- Abstract number = number of abstract classes / number total classes
- A + I = 1
- D = | A + I | – 1
- Zone of uselessness is where there many abstraction but nothing using it
- Zone of pain is there are too many concrete classes depend on each other
- Distance metric between zone of uselessness and zone of pain
This episode talks about how components should work together.
- Release Reuse Equivalency Principle
- Common Closure Principle
- Common Reuse Principle
- What is inside a component? The pieces is functions and the force to bind them is cohesion
- False Cohesion
- A group class work together to achieve a common goal is not a good reason to bind them together as a component.
- Base class and their derivative are often package into separate component, with class that use base class package with the base class.
- When one class polymorphic uses another class, those classes should be separate into different components.
- Goal is interdependent deployability
- Release Reuse Equivalency Principle
- The granulate of reuse is granulate of release, you can’t reuse a component unless the author is willing to manage it thru a release cycle
- You want a few strategic components instead of managing hundreds of tiny components
- Common Closure Principle
- Component don’t cross boundary
- When requirement change, best outcome is one component per layer changes.
- Minimize the number of component change when requirement changes.
- The classes we grouped together should be closed to the same kind of changes.
- We gather the classes that change for the same reason and separate the classes that changes for different reason.
- This is similar to interface Segregation Principle
- Those class that group into a component should have same responsibility, serve the same actor.
- Common Reuse Principle
- Group together classes that are using together, separate classes that are not. In another word, when you use a component, you use all the classes within that component.
- The Tension Diagrams
- CRP & REP → component affected by many responsibility
- CRP & CCP → component aren’t reusable
- CCP & REP → component are needlessly affected
- As project mature, it moves from CRP & CCP to REP.
- Component partitioning changes with project maturity
This episode gives an overview of the component principles.
- What is a Component
- Relocatable Module
- The creation of linker to link the subroutine library and create executable binary
- Explosion of Libraries
- Application call subroutines
- Framework call applications
- Dependency inversion of Framework and Application
- Framework → flow of control → Application
- Application → source code dependency → Framework
- Linker’s Demise
- Component is an independent deployable library (dll, gem, or jar)
- Independent deployable mean change in one doesn’t cause another to recompile or redeploy.
- The key is Application depends on Subroutine and Framework but not the other way around.
- Coffee Maker Requirements
- void setBoiler(bool);
- void setWarmer(bool);
- void setValve(bool);
- void setLight(bool);
- bool getBoiler();
- int getPlate():
- bool getButton();
- The Architect’s Solution
- A Real Design
- Apply Single Responsibility Principle first
- Who is the actor?
- Brewer, Hot Drinker, Now Drinker
- High Level Modules
- Three Actors means at least 3 modules. One Module per Actor
- Describe abstract purpose of component because High level module should not depend on low level details.
- Methods and Relationships
- Examine the methods and relationships of the modules by moving up and down the abstractions.
- First, the actor send a start message to UI module.
- Then, if UI not brewing? and HotWaterSource & ContainmentVessel ready? then send start message to HotWaterSource
- This shows there is a start method for UI and HotWaterSource and ready?method for HotWaterSource and ContainmentVessel.
- Brewing Begins
- When the pot is removed, ContainmentVessel send suspend message to HotWaterSource
- When the pot is replaced, HotWaterSource send resume message to ContainmentVessel
- The take away is that message being send between module on high level abstraction
- Use Open-Close Principle
- Creating a derivative for each of the modules.
- Main call the derivative.
- All the dependency crossing from main to the application is pointing inward to the application
- Benefit from good component design: Interchangeability, Interdependent deployability, the physical separation from high level policy to low level details.
- Component Cohesion
- Component Coupling
This episode reiterates all five SOLID principles.
- Requirement and Use Cases
- Use Case List (action)
- Entity List (subject)
- Building the data dictionary to support the requirement
- The Single Responsibility Principle
- Looking at which module belong to which actors
- Diagrams and YAGNI
- Diagram is usually using by communicate thought process and become obsolete
- YAGNI – You ain’t gonna need it.
- Separate each module to only one actor.
- The Open-Closed Principle
- Controller should not depend on the details of the data structure
- Using Builder, Factory, and Interface to decouple the controller from the data structure and use cases.
- The Liskov Substitution Principle
- Don’t put a method in an interface that does apply to all the derivatives.
- Only ok when all the methods of a derivative do nothing, then you have an null object pattern.
- The Interface Segregation Principle
- Violation indicated by the growing fan in from the controllers to the use case and the builder.
- Let the use case Factory makes the controller by passing the type of controller it will use. This is dynamically create the controller.
- The Dependency Inversion Principle
- High level policy should not depend on the low level details
- The algorithms should be generic without the details
- Use these principles but use engineering judgement.
This episode talks about the Dependency Inversion principle.
- Dependency Inversion Begins
- Independent Deployability
- What is dependency
- Run Time Dependency exist whenever two module interact at run time.
- Compile Time (Source Code) Dependency exist when a name is define in one module but use in another module.
- If Module A uses the name from Module B, then you can’t compile Class A only, you must also compile both A and B because Class A depends on Class B.
- Structured Design
- A top-down methodology by start with main and design what main should call and so on down the tree.
- The source code dependency follows the same path as the run time dependency.
- Dependency Inversion
- The purpose is to have source code dependency going the opposite of the run time dependency path.
- The solution is polymorphism.
- A –> B [+f()], if A call f() in B, then A has both run-time and source-code dependency on B.
- A–> Interface[+f()] <– B [+f()], insert an interface between A and B by having A uses the Interface while B implement it. Then A has run-time dependency on the Interface but not compile time dependency. And B has source-code dependency on the Interface.
- Dependency Inversion create a boundary for the software. We want all the dependency across the boundary are pointing to the same direction, the application.
- This is by creating plugin that depend on the application.
- Architectural Implications
- High level policy should not depend on low level details
- Low level details should depend on high level policy.
- High level like use cases.
- Low level like web pages and database.
- Therefore, design the low level details as plugin to the application.
- A Reusable Framework
- Building the reusable framework should be done parallel between those application that uses it.
- The Inversion
- Independent Developability
- Developers can work separately without interfere each other’s module.
- Dependence Inversion Example
- The Furnace Example
This episode talks about the Interface Segregation principle.
- Example: A light switch
- switch —>> Interface <|—- light
- The interface is couple with the switch more than the light object. Should call switchable.
- Interfaces have more to do with the class that use them then the class that implement them.
/--- D1 <---\
B (v) <---| |--- M
\--- D2 <---/
- The Interface Rant
- Deadly diamond of death
- M could either have one instance variable v or two coming from each D1 and D2.
- Fat Classes
- The main class multiple inheritance many interface classes. Isolate fat classes by isolated each client specific interfaces and deploy them separately and able to develop separately.
- The ATM example
- Physical Structure
- Ensure you don’t depend on method that you don’t call, otherwise, unnecessary coupling.
- Dynamics and Injection
- Dependency Injection
- Main should create the messenger instance, all of the interactors, and main should takes the interactors and pass them across the boundary to the application.
- The Need to know
- Don’t force the user to depend on thing that you need
This episode talks about the principle govern inheritance, polymorphism and subtyping.
- Type Theory
- “This statement is false” is a paradox.
- Solution is typing to avoid paradox loop.
- What is a Type?
- A Type is a bag of operations and how the data is stored is not matter to us as long as the operation is done correctly.
- Similar to a class, a class is nothing but its methods and the data is hidden as private.
- A subtype relationship of described point as a subtype of point is when described point can be cast into point but not visa versa.
- Liskov & subtypes
- What is wanted here is something like the following substitution property: If for each object 01 of type of S there an object 02 of type T such that for all programs P defined in term of T, the behavior of P is unchanged when 01 is substituted for 02 then S is a subtype of T.
- Duck typing
- Invoking method vs Send Message (Static vs dynamic language)
- Refused Bequest
- Refused Bequest is when an object invoke a method or send a message that is not expected.
- Exception and side effect by the subtype that the base type didn’t expect
- The Representative Rule
- Rectangle and subtype Square with setHeight and setWidth vs setSide
- Refused Bequest!
- When program setHeight on an object of square, there is an undefined behavior of how to handle a setHeight in a square.
- Latent OCP Violation
- Dependency will be created when fixing the problem by checking if a Rectangle is a Square in the program. This is a violation of Open Close Principle.
- Treat square and rectangle completely different type.
- The Representative Rule
- Representative does not share the same relationship of the objects that they represent
- Integer is subtype of Real and Real is subtype of Complex
- However, complex number has two parts of Real number, one for the real value, and the other for the imaginary.
- In programming, this complex and real relationship will create a recursive definition similar to the paradox at the beginning of the episode.
- This is an example where real world model does not work with a computer representation
- If S is subtype of T, List of S is not a subtype of List of T
- For example, a Circle is a subtype of Shape but List of Circle is not a subtype of List of Shape because a List of Shape could include Rectangle, Square and other Shapes.
- If base class does something, the derived class must do it too in a way that does not violate the expectation of the caller. This is appearance when a derived class throw an unconditional exception for a method or override with an empty method that the base class implemented.
- Another indicator is the usage of if instanceof is being used.
- Only use instanceof when you know the type to help the complier.
- The use of if instanceof can lead to additional else if instanceof and should replace with polymorphic dispatch.
- Statics vs Dynamics
- Dynamics language use TDD to help with type checking
- Design by Contract
- invariant, precondition and postcondition.
- The Modem Problem
- Long Distance Rigidity
- Adapter is derive from the Modem and delegate to the Ded Modem in the example.
- If you need an ugly hack, make sure to isolate it from the system by pointing all the dependency away from it.
- The Annotated Turing by Charles Petzold
- Advanced C++ Programming Style and Idiom by James Coplien
- Refactoring: Improving the design of existing code by Martin Fowler
- Object-oriented Software Construction by Bertrand Meyer