Notes about the Agile Software Development book
These are my notes to the Robert Martin's book Agile Software Development: Principles, Patterns and Practices.
Don't immediately buy the priciest database there is, but try the flat files first. The same goes for VCSs. Look at the tools with the jaded eye.
The first Law of Documentation: Produce no document unless the need is immediate and significant.
Fixed-time fixed-price projects don't work. The best contracts are those which specify how customers and developers should work together.
We should plan only weeks ahead. We should have rough plans for the months ahead and almost no plans for years ahead.
The less functionality the initial release has, the more quality the final product will have. Progress is measured in software done: 30% done means 30% of functionalities are working.
During initial exploration, the team explores the first user stories. It will give rough relative estimates about the implementation time for the user stories. If a user story is too small, it should be merged with other user stories. If it is too big, it should be split. As the team will learn to know its velocity, the relative estimates will become more and more absolute and accurate.
During iteration planning, the team decides on iteration size, typically 2 weeks. Then the business people can choose which user stories to put in there based on team's velocity. The list of user stories for the iteration cannot be changed. The iteration ends after 2 weeks even if the user stories are not done. The team's real velocity is computed and it is used for next iteration planning.
Any task can be picked by any developer regardless of their specialty. We want the know-how to spread as much as possible. The iteration ends with a demo for the customer. The customer provides his feedback on look, feel and performance of the application.
Chapter 1 - Agile Practices
Don't immediately buy the priciest database there is, but try the flat files first. The same goes for VCSs. Look at the tools with the jaded eye.
The first Law of Documentation: Produce no document unless the need is immediate and significant.
Fixed-time fixed-price projects don't work. The best contracts are those which specify how customers and developers should work together.
We should plan only weeks ahead. We should have rough plans for the months ahead and almost no plans for years ahead.
The less functionality the initial release has, the more quality the final product will have. Progress is measured in software done: 30% done means 30% of functionalities are working.
Chapter 2 - Overview of Extreme Programming
Pair programming does not decrease the efficiency of the team, but it dramatically improves the defect rate. Also the know-how spreads much more rapidly.
The software process is not a sprint. It is a marathon. The team is not allowed to work overtime. The only exception is the last week before the release, when the overtime is permissible.
Chapter 3 - Planning
During initial exploration, the team explores the first user stories. It will give rough relative estimates about the implementation time for the user stories. If a user story is too small, it should be merged with other user stories. If it is too big, it should be split. As the team will learn to know its velocity, the relative estimates will become more and more absolute and accurate.
During iteration planning, the team decides on iteration size, typically 2 weeks. Then the business people can choose which user stories to put in there based on team's velocity. The list of user stories for the iteration cannot be changed. The iteration ends after 2 weeks even if the user stories are not done. The team's real velocity is computed and it is used for next iteration planning.
Any task can be picked by any developer regardless of their specialty. We want the know-how to spread as much as possible. The iteration ends with a demo for the customer. The customer provides his feedback on look, feel and performance of the application.
Chapter 4 - Testing
Unit tests are white-box tests. TDD is primarily a design tool, than a documentation tool and after that a verification tool. It is a design tool, because if you write your test first, you will make your API convenient for caller. You will also make it testable, and therefore decoupled from its surroundings. We decouple code by programming to interfaces and using mock objects instead of uncomfortable surroundings, like databases, printers, etc. It is interesting that the need to program to interfaces came from the need to test the code.
Acceptance tests are black-box tests. They should be also automated and written before implementation. This has profound effects on system architecture. E.g. UI will be possible to bypass for the test purposes. This is beneficial for the architecture. It is advised to spend just an iteration for developing acceptance tests framework for business people and QA to work on. The architecture will be decoupled from the input sources and probably also from the system surroundings.
Chapter 5 - Refactoring
There are 3 functions for a code:
- The operation it needs to provide is the reasons the code exists.
- Adaptability, because every module changes given enough time.
- Communication, because it has to be understood by many other developers.
When refactoring, the IDEs will make things trivial. Most of the refactoring are just renames and functions extractions. Then the final reread is recommended to double-check the increased readability. This is basic stuff. Such refactoring must be done all the time. It is like cleaning dishes after dinner. The first few times you omit that, it will make your dinner faster. But overall, it will really only slow you down.
Chapter 6 - A Programming Episode
This chapter is about pair programming episode of bowling game score computation. Bob Martin and Bob Koss start with a example of bowling game score card, which can be considered sort of acceptance test. They use UML sketch for domain design.
Then they start testing the most independent domain object, the Throw. Soon they found out there is nothing much to test on Throw, as it has no behavior. It might even not exist. So they move up the dependency chain to the bowling Frame. They test that initial Frame score is 0. When they add a 5 to Frame, the resulting score should be 5. They noticed the simplest interface is add(int pins) functions instead of the Throw class and moved on.
They want to introduce linked list of Frames soon, so they continue by testing the Game score. The simplest implementation uses just a list of integers. Koss doesn't like the code and wants to refactor, but Martin wants to have all the scoring functionality in place first. So next they try to add a test for spare. The score of the 1st frame should be more than 10 in this scenario. They hack it in. The frame score is working but the total score isn't. They figure they need a getter for a current frame and then they can use current frame score as the total score. Everything is still hacked with array of integers.
They continue with the strike test. They add a few more if-s and the code works. They have a problem with the meaning of "11th current frame" after the game is finished, but ignore it. Everything seems to work. So they start with refactoring.
They refactor and refactor to make the code more readable. Koss wants a Scorer class and probably also a linked list of Throws. Soon they have a method which seems almost the same as a pseudocode for bowling rules. But it violates the SRP because it knows how to compute the score and also how to add a throw. So they extracted the Scorer object out of it. Then then refactored for readability some more.
In the end, they verified the code and tests readability and that there was nothing to remove from the tests. It was all good. Specifically then didn't design the thing in UML upfront. They didn't have any sequence diagrams or anything like that. There was no real object-oriented design. The Scorer was more about partitioning than OOD. They didn't need the Frame object. The result was as simple as reasonable.
Then they start testing the most independent domain object, the Throw. Soon they found out there is nothing much to test on Throw, as it has no behavior. It might even not exist. So they move up the dependency chain to the bowling Frame. They test that initial Frame score is 0. When they add a 5 to Frame, the resulting score should be 5. They noticed the simplest interface is add(int pins) functions instead of the Throw class and moved on.
They want to introduce linked list of Frames soon, so they continue by testing the Game score. The simplest implementation uses just a list of integers. Koss doesn't like the code and wants to refactor, but Martin wants to have all the scoring functionality in place first. So next they try to add a test for spare. The score of the 1st frame should be more than 10 in this scenario. They hack it in. The frame score is working but the total score isn't. They figure they need a getter for a current frame and then they can use current frame score as the total score. Everything is still hacked with array of integers.
They continue with the strike test. They add a few more if-s and the code works. They have a problem with the meaning of "11th current frame" after the game is finished, but ignore it. Everything seems to work. So they start with refactoring.
They refactor and refactor to make the code more readable. Koss wants a Scorer class and probably also a linked list of Throws. Soon they have a method which seems almost the same as a pseudocode for bowling rules. But it violates the SRP because it knows how to compute the score and also how to add a throw. So they extracted the Scorer object out of it. Then then refactored for readability some more.
In the end, they verified the code and tests readability and that there was nothing to remove from the tests. It was all good. Specifically then didn't design the thing in UML upfront. They didn't have any sequence diagrams or anything like that. There was no real object-oriented design. The Scorer was more about partitioning than OOD. They didn't need the Frame object. The result was as simple as reasonable.
Chapter 7 - What is Agile Design?
There are 7 design smells:
- Rigidity - the application resist change because every change causes many changes in the system.
- Fragility - changes to the system break the unrelated code.
- Immobility - it is had to decompose the system into parts reusable in other systems.
- Viscosity - doing thing right is harder than doing things wrong.
- Needless complexity - the design contains unnecessary infrastructure complexity.
- Needless repetition - application contains duplicated constructs which could be moved to an abstraction.
- Opacity - the system is hard to understand. It doesn't express its intent well.
It is the changing requirements what makes the systems rot. We as a software developers should recognize requirements as one of the most volatile things in the software world. So it is our fault if the software rots.
An agile approach to a requirement which changed is to modify the design of the software so it will be resilient to that kind of change in the future. Agile developers never let the application rot. There is no repeated "clean-up" event. They continuously improve the design to make it as simple, clean, and expressive, as possible.
An agile approach to a requirement which changed is to modify the design of the software so it will be resilient to that kind of change in the future. Agile developers never let the application rot. There is no repeated "clean-up" event. They continuously improve the design to make it as simple, clean, and expressive, as possible.
Chapter 8 - The Single Responsibility Principle
This principle was described by DeMarco and Page-Jones, who called it cohesion, i.e. relatedness of elements of a module. This is a slightly different axis on the problem. The responsibility is defined as a reason to change. If various parts of a class/module change for more than 1 reason, they should be probably split. If there are no such changes, they should be kept together. Otherwise we would introduce needless complexity.
Chapter 9 - The Open-Closed Principle
OCP was coined by Meyer: The software entities (functions, classes, modules, ...) should be open for extension but closed for modification. When we need to change behavior, we should add an extension, not modify the core code. We could use a Strategy pattern or a Template Method pattern for achieving this.
But how to choose in what way to close an entity? No entity can be close to all kinds of changes. Therefore the decisions should be strategic. Based on experience and common sense. And then we should still wait to see those change happening. When we see the same kind of change repeated, we should probably introduce an abstraction by applying OCP. Not sooner, because of needless complexity of any kind of abstraction.
Chapter 10 - The Liskov Substitution Principle
LSP states that sub-classes should preserve their super-class contract. Applying the IS-A rule when deriving classes can be faulty. It is the behavior which has to remain. Depending on a client, the Square class should not derive from Rectangle. A code smell is a derivative class which removes functionality from its super class. Such class probably doesn't conform to LSP. Another code smell is when a sub-class throws and exception which super-class didn't throw from the method.
Chapter 11 - The Dependency Inversion Principle
a. High-level modules should not depend on low-level modules. Both should depend on abstractions.
b. Abstractions should not depend on details. Details should depend on abstractions.
Chapter 12 - The Interface Segregation Principle
Many small interfaces serving different clients are better than one big non-cohesive interface. This way the clients can be more independent on each other.
Chapter 13 - Command and Active Object
Command seems to be the favorite design pattern of Uncle Bob. It is deceptively simple. It is represented by an interface with one method - do(), run(), or execute(). But it can be quite powerful. Its purposes include transactions, device control, GUI do/undo or even simple multi-threading. Command's constructor typically stores the data which the execute method uses.
The Active Object is the example of the multi-threading use of Command pattern. You create a linked-list holder object of commands called Active Object Engine. The commands are non-blocking commands, which either finish their job, or add themselves again at the end of the linked-list. This way a simple multi-threading can be implemented which needs little memory/resources.
The Active Object is the example of the multi-threading use of Command pattern. You create a linked-list holder object of commands called Active Object Engine. The commands are non-blocking commands, which either finish their job, or add themselves again at the end of the linked-list. This way a simple multi-threading can be implemented which needs little memory/resources.
Chapter 14 - Template Method and Strategy: Inheritance vs Delegation
Both Template Method and Strategy design patterns serve a similar purpose. They decouple algorithms from low-level details. While Strategy costs slightly more in complexity, it allows more reuse of the low-level details of the algorithm.
Chapter 15 - Facade and Mediator
Both Facade and Mediator design patterns serve a similar purpose. Facade hides a complex set of classes underneath, creating a policy, a convention to use, instead of using the classes underneath directly. The Mediator is much smaller policy between couple of objects which delegate their interactions to the Mediator.
CCP - Common-Closure Principle states that classes in one package should be closed to changes of the same kind. A change that affects a package affects all classes in the package and no other packages. I liked this principle much more than the first two. We should observe changes in the code and repackage accordingly.
ADP - Acyclic-Dependency Principle states that there should be no cycles in the package-dependency graph. I like this principle very much. There are several hacks how to get ADP working, like dependency inversion.
SDP - Stable-Dependency Principle states that we should depend in the direction of stability. It seems obvious. I wouldn't want to depend on a library which is less stable than my code.
SAP - Stable-Abstraction Principle states that the package should be as abstract as it is stable. It seems obvious. Libraries have more abstract code that the dependent codebases.
Overall I don't like the presented principles much. I liked CCP and the ADP for the practical hints what we we could do to make the codebases better. I don't see much real-world use for the other 4 principles.
We can even use Adapter pattern if we don't control the implementation class of the interface. We can create an Adapter which delegates to the class we don't control. But it comes with a price. We should only do this if it is necessary.
The principle of Proxy pattern is simple. For example, we create an interface of a Product class, change the original Product class to ProductImplementation and create a proxy ProductDBProxy. When client code works with Products, in reality, it works with ProductDBProxies. They fetch and deserialize ProductImplementations from the database and then delegate to them.
It sounds good in theory, but in practice, the code didn't look so neatly. Uncle Bob had to make some shortcuts in the getters, and some delaying in the one-to-many relationship of orders and products. So the pattern has its drawbacks, but it also separates the database details from the higher-level policy of e.g. calculating the order total.
Another presented pattern, the Stairway to Heaven is relatively unknown and only possible in languages with multiple inheritance. In the end, the Uncle Bob recommends starting with a Facade, even with too much coupling between the business objects and the database Facade. He would only refactor to Proxy if necessary.
Another pattern in the visitor hierarchy is the Decorator pattern. You want to add some non-cohesive functionality to a hierarchy of classes. Instead of adding it, you can create a wrapper class, which adds it and delegates everything else to the original class, i.e. a Decorator class.
Another pattern, Extension method was presented, but I find it's benefits to be too specific for a real-world scenario.
This first attempt to write a framework was for the first kind of the vignette. The most complex one, the Building design. It failed. It was very hard to reuse and they had to start from scratch. They decided to include only those things in the framework, which were reused in at least 4 different vignettes. The second attempt for a framework was a success.
The used a Template method for the core of the scoring algorithm. They did not choose Strategy, because it is more complex and was not worth it. They did use State pattern for the generated FSMs specific to each vignette. Even each action that the examined architect would take was a small FSM with most parts part of the framework.
Chapter 16 - Singleton and Monostate
Both Singleton and Monostate design patterns serve a similar purpose. Singleton is well known and limits the count of instances of a class to one. Monostate is a twisted pattern, an object, whose all fields are static. Therefore instances of it share state. Uncle Bob presents an example of using Monostate for finite state machine.
Chapter 17 - Null Object
Null Object is a design pattern when we return a dummy object instead of a null in case the searched object was not found. This null object does "nothing", where "nothing" depends on the method called. This way we can get rig of null checks.
Chapter 18 - Payroll Case Study: Iteration One begins
The chapter presents use cases to implement in the first iteration. We start with creating the employee. Although Uncle Bob is tempted to start with the database design, he wants to defer it as late as possible. Database is an implementation detail. We found that employees get paid by hour, or monthly or with commission. Also they can switch from hourly to monthly pay etc. This suggests that we use Strategy for the payment classification.
Uncle Bob finds an opportunity to use it by finding an underlying abstractions. Use cases tend to be lost too much in the details, so it is the designer who needs to find abstractions. In this case the abstraction is "all employees get paid". Another underlying abstraction are the salary payment schedule. It shouldn't be part of the payment policy. Yet another abstraction is the affiliation. Employees can be part of an union, which gets part of their salary.
Uncle Bob finds an opportunity to use it by finding an underlying abstractions. Use cases tend to be lost too much in the details, so it is the designer who needs to find abstractions. In this case the abstraction is "all employees get paid". Another underlying abstraction are the salary payment schedule. It shouldn't be part of the payment policy. Yet another abstraction is the affiliation. Employees can be part of an union, which gets part of their salary.
Chapter 19 - Payroll Case Study: Implementation
The author starts implementation with a test for creating an employee. He uses in-memory database fake, with hash-map of employees as a storage. He wants to defer decision about the database as late as possible, when he knows a lot more about the system. He uses Command and Template method patterns to implement CreateEmployeeTransaction and DeleteEmployeeTransaction. He has no problems with the global variable for the database, because it is the nature of the database. It has single instance and Uncle Bob feels that the singleton or monostate would be a needless complexity. He warns about too much design upfront and recommends verifying design quicky in the code. He warns about programmers making business decisions without consulting with the customer. Personally I really started to dislike the C++ examples. Even simplest ideas were needlessly hard to read because of the language of choice. C# or Java would be much better options IMHO. The Payroll Application uses TransactionSource interface, which hides low-level implementation details of how input transactions are stored. It could be a file, or a GUI, or anything else. The same goes for the database. The Payroll application can use OODBMS, a flat file, or a RDBMS, or anything else. He wrote everything test-first.
Chapter 20 - Principles of Package Design
REP - Reuse-Release Equivalence Principle states that the granule of reuse is the granule of release.If we are creating a set of reusable classes, we must release them separately. There is nothing like reusable classes if we don't release them in a separate library. I didn't like this principle. If you need to reuse something, of course you will release it as a library. I don't see any other option, so I don't see the value in the principle.
CRP - Common-Reuse Principle states that the classes in one package are reused together. If you reuse one, you reuse them all. I also disliked the second principle. I don't see how can I use it in practice.
CRP - Common-Reuse Principle states that the classes in one package are reused together. If you reuse one, you reuse them all. I also disliked the second principle. I don't see how can I use it in practice.
CCP - Common-Closure Principle states that classes in one package should be closed to changes of the same kind. A change that affects a package affects all classes in the package and no other packages. I liked this principle much more than the first two. We should observe changes in the code and repackage accordingly.
ADP - Acyclic-Dependency Principle states that there should be no cycles in the package-dependency graph. I like this principle very much. There are several hacks how to get ADP working, like dependency inversion.
SDP - Stable-Dependency Principle states that we should depend in the direction of stability. It seems obvious. I wouldn't want to depend on a library which is less stable than my code.
SAP - Stable-Abstraction Principle states that the package should be as abstract as it is stable. It seems obvious. Libraries have more abstract code that the dependent codebases.
Overall I don't like the presented principles much. I liked CCP and the ADP for the practical hints what we we could do to make the codebases better. I don't see much real-world use for the other 4 principles.
Chapter 21 - Factory
The author presents Factory design pattern for creating objects. By using factories we can conform to the dependency inversion principle. But they add some complexity to the design, so they shouldn't be used as a default option.
Chapter 22 - The Payroll Case Study: Part 2
Uncle Bob applies the principles from the chapter 20 to the Payroll Application. The result is the packaging with great metrics (he invented formulas for most of the principles), but I still disliked it. It was packaged mostly by technicalities (e.g. all transactions grouped together) instead of business functionalities. I think this could evolve badly in a bigger project despite having good metrics. I like packaging by business features more. One commit will more probably work with one business feature than with one technical aspect of the application (like transactions).Chapter 23 - Composite
I liked a view of the Composite design pattern as a solution to one-to-one vs one-to-many problem. With Composite, you can sometimes have one-to-many relationship with one-to-one simplicity. This can only be applied if all the child members are treated the same way.Chapter 24 - Observer
Uncle Bobs states that the use of the design patterns is evolutionary. The code should evolve into a pattern instead of being introduced a pattern. When he recognize a well-known pattern forming in the code, he just renames the classes, so that they are more "standard" named. Then he provides us with an example how one could evolve code into the Observer design pattern.Chapter 25 - Abstract Server, Adapter and Bridge
Abstract Server solves the DIP violations by introducing the interface in between them. Interfaces always belong to their clients, so they should be named accordingly (in client's words).We can even use Adapter pattern if we don't control the implementation class of the interface. We can create an Adapter which delegates to the class we don't control. But it comes with a price. We should only do this if it is necessary.
Chapter 26 - Proxy and Stairway to Heaven: Managing 3rd Party APIs
We have two implementations of a shopping cart. One that ignores storage completely, and another one written using JDBC. The second one is not about the shopping cart at all anymore, but about SQL instead. Author warns us not to write the second kind of code. Proxy pattern can help with that.The principle of Proxy pattern is simple. For example, we create an interface of a Product class, change the original Product class to ProductImplementation and create a proxy ProductDBProxy. When client code works with Products, in reality, it works with ProductDBProxies. They fetch and deserialize ProductImplementations from the database and then delegate to them.
It sounds good in theory, but in practice, the code didn't look so neatly. Uncle Bob had to make some shortcuts in the getters, and some delaying in the one-to-many relationship of orders and products. So the pattern has its drawbacks, but it also separates the database details from the higher-level policy of e.g. calculating the order total.
Another presented pattern, the Stairway to Heaven is relatively unknown and only possible in languages with multiple inheritance. In the end, the Uncle Bob recommends starting with a Facade, even with too much coupling between the business objects and the database Facade. He would only refactor to Proxy if necessary.
Chapter 27 - Case Study: Weather Station
This was a long case study about a weather station application. It applied most of the patterns mentioned previously. We saw couple of principle violations, most notably SRP. Most times it was about putting low-level details such as database together in one place with business logic.Chapter 28 - Visitor
Visitor patterns solve the problem of how to add a method to a hierarchy of classes without really adding it there. Visitor pattern is about a hierarchy of classes implementing the accept method. The method accepts the hierarchy visitor class. The interface of the Visitor classes must be list of methods for all the classes in the hierarchy, e.g. visit(HeyesModem m), visit(ErnieModem m), etc.Another pattern in the visitor hierarchy is the Decorator pattern. You want to add some non-cohesive functionality to a hierarchy of classes. Instead of adding it, you can create a wrapper class, which adds it and delegates everything else to the original class, i.e. a Decorator class.
Another pattern, Extension method was presented, but I find it's benefits to be too specific for a real-world scenario.
Chapter 29 - State
This chapter is about Finite State Machines (FSM). The easiest implementation are nested switch statements. These can be appropriate in the simplest scenarios. The second option is to implement a transition table. The table would contain all possible transitions and the states. This is more maintainable but slower algorithm. Yet another algorithm is the State pattern. You would create a separate class for each state of the FSM performing the actions needed based on the common interface. The advantage is great flexibility and speed. Disadvantage is the readability, because there is no single place to look at the FSM anymore (the code is distributed). Uncle Bob thinks the FSMs are underutilized.Chapter 30 - The ETS Framework
This chapter explains the history behind the ETS Framework which Uncle Bob and his colleague implemented for ETS, which tests architects. The architects would design a building or a roof in a CAD-like environment and the program would then automatically score the design. According to Booch recommendation, they started with the most complex type of the vignette/test - a Building design. Uncle Bob and his colleague decided to create a framework, as 15 types of vignettes were required and the maintainability of a solution without a framework would be overwhelming. But implementing reusable frameworks is known to be hard.This first attempt to write a framework was for the first kind of the vignette. The most complex one, the Building design. It failed. It was very hard to reuse and they had to start from scratch. They decided to include only those things in the framework, which were reused in at least 4 different vignettes. The second attempt for a framework was a success.
The used a Template method for the core of the scoring algorithm. They did not choose Strategy, because it is more complex and was not worth it. They did use State pattern for the generated FSMs specific to each vignette. Even each action that the examined architect would take was a small FSM with most parts part of the framework.
Comments