How to remove dependencies between tasks?
Two factories produce two times more products than only one in a period. In software development, this does not apply, and can be worse. How, two programmers in a team, can get closer or even outperform this level of output?
- Chapter 1 An example
- Chapter 2 Decide a boundary between two tasks to enable parallel work.
- Chapter 3 While increase the communication among team members.
This post will not say that your team should focus only on output, as the outcome is even more important.
This post will provide you with guidelines to maximize your teamwork. It will explain the most important concepts to avoid dependencies instead of managing them.
Lets starts in practice
Prerequisite
Given a component with a dependency like the GUI or the database.
In order to test a component with only a boundary definition, we need the ability to mock a boundary and its behavior.
Let's assume the prerequisites are met.
How does all this play out in practice?
Given "A" and "B" seems to be dependent tasks.
Firstly, we ask ourselves the following question:
Is it possible to define a boundary between A and B?
The answer must have 5 minutes timeout.
If it is possible to define a boundary between "A" and "B"
Then, we add a task "C" to implement this boundary.
If no boundary can be drawn yet
Then, merge both tasks in one and starts implementing in pair or "mob programming".
What are the concepts behind this?
Architectural concepts
To enable parallel work on tasks without interfering, use the simple design principle:
High-level policies are ignorant of low-level details.
— Quote from the book "Clean Craftsmanship: Disciplines, Standards, and Ethics (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
In practice, this principle is applied by drawing a boundary between components. Therefore, a task needs to focus on a single component.
Boundary drawing
Between high and low level components, a boundary is drawn. It's like an electrical outlet with a male plug on one side and a female plug on the other.
- The male socket plugs into the female socket.
- The male plug completely ignores what is plugged in.
So with business rules, database and GUI:
- The database plugs into the business rules.
- The GUI plugs into the business rules.
- Business rules completely ignore the database and the GUI.
Look at this diagram, both arrows cross the boundary and point to business rules.
Tips, when drawing a diagram, it is recommended to place high-level rules above low-level details. Therefore, all arrows will point upwards. Then, It is easy to detect a wrong dependency direction.
Let's avoid rebuild the wheel, as Robert C. Martin done a perfect description.
Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another, and restrict those on one side from knowing about those on the other. Some of those lines are drawn very early in a project’s life—even before any code is written. Others are drawn much later. Those that are drawn early are drawn for the purposes of deferring decisions for as long as possible, and of keeping those decisions from polluting the core business logic.
Recall that the goal of an architect is to minimize the human resources required to build and maintain the required system. What it is that saps this kind of people-power? Coupling—and especially coupling to premature decisions.
Which kinds of decisions are premature? Decisions that have nothing to do with the business requirements—the use cases—of the system. These include decisions about frameworks, databases, web servers, utility libraries, dependency injection, and the like. A good system architecture is one in which decisions like these are rendered ancillary and deferrable. A good system architecture does not depend on those decisions. A good system architecture allows those decisions to be made at the latest possible moment, without significant impact.
Boundary crossing
At runtime, a boundary crossing is nothing more than a function on one side of the boundary calling a function on the other side and passing along some data. The trick to creating an appropriate boundary crossing is to manage the source code dependencies.
When a low-level client invokes a higher-level service.
The simplest possible boundary crossing is a function call from a low-level client to a higher-level service.
In figure below, the flow of control crosses the boundary from left to right. The
Client
calls functionf()
on the Service. It passes along an instance ofData
. The<DS>
marker simply indicates a data structure. TheData
may be passed as a function argument or by some other more elaborate means. Note that the definition of theData
is on the called side of the boundary.
— Quote and image from the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
How to test?
Let's take the example of adding proof to ensure the GUI Controller work as it should.
Testing the controller is trivial, our tests simply invoke events through the event handler interface and then make sure that the controller builds the right request model.
— Quote from the book "Clean Craftsmanship: Disciplines, Standards, and Ethics (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
It needs to mock the higher-level service in order to spy and assert on the request model passed. And also stub the higher-level service to tests the usage of outbound data.
When a high-level client invokes a lower-level service
When a high-level client needs to invoke a lower-level service, dynamic polymorphism is used to invert the dependency against the flow of control.
In figure below, the flow of control crosses the boundary from left to right as before. The high-level
Client
calls thef()
function of the lower-levelServiceImpl
through theService
interface. Note, however, that all dependencies cross the boundary from right to left toward the higher-level component. Note, also, that the definition of the data structure is on the calling side of the boundary.
— Quote and image from the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
How to test?
Let's take the example of adding proof to ensure the database GatewayImpl work as it should.
Testing the database is trivial. You create a suitably simple test database, and then you call each query function of the GatewayImpl from your tests and ensure that it has the desired effect on that test database. Make sure that each query function returns a properly loaded set of business objects. Make sure that each update, add, and delete changes the database appropriately.
— Quote from the book "Clean Craftsmanship: Disciplines, Standards, and Ethics (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
The result of the simple design principle
With boundaries between components, we can remove dependencies not bound to a boundary. We can build something like an onion with some levels. But be careful, when there are too many levels, one modification made on the highest level will may require the modification of all dependents.
Tips, try to avoid using more than two levels.
How boundaries emerge?
There are at least 2 ways to get emerging a boundary.
- During the implementation
- With a dedicated task to draw the boundary
The best way is to start coding and let emerging boundary. Time go on, programmers gather experience and are able to design boundaries before coding using heuristics.
Keep in mind that boundaries must be able to move as easy as changing shirts. This allows for mistakes, and makes them inexpensive. How to allow for mistakes?
How can you prevent harm to the structure of your code if you don’t have the tests that allow you to clean it? And how can you guarantee that your test suite is complete if you don’t follow the three laws of test-driven development (TDD)?
— Quote from the book "Clean Craftsmanship: Disciplines, Standards, and Ethics (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
Lets explain the first way.
Boundaries emerge during implementation
The implementation starts with nothing, but it quickly achieves a simple behavior. Each time a new test case passes, the complexity of the production code increase unless the structure is improved. During theses improves, the boundaries emerge.
The first step is to draw the boundary line.
- With a function call
- With an HTTP request
The second step is to describe the inbound and outbound data that will cross the boundary.
Add an argument to the function call.
- Name it well
- Add a data type to it
When there are more than 2 arguments, it is time to create a data structure.
I wish you happy drawing, this principle is the first step to increase your productivity. The second step will be to increase the communication.
Communication between team members
How can you make progress on a task if you do not have feedback on about choices made? And how to have a feedback loop without increasing your communication?
Your brain have high difficulties to focus on a single task. As the task switching effect is too easy to practice as your unconscious do it himself.
As an example, while you are working and focus on a task, but someone asks you a question related to another topic. You ask him that you will answer later. When you come back to your task, your unconscious thinks about that question. Therefore, you just lost 20% of your efficiency.
Working together on the same topic, aka swarming, enables better communication and reduces task switching effects.
So to increase communication with your teammate, you need to work on the same topic as him. Therefore, questions will not be on another topic. And you will not be afraid to disturb him.
In practice, all programmers are in constant communication — Open Space or video — in order to answer any questions as quickly as possible or even help to ensure that boundaries are well understood.
This high communication level is the key of adaptation. Therefore, boundaries can evolve over the learns carry out during the implementation.
Summarize in image
And now the conclusion
Even in a monolithic, statically linked executable, this kind of disciplined partitioning can greatly aid the job of developing, testing, and deploying the project. Teams can work independently of each other on their own components without treading on each other’s toes. High-level components remain independent of lower-level details.
— Quote from the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
☯ The yin
- Pairs to implements the highest-level component
- Draw boundary line
- Define how to cross the boundary
- Dispatching the work force: enables parallel work
- One guy work on the Database
- One guy work on the GUI
☯ The yang
- Pairs to draw boundaries
- Dispatching the work force: enables parallel work
- One guy work on the highest-level
- One guy on the GUI
- Once one guy is finish a component, then switch on the Database
☯ Mixing the yin and yang in order to reach your efficiency with your formula.
Tips
- At the beginning it will feel slow, be disciplined and practice.
- Never, never force your teammates applying a practice.
- Create your own formula, depending on your team and your context.
- Invite all team members to practice each step.
- Practice as a team, practice, and practice as a team.
Appendices
Usage of term "boundary"
The term "boundary" has been used instead of "interface" in order to mark greater separation and isolation between components.
Usage of term "task"
The term "task" represent what we call a technical task, which is a subtask of a Sprint Backlog item, which is a Product increment.
A technical task has no value outside the context in which it takes place. As soon as it no longer contributes to answer the need, it no longer has a reason to exist. It is only a means, and not an end in itself.
Don't hit the mistake to forget the end goal and getting bogged down in code.
Outcome over output
This post does not say that a product team should focus only on output, as the outcome is more. On this topic, I suggest reading about agile context.
Resources
Architecture
- Read the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
- Read blog post: The Clean Architecture
- Watch video: The Principles of Clean Architecture by Uncle Bob Martin
Craftsmanship
- Read the Manifesto for Software Craftsmanship
- Read the book "Clean Craftsmanship: Disciplines, Standards, and Ethics (Robert C. Martin Series) (English Edition)" wrote by Robert C. Martin
- Watch the video: Uncle Bob Martin - The Craftsman's Oath at SC London Conference 2018
- Watch the video: The Three Laws of TDD
Task switching
- Read the blog post: Swarming: How To Instantly Boost Your Scrum Team’s Productivity
- Reading the about the pattern: Swarming: One-Piece Continuous Flow
- Watch the video: Henrik Kniberg : Multiple WIP vs One Piece Flow Example
Agile context
Diagrams
Acknowledgments
- Without them this article would not exist, because they were part of the adventure that allowed the birth of this article:
- For review:
- For resource sharing on Swarming:
I invite you to see my posts, not as my static opinions, but rather a stream of thoughts, caught in the middle of their update.
Top comments (0)