Architecture and Growing Teams
Development teams grow over time. How does it happens and how does the overall application or system architecture influence that grow?
Members of the Team
Look at the members of your team. Are they similar? Do they have a same set of skills? Does every member want to work on an exactly the same piece of code?
In real teams, answer to those questions is “No”. Team members have slightly different skills (at least). One team members are stronger in one area, others are stronger in some another. If managed properly, that difference makes the team very strong. Well-managed team could benefit from strengths of each team member. However, poorly managed team would use an average (or, even worse, the worst) level of the skills.
If the team grow, situation become more complex. New people bring new ideas and skills. How could we integrate those traits into the team? Would the new people be happy maintaining the legacy code? Would they have enough space to implement their ideas and to be happy? Or would they be restrained by decisions already made?
The following chapters cover two approaches of team management.
Lead by Consensus
Having a team consensus for each decision is a pretty common pattern (especially in big organizations) saying that the team should achieve a consensus on each significant topic. Is this a good strategy? There are the following points to consider:
- Amount of communication. Each aspect must be communicated to each individual team member. Either a team-wide meetings are required or there is a huge amount of interpersonal communication. And this amount of communication happens for each and every decision to be made. This aspect influences almost all other aspects.
- Individual initiatives. Given the amount of communication, it is hard to introduce some new initiatives (regardless if they are good or bad). Each initiative involve the whole team. It may be OK when application is in a steady “support” stage where every possible request was anticipated by the application design and its implementation is straightforward. But the situation is usually different. New requirements emerge, new technologies are created. And usually there is no application grow at that stage.
- Cost of fixing a mistake. It is not possible to get right solutions all the time. What is the cost of each individual mistake in choosing approach or technology? The cost of mistake could be different. But would it be easy to fix the mistake? Remember, there is a huge amount of communication involved in each decision. Fixing a high-level mistake means that some team-wide decision have to be changed so all the team will be involved in the communications. And not only the meetings will take the time. Team members have to spend time preparing to those meetings, remembering the context and requirements, etc…
- Time to get involved. Speaking of the context, there is another problem. There are no universally good or bad decisions in the software. Very good and pretty universal decision could be an absolute disaster in some specific circumstances. Those circumstances are called a context of the decision. To make a good decision, software developer have to understand the context. Actually, significant part of the software developer’s work is understanding and writing (fixing) the required context. Each team member involved in the decision should be in a “context” of a problem. As the whole team should achieve the consensus, each individual team member must understand the context. Without the context he/she would be useless (or even counter-productive). And getting the right context takes time.
- Experience gap. What if a team is pretty mixed? It have few experts, some senior developers and few junior developers starting their career path. Could this mixed team get to the best solution possible? Some important skills came to developers over time. They must learn on their (or others) errors to see a subtle points. When correctly identified, those points helps to identify the context and to choose the best solution for it. Without noticing those tiny bits of information junior team members see “many” good solutions. And the price for the right one looks t0o high for them. So these mixed teams tends to choose not-so-good solutions (which are problematic on the long-term run). Or they are finally convinced by the senior members, but it takes a huge amount of time (again, the whole team is involved in the process).
- Long-term learning. An interesting situation happens in large teams where all the team members are “interchangeable” between tasks. Junior team members usually have a problem with seeing long-term aspects of their decisions. Yes, they are given a chance to implement some “core features”. As they are junior, they make mistakes (which is OK). Some time later those mistakes are discovered. However, different team members are usually have to fix those mistakes. Those problems are incorrectly attributed to a particular component (if those components are present at all). Due to a general tendency to non-optimal solutions, the problem is usually complicated by issues in related components. So junior developers could not fix it at all and senior developers came to help. But in this case senior developers learn to be more productive and junior developers never learn.
- Developer happiness. There are many different ways to do the same thing. There are several ways to do the thing right. However, those right ways are quite different. And as the team grows, it is easier and easier to find at least one developer not happy with the approach chosen. Not because the approach is wrong, but because people are different, have different workflows and ideology. So even if consensus is achieved, some developers are left unhappy. And during they daily work they have to follow the decisions they do not like. It does not make them happier over time.
Divide and Conquer
Another approach is to assign areas of responsibility for each team member. These areas could change over time, but at each moment there is a responsible developer for each solution. The natural breakdown of the application is roles covered in the Managing software growth article.
Lets consider the same points for this strategy:
- Amount of communication. As the decision is made by an individual developer, general consensus is not required. Some communication is required to figure out proper requirements (get the right context). Some communication is required to ensure that each decision does not affect other developers. However, those other developers are not required to get the full context of the problem. They just have to insure that a proposed solution does not negatively affect components they are responsible for. And as the engineers works on those components, they already have the required context (and actually this “focus” on a specific piece of an outside context provides more benefit than a shallow understanding of all possible contexts).
- Individual initiatives. The core of this approach supports individual initiatives. There is no much coordination required to make a decision. Each team member could prove their best (or worst) point without a significant effort. As long as the “component” fulfils its contract, everything is in place. This also allows some degree of “competition”, where multiple developers create a code for the same “role” and the best implementation/API is chosen.
- Cost of fixing a mistake. Fixing a mistake usually requires less communication. With a good software architecture (i.e. division to large amount of roles) it is usually possible to not only improve the implementation, but replace it completely with a different (and flawless) implementation. Note that this benefit is not caused by “code ownership” but by “divide” phase of the strategy, which creates a “replaceable” components.
- Time to get involved. Usually, developers work inside their area of the responsibility. They are already pretty involved and know their context well. Yes, they have to learn a new context when switching to work on another component. But only individual developers have to do that (and only when ownership of the component changes). Good requirement tracking helps to do the switch.It
- Experience gap. The gap still exists and junior developers still write non-optimal code. However, we could assign expert and senior developers to a critical software parts. In practical implementations, those experienced developers usually owns “larger roles” of the software. Those “large components” have their internal roles owned by more junior developers. And the senior developers are responsible for that “sub-division of components”, its their responsibility to make sure that any possible problems have very limited impact on the overall system.
- Long-term learning. Switching the owned component is not encouraged in this system. It is nice to have different team members knowing internal working of the component. However, developers should own their components for the significant amount of time. They could not implement a new component and switch to implementing another new component leaving support to somebody else. Observing long-term consequences of a decision is crucial. As components are usually not too bad, developers ends up being responsible for multiple small components. Even if some components do not grow, other do. So there is a place to learn. As the system grows, senior developers became responsible for larger parts of the system and grow into architects.
- Developer happiness. Developers are usually happy. They are give an area to make their decisions and learn. They could use their skills at the best extent possible and could get other “areas of responsibility” to practice within them. Their leaders should help them to avoid “critical” errors but allow non-critical ones as a learning experience. Team members see each other as responsible and professional individuals. And they are not affected by approaches they do not like (those approaches used in components owned by other team members). So professional teams tends to be happier with this approach.
As you see, “divide and conquer” provides a happier team which could produce results faster and not worse than a consensus-based team.
There are some practical advices for those who want to implement “Divide and Conquer” strategy.
- Roles in interactions. It is the ultimate goal to have independent components. However, it is not possible and components (“roles”) have to interact with each other. It means that owners of different “components” have to communicate with other component owners and their team lead or architect (owner of the role breakdown). However, the goal is not to achieve the “consensus”. Goals are different for different roles and are as follows:
- For the component owner (owner of the component in question), the goal is to get the right context, to collect proper requirements from other stakeholders.
- For owners of other components the goal is to ensure that any decisions made do not harm components they own. They should not care too much about implementation of a new component, they should care about interface of that component and impact on their “area of responsibility”.
- The main goal of the architect/team lead is to ensure that there are not too many component interdependencies (so implementation of the component may change in future). Another goal is to ensure that there are no obvious mistakes in the component implementation.
- Control by negation. As said above, many decisions are still reviewed by owners of other components. “Control by negation” is a good way to inform all parties about important decisions and reduce overall amount of churn. In this scenario important decisions (requirement lists, proposed APIs, etc…) are communicated to all the stakeholders (other technical teams). No explicit “positive” response is expected, it is assumed by default. However, any possible issue should be raised by other team members and should “stop” further process until an issue is resolved by all involved parties. This provides a good visibility for decisions made, provides a “feedback point” for all the affected parties and do not introduce too many meetings. Written requirements are essential when component changes its owner or is not developed for some time. Written interfaces are part of the component documentation so those proposals/notifications do not require additional time investments.
- Limit turn-over. Developers should be discouraged from changing the components they are responsible for. The expected “career” is that they would get more components to own (if there is no much work on the components they already own). Another path is to provide component decomposition and get additional developers to help on the work (lead/architect path). On real project there are enough different tasks so developers grow in both directions (more components and larger components) simultaneously. This rule does not mean that a developer should write all the code. He/she should “own” the component and its internal design. The code could be developed and written by other developers. This helps developers to learn long-term consequences of their decisions.
- Expect component change when an owner changes. Consider that a component changed an owner. What would happen then? By default you should assume that an implementation of the component could be changed over time. As code is just an implementation detail, other parties should not be affected. Owner could have his/her own ideas of doing things. And it is easy to revert to a previous implementation of a component if needed. However, in large teams it is usually easy to find a person desiring to own the component in it existing implementation anyway.
- Leader role. The main role of the leader is to “break down into pieces workable by individual team members or sub-teams” . He/she is not the person dictating implementation of each individual component (role). Other team members are responsible for the implementation. Leader should control their work and prevent obvious mistakes (missing requirements, incorrect implementation, tight coupling between components). Leader is also responsible for monitoring long-term health of his component (or application) and taking preventive actions (refactorings, reengineerings, spike work) when component breakdown does not match current or future demands.
Good architecture (a good set of loosely-coupled “roles” inside an application) allows to implement a “divide and conquer” work distribution strategy. It allows faster and more adaptive software development, reasonable amount of communication, stable grow scheme (additional levels are added in the hierarchy over time), space to learn and make own mistakes for team members and a way to use best set of skills from each team member. All those benefits could not be achieved without good architecture, tight coupling between parts of code does not allow that kind of code and team evolution.
In following posts:
- Consensus is not the best strategy. Why is it chosen by managers?