Thursday, February 27, 2025

Team learning session: the (in)famous types folder

This is the description, and experience report, of an exercise I used in a team I lead. I'll describe this in the present tense as I imagine applying the exercise in similar contexts.

Context

The team has been working on a Typescript monolith application; Typescript is used exclusively on the backend and compiled with Node.js as a target.

Encapsulation and modularization have been promoted as important software engineering concepts, and some high-level architecture and modules are emerging. For example:

  • a clear write and a read side in a CQRS application
  • a third-parties module to isolate from external dependencies
  • a top-level evolving http layer to isolate the Domain Model and the rest of the code from HTTP and other delivery mechanisms.

The team is oriented to functional-programming, modeling some domain types but also keeping related functions close to those types for cohesion. Or they could be working within the object-oriented paradigm, similarly organizing the domain model and other code with types and classes.

For the rest of the discussion, the word module applies to units of encapsulation of varying sizes. In a TypeScript monolithic codebase, this could be a folder with a index.ts file; depending on its size, it could contain other sub-modules following the same structure. This exercise is also focusing on compile-time dependencies, mostly import statements.

The types folder refers to a (anti-)pattern where shared types are collected in a top level, or very high level, folder. The types can then too easily be imported anywhere, with a thick tree of dependencies towards the types folder being established, inadvertently coupling together disparate parts of the codebase.

Learning goal

The team is picking up encapsulation and information hiding at the small scale, encapsulating functions in a folder behind a index.ts and its export statements.

We want the team to expand this process to larger scale modules, sized at 1K to 10K lines of code: how much code can be hidden inside within these modules, or added to their public interfaces to elevate it as a contract between modules?

We also want the team to think inside a monolith as we are not sure there is enough capacity to address the overhead of separating repositories and services.

Activity

The original text of the exercises follows, edited for clarity:

Our src/types/ folder acts as a catch-all for types and related functions when we do not find an encapsulated place for them to be placed into. While our architecture is often changing for the better, over time these global types remain visible everywhere in the monolith's codebase even if hiding them could help working on the code that does not need to know about them.

For each of the .ts files linked here, work as an ensemble to ask the following questions before deciding any refactoring move:
  • Where are the contents of this file currently used?
  • Are all the usages legitimate?
  • Are these file contents addressing too many responsibilities?
  • Do the names reflect the intended responsibilities or usage?

Once you decided on a new location for the file (or for part of it), ask:
  • Do the resulting import rules agree with our architectural maps?
  • If we change a decision within the newly encapsulated file, how far does the change propagate?
This is not a backlog to clear: do not rush to cover them all (there are 32 in total anyway). The goal is to identify patterns for architectural refinement.
Some example of problematic types we found when running this exercise, with real named and an general explanation:
  • RecordedEvaluation, Group: used only in module A and module B that depends on A. Yet globally visible.
  • GroupId: used in multiple modules that relate to writing and reading to a database, yet visible to the HTTP layer.
  • EvaluationType, ArticleServer: union types of a few strings, global for convenience but also only used in a few modules.
  • CommandResult: a return type and part of the contract between module C and D. Module C depends on module D. Should this be defined by C, D, or a third module to break the dependency completely?
  • DescriptionPath:  a branded and validated string. The same problem could be solved by ensuring validation happens in the right modules. Should this type be simplified to a string?


A map of (compile-time) dependencies between high-level modules, intentionally zoomed out. Dependencies are generated by import statements originating in one module and reaching out for a type or a function contained in another module at any level of nesting.
Resources

A small guide to barrel files (index.ts files containing only export statements) can be useful. They are often mentioned as having performance implications, but if there are any measurable drawbacks they should be traded-off with maintenance.

dependency-cruiser can be used to help map out the current dependencies of a codebase, generally at a very fine-grained level rather than top-level modules.

Retrospective

A couple of questions can be asked here in a round robin, or for lack of time written as prompts on a digital board for participants to brainstorm about for a few minutes.

As often in quick retros, one question is looking backwards into what has happened and one is looking forwards to improve our practices:

  • did you see a type which looked reasonable before today, but now is a target for refactoring?
  • what would you be differently tomorrow when you create a new type?
The reflections that emerge from all the participants help set a shared direction for the architecture that you want to see. Enjoy!

Monday, February 24, 2025

Ensemble programming roles and cues

In the last few years I've been leading a team that has adopted ensemble programming and pair programming as the main practice to deliver production code. We have evolved cues that have a precise meaning for team members and that make communication more effective, especially when some of these phrases are used every few minutes.

I considered various terms to indicate this set of phrases, and discarded most of them:

  • mantra (a slogan repeated frequently,  but often to yourself, and with spiritual implications),
  • shibboleth (implies a closed group having secret handshakes and hiding from an external force)
  • catchprase (a signature phrase associated with a specific character)
  • formula (works in other Romance languages but has many other meanings in a English context)

Formula could be considered a loan word from Italian, but I think its original etimology fits:

1630s, "words used in a ceremony or ritual" (earlier as a Latin word in English), from Latin formula "form, draft, contract, regulation;"

To keep with the metaphor of an ensemble of actors or musicians performing, I found cue was fitting too:

the trigger for an action to be carried out at a specific time

A few roles in ensemble programming

The rotation of roles visualized on a Miro board

This experience is in the context of remote ensemble programming, where there is a single audio medium shared across a virtual room, and limited surface space for visual cues such as body language or facial expression. One of two monitors, or part of a monitor, would usually be dedicated to 2 or 3 other camera feeds, with a digital whiteboard or an IDE on a shared screen being the place of operation. This isn't to say that paying attention to your colleagues isn't prioritized; rather than making use of continuously-improved verbal language is one way to achieve that.

Within this setting, a few roles are assumed by the team members participating into a particular ensemble:

  • the Driver inputs all changes into a working copy of the codebase.
  • the Navigator has the responsibility to coordinate the group into the next decision it needs to take, and verbalize all changes for the Driver to enact.
  • other ensemble members assume roles as needed and without even noticing, actively contributing to the discussion. They might help with diagrams or note-taking, or notice code smells or other people dynamics since they can dedicate their attention to something different than the next code change.

In this team, a rotation based on committing and pushing the last change emerged. A strict rotation where a navigator remains in that role for the whole duration of the cycle also emerged, mostly to keep a level playing field across all levels of seniority and giving the space to everyone to lead the next change.

The cycle described here is the one of starting a new screen share session; pulling the latest version of the code; making a change and pushing it to make it available to the rest of the group. This micro-iteration has an associated cycle time, which is where I believe the term came from. After each cycle, the previous Navigator would rotate in to become a Driver; someone else would become a Navigator.

Many of these cues are commonly uttered from the Navigator or the Driver, but they are not limited to them. Consider this list harvested in a design patterns sense: extracted from repeated real world experience; no claim to completeness. It is however limited to a single team, operating in various context and changing its membership over time. Crediting Kevin Rutherford for introducing ensemble programming when this team was newly formed at the beginning of 2020, and helping the team honing in on particular solutions over time.

At the beginning of a cycle

"What do you want to see?"

The Driver may start a cycle asking this to the Navigator, to relieve pressure on deciding a direction quickly. To ease into the role of Navigator, there is little downside in spending time observing or understanding some area of the code, rather than attempting to change it immediately.

In the context of new ensemble members, this cue also relinquishes control explicitly to the Navigator. If you are used to the person with control of the keyboard showing what they mean, you will instead seen a Driver waiting for instructions.

"I'm just a pair of hands" or "Waiting for instructions"

Indeed, the Driver uses these or other cues to remark how they are not taking decisions just because they happen to hold the keyboard or the mouse that can perform changes. The Navigator has the responsibility to coordinate. The driver can focus on efficiently executing a move.

"Let's go to the IDE/"the code"/Github/maps/Miro"

At the beginning of a new cycle, or triggering a new cycle, an ensemble member suggests to change the visual support to fit the discussion. This might involve not sharing a screen anymore if that medium supports collaboration natively e.g. Miro as a digital whiteboard.

For example, Github could be useful to navigate unfamiliar repositories; a whiteboard is vital for remote Eventstorming or for diagrams; maps indicate a set of long-lived architectural diagrams for context, dependencies or separation of concerns.

During a cycle

I think this is where the phrase comes from. But lack of verbalization of intention should be the exception, not the rule.

"Make it so"

The group has shared understanding of a change that needs to be applied. For example, we slice a change into many small steps; there is low risk because we already performed a few of these slices; uncertainty might lie more related to what can be discovered from the compiler or a test suite reacting to the change; or a refactoring move may need to be applied to a different part of the codebase, in what we suspect will not require new design decisions.

After having stated the change they'd like to see, the Navigator hands over control to the Driver with this cue, and lets them loose in achieving it with their preferred tooling: VSCode, Vim, grep, even an LLM. The separation of concerns is between the destination, and how to get there.

"Rename the ... class/function/method/variable and all references"

The Navigator directs the Driver at a high level of abstraction. The Driver is a intelligent IDE and applies this change predictably as they both understand what the outcome should look like. In case the Driver runs into trouble such as unforeseen situations, they stop and ask for more precise direction.

"Can we scribe on the board something about ...?"

The Driver or the Navigator asks other members of the ensemble to take a note about something that we should look at later. They could just ask to take a note, but we picked the word scribe from Eventstorming practices to identify whoever is currently responsible for creating artifacts to a shared digital board.

Assigning this role, for example, to the third ensemble member in the rotation helps avoiding concurrency clashes, with multiple people trying to take the same note.

"From the back of the room, ..."

Other roles than Driver or Navigator hand out suggestions or considerations, without wanting to interrupt or override the navigator's intention. The Navigator still coordinates making a decision, but the other ensemble member can contribute specific knowledge or cues that help the group make progress without taking away the opportunity for the Navigator to exercise their skills.

The expression comes loosely from the phrase "leading from the back of the room"; I've seen this role referred to as the Rear Admiral in some ensemble programming literature, turning the Driver and Navigator rallying metaphor into a naval one where everyone helps running the ship.

"... can be a rabbit hole"

A problem we face is recognized as something that might be taking away from the momentum of the group, or its overall importance is controversial. It might be more productive to mitigate it now, rather than digging into its ultimate causes. The group can agree to postpone the discussion of this problem, to focus on the next test to write or to make green. If the problem makes it very difficult to make progress, it will keep emerging. Rabbit hole is just internet slang for an engrossing and time-consuming topic such as programming language trade-offs; a troublesome library upgrade; a non-deterministic test; or large-scale architectural changes that require a lot of evidence collection and consideration.

At the end of a cycle

Group peer pressure helps doing better than that

"Anything else for this cycle?"

The Driver is asking the Navigator about whether the intended scope has been achieved. Confirmation will start a commit process if no other checks or changes are necessary.

Sometimes the Driver acts as a preemptive time keeper, reminding everyone uncommitted code is work in progress and the bigger it gets, the higher the risk of progress being lost if it triggers inadvertent behavior changes in the codebase.

"End of cycle" or "Let's rotate"

The Navigator decides it's time to rotate the roles, considering what has been learned in this cycle enough to perform a switch without loss of context. The cycle might have been focused on investigation or reading code, given in this case it ends without a commit. Even if there is an attempted change at play, the knowledge gathered can led to the next cycle to regenerate the same git diff quickly, or to find a safer or easier path through that change.

"make check and commit" 

The navigator communicates it's time to commit what we have done. Substitute make check with your local testing command of choice; it's just a team convention to capture a set of compilation, static analysis and tests that we deem acceptable to run before every commit. Other more specific or intensive testing can be left to a Continuous Integration build as a safety net that will constitute a longer feedback loop. Normally, the commit is followed by a push and a default end of the current cycle.

"What [commit] message would you like?"

A Driver understand we are ready to commit a change, and invites the Navigator to summarize and describe it. This is an opportunity, if the team wishes so, to try other cues to repeatedly nudge towards a certain behavior and getting into a new habit. For example, you could experiment with having Drivers ask "Why are we doing this change?" for a whole day.

Thursday, May 11, 2023

I just want to run a container...

 ...is a developer-centric point of view?

Generic data center image to exemplify a place many of us have never set foot into.

After a few hours spent on upgrading the toolchain that starts from Terraform, goes through a few AWS-maintained modules and reached the Elastic Kubernetes Service APIs, my team was entertaining the thought of how difficult it is to set up infrastructure. Infrastructure that, for this use case takes a container image of a web application that Docker generated and run it somewhere for users to access.

Thinking through this, either Kubernetes is the new IBM (no one ever get fired for choosing it), or there is more to a production web application than running a container which is what Kubernetes is often sold as: a tool to run containers in the cloud without the necessity to set up specific virtual machines, but treating them as anonymous, sacrificeable nodes where the aforementioned containers can be tightly packed and sharing CPU, memory and other resources.

What exactly is the infrastructure doing for us here? There are a few concerns about operations, the other side of that magic DevOps collaboration. For example:

  • the container image is exposing an HTTP service on port 80. This is not useful for a modern browser and its padlock icons, so to achieve a secure connection a combination of DNS and Let's Encrypt generated and automatically renews certificates after verifying proof of ownership of our domain name.
  • the container image produces logs in the form of JSON lines. Through part of the Grafana stack, these lines are annotated with a timestamp, the container that generated them and various labels such as an environment or the name of a component of the application (think frontend or prod or staging). After these lines are indexed, further software provides the capability to query these logs, zooming in on problems or prioritizing errors during investigation.
  • if we get too much of these logs at the level of error for a particular message, we'd like to receive an alert email that triggers us into action.
  • invariably applications benefit from scheduled processes that run periodically and independently from the request/response lifecycle. It's such a useful architectural pattern that Kubernetes even named a resource after the cron daemon, introduced in 1975.
  • it's also very useful for applications to maintain state and store news rows in a relational database. Provided turnkey by every cloud, a set of hostname, username and password allows access from any combination of programming language and library. No need to worry about rotating the logs of Postgres anymore, or that we are running out of space.
  • the data contained in the application and generated by user also lends itself to timely analysis, so that we know whether to kill a feature or to invest in it. This means somehow taking out recent updates (or whole tables) into a data pipeline that transforms it into something that can be analyzed by a data scientist. All with an acceptable speed.
  • Those credentials nevertheless need to be provided to the application itself, hopefully without accidentally disclosing them into chats, logs, or emails. No human should need to enter them into an interface.
  • Configuration is slightly easier to manage than credentials due to the lack of sensitive values, but we'd still like the capability to generate some of these values such as a canonical URL or to pass in different numbers for different environments.
  • And of course, whenever we commit, we'd like to deploy the change and start the latest version of our application, check that it is responding correctly to requests, and then stop the old, still running one.

This is all in the context of a single team, running operating a single product. Part of this is achieved through running off-the-shelf software on top of Kubernetes; part of it by paid-for cloud services; part of it by outsourcing to a specialized software as a service.

However, when we think about software development we don't necessarily think about software operations. Many of us started our careers by writing code, testing and releasing it without a further look to what was happening afterwards. DevOps as a philosophy meant bridging the gaps; You Build It You Run It is the easiest summary for me.

Yet what I see and what I hear from experienced people is that silos in infrastructure seriously slow teams down, and create fears of insurmountable problems. Abandoned projects, "finished" from a development point of view, but now having unclear ownership and running in production supporting the main revenue stream of an organization.

So I don't want to just run a container. I'd rather deploy many incrementally improved versions of a container, monitor its traffic and user activity, and close the feedback loop that links usage to the next development decision.

In other words: I can always build simple code if it doesn't have to run in production.

Thursday, January 20, 2022

A year of mob programming, part 5: methodology

I wouldn't call mob programming a methodology, but rather a practice that can be adopted. Like methodologies, however, it only surfaces problems and questions faster rather than providing solutions.

For example, a team might have a low bus factor due to the specialisms of its members, and only one person might be used to edit code in a certain language or application. Other team members might have gaps, such as not being set up with the tools and credentials to work on infrastructure and operations; or lacking the knowledge to perform screen reader testing for accessibility.

Besides exposing bus factors and gaps, mob programming brings design conflicts and opinions into the open. It takes little effort to turn the other way when looking at a pull request, or to switch to busy work or generic "refuctoring" when alone; disagreements and questions about value can often prop up under your nose inside a mob session.

However, that doesn't mean that the practice automatically gives the team the psychological safety to talk about those issues out loud, and to resolve them. That's like asking an hammer to walk to the shop and buy the right nails.

At the last part of this post, I thought I'd compare my mob programming experience with some of the theory; the theory being famous Agile software development methodologies and their values or principles.

The serene predictability of waterfall software development

Within Extreme Programming values:

  • Communication inside a mob is very high: synchronous conversations supported by a visual medium such as code or a digital whiteboard.
  • Simplicity is fostered by people in a diverse team reminding others that You Aren't Gonna Need It, but also needing to understand what's going on at all times.
  • Feedback from the rest of the team, or even from a customer proxy, is immediate.
  • Courage is needed to be a driver in an unfamiliar setting, or even a navigator asking the mob for help on a task we are uncomfortable with.
  • Respect is necessary to work face to face for prolonged periods of time.

Within Lean values, mob programming:

  • eliminates waste such as partially done work: there should ideally be no pull request open or branch, as code is produced and integrated in the mob continuously;
  • eliminates waste such as outdated features or gold plating: when you include a product owner in the mob, it's easy to steer and change the scope upon feedback rather than plowing on with the original idea;
  • eliminates waste such as handoffs: there is no packaging up of code for review, or of pixel-perfect Photoshop design to front end developers, or front end requests to back ends to be implemented, and so on.
  • reduces task switching: since everyone that is needed for progress is present, you often don't need to switch task because of waiting on someone, but can just carry on.
  • amplifies learning: ideas are tried out in code from the get go, and most of the team can learn from the results including the product owner.
  • optimizes the whole: most of all, we are not optimizing how much we can use everyone's time, but how fast we can flow a single piece from idea to customer value.

Image by Yann.

Saturday, January 08, 2022

A year of mob programming, part 4: the remoteness of it

I don't have a frame of reference for mob programming in a co-located environment, besides conference (or team exercise) workshops that didn't involve production code.

On a video call, the driver is still always explicitly defined as the person sharing their screen and tools. This is similar to the person physically having the keyboard when you are all sitting around a table.

The navigator can either be more implicit, move around or be strictly defined by a process. If an implicit navigator doesn't emerge for the current commit, it should be considered normal to quickly nominate one as needed. Over time, we normalize the phrase "can you navigate [that change]?" in response to a proposal.

Due to latency, in a video call environment there is a sort of Lorentz time where you may hear overlapping voices when someone else doesn't, and vice versa. It all depends on how much latency each video link is experiencing, and the adjustments that software makes can be jarring, such as video freezes or audio being delivered 10-20 seconds later.

"Sorry, two people talking over each other" "Really??"

I'm sure though everyone has already experienced people talking over each other in a physical room: it isn't exclusively a remote working problem and relies on team interactions more than technology.

We also want to communicate and especially give feedback through voice, as people's faces are not always visible to a driver looking at code; nodding doesn't necessarily help.

Perhaps a bigger problem is the fact that video calls don't allow side conversations to happen, as there is a single audio channel that our ears cannot separate into directions. This is likely to impact a massively parallel EventStorming around a whiteboard, less so an ensemble of 3-4 people trying to get to a consensus on their next move. The side conversations however include those famous water cooler talks, bumping into people in the kitchen or while on a break.

Your theory of mind might have more trouble cutting through the limited signal, and understand in what mental state other people in the team are; and whether today's problem is more due to Internet connection trouble, or a real conflict between team members. The daily retrospective helps to let people put tools down, and reflect on the day; possibly proposing experiments for the next session. 

Looking at the world around me, I'm quite sure I would be as depressed by remote work as many, if it consisted of me being alone for most of the day. So due to mob programming I have a different perspective on how incredibly engaging working from home can be.

Stay tuned for the next (and last) part of this post, Methodology.

Image by Duesentrieb.

Wednesday, November 17, 2021

A year of mob programming, part 3: a laboratory for team dynamics

Our implementation of mob programming consists in a permanent Google Meet videocall, with camera on, in which we rotate a developer sharing a screen containing IDE and browser. So there are two unusual practices to get used to: being in a group all the time, and being on a camera all the time.

I was originally surprised on how both practices went from exhausting to being a normal work day. This is my experience, and we have to consider neurodiversity and personal preferences in the team on having a long screen time and continuous social interaction. It's almost obvious to say, but sometimes people hate inconclusive meetings in which they have no say; not being together with other human beings.

Don't underestimate your capacity to adapt, but recognize that your energy is not going to always be the same; whether because you got a cold, or something is going on at home, or some task is particularly draining. 

The Flemish inscription at bottom reads: because feedback is perfidious, I will go code in my cave

Gold cards and spike branches give people the ability to work on their own when they request to. Mechanical work such as upgrade of dependencies or investigating logs can benefit from focused time from one person. If you were in an office, you could go to an isolated room; working remotely, it's even easier as it just consists in leaving the video call and coming back refreshed later.

The opposite can also happen, when someone like me can manifest Fear Of Missing Out and the mob making lots of decisions in their absence. As the team gets to a norming phase, we should expect fewer surprises for members coming back to the mob after some time off.

Being together with the rest of the team can make your motivation higher due to the group support in getting started or unstuck from a particular problem; but of course the whole group can struggle on occasion.

Every time there is a new composition, though, there are changes in how the mob is working and what it pays more attention to. And from the point of view of a growing team leader, being embedded in a mob environment means being positively inundated with information. The challenge is to make sense of what we see to understand where team members are struggling and what are they finding most effective.

"I wish I could see players only in 121 meetings" -- No football coach ever

This requires a lot of (cognitive) bandwidth, and I usually can't at the same time focus on technical architecture and on social roles. But maybe this is just an argument to work in a team where the different hats can be rotated; as much as there is always someone writing code, there can always be someone checking if we have started to talk over each other, or we are falling into a rabbit hole of an hour without committing.

Stay tuned for the next part of this post, The remoteness of it.

Images by Michael Kranewitter and in the public domain.

Tuesday, November 09, 2021

A year of mob programming, part 2: Collective Code Ownership

With respect to a team assigning tasks to developers by their function (frontend, backend, infrastructure, and so on), mob programming fosters Collective Code Ownership.

This is more in the sense that everyone should be able to contribute through the mob in any area, rather than everyone being able to prioritize on their own what needs to be improved. The pact is that our time belongs to the team, and the team decides what is important.

A result of this is an healthy prevention of knowledge silos, where you can exercise your own creativity just because no one else knows how to work in that area anymore.

Code review happens continuously in the mob on small changes, and there are no huge feature branches to go through. In large changes such as those, it's too late for review to suggest a valuable but completely different approach; there is too much investment of time and energy into code. If review dares to suggest groundbreaking changes, it causes extensive rework instead.

There is a larger picture on ownership: team members tend to keep track of what they care about, whether that is consistency in architecture, front end approaches to styling, performance issues, and so on.

Sometimes I found myself being talked out of a design I had in mind, counteracting bias I could have for the first or most familiar solution. I started working on this team with an object-oriented mindset, but we have transitioned to functional programming in TypeScript.

Sometimes, there are hills to die on: hard to reverse decisions on architecture, or programming language choices. It's a job of a psychologically safe team to discuss these choices collectively without drama or oversimplification like JavaScript being the the death of computer science. 

"I told you we should have written the code in English, not Latin!"

It tooks three different iterations to get a repeatable pattern for accepting Commands (as in CQRS). But it was more fruitful to focus on the Domain Events design, as a difficult choice to revisit, than on the shape of the code itself which can be refined at any time.

Working outside of the mob

The counterpart to the XP mantra of writing all production code in pairs (or a mob) is to allow team members to contribute when they are working alone due to other necessities, such as an unstable Internet connection or a flexible day where they can't align completely with the core hours of the team.

I have learned to ask the team to be commissioned something to do, or at least to give them a choice. This keeps the prioritization in the hands of the group, again reducing bias.

It's helpful to assign problems that have only a constrained possible solution such as renaming, or propagating a refactoring through the codebase for consistency's sake.

It also helps to report back what you have learned during a solo coding exercise when rejoining the mob; unexpected issues or decisions you had to take on the spot and you feel unsure about.

You're allowed to stop at a certain point, as making progress alone at all costs is valued less than maintaining collective code ownership and a shared understanding of how our application works.

Technical spikes can also be chosen by a single person to work on, either because they want to individually prioritize them to demonstrate an idea; or because the amount of investigation required makes it difficult or frustrating to collaborate.
Spikes can be built on throwaway branches, being optimized for learning rather than for delivering production quality code. Once an idea has been demonstrated and approved, the mob can implement it pretty quickly on the trunk, and if we believe in the practice, with an higher level of quality. I constantly find feature branches worked by a single person to be a dead end (especially if they are my own).

In the end, people are frequently in meetings, researching or even just on holiday for a given day. Hence there's always someone missing that will catch up on the progress when they come back into the mob. But the group itself never stops even as the components change, so some of that progress will happen every day, other things being equal.

Stay tuned for the next part of this post, A laboratory for team dynamics.

Image by Johann Jaritz.

Featured post

A map metaphor for architectural diagrams

It is a (two-dimension) representation of a pipe. The map is not the territory , but in software engineering terms they are models of it....

Popular posts