The approach shown in this article is a modelling of the software development process using agent-based modelling concepts. The agents of the model corresponds to participants of the software development process, such as developers, testers, etc. The characteristics of the process properties, such as performance, emerge from specific actions of the individual agents and communication between them.

In the agent-based modelling (ABM) the system is represented by a network of interacting autonomous entities, called agents. The behaviour of the whole system emerges from the behaviour of agents and the environment where they operate. The well-known ABM example is Conway's Game of Life, in which the agent's behaviour defined in four simple rules leads to a complicated patterns on the system level.

Quite frequently in ABM the agents are constructed as state-machines: they have finite or infinite number of internal states and transition rules from one state to another. The operation logic is implemented in a form of triggers, called when the agent leaves a particular state or comes into a state.

The environment state in the software process modelling is a list of requirements and time. The agent is a developer, that takes a requirement from the list and implement it, advancing the time. When there is no requirements, the simulation stops and records the time.

Basic elements of the simulation engine:

  • Event - an object, encapsulating change of the agent's state. Events are invoked in the order of their time.
  • Agent - an autonomous entity, that represents machine, individual, organisation, group or any other domain-specific acting object or person. The agent informs the environment about further state change by generating an event.
  • Environment - a container for the agents, queries them for the events, invokes the events in the right order until specific condition is meet or no more events to invoke.

In the particular domain-specific simulation there is one environment, one or many agent types, which are producing events of the same or different types. Below is a minimalistic implementation of the described model. Its main properties are:

  • There are interfaces for the agents and events types to abstract their behaviour from implementation.
  • The instantiation of the agents or spawning them during simulation is not a concern of the environment.
  • Environment is implemented as class, not by interface, as there is only one environment.
  • The language of this implementation is TypeScript, which is transpiled to JavaScript, so it can be used as a server module, as stand-alone application, or on the Web pages.

Source code:

interface IAgent {
  event(): IEvent | null;
}

interface IEvent {
  time: number;
  invoke(): void;
}

class Environment {
  agents: IAgent[] = [];

  run() {
    do {
      let e: Event | null = null;
      for (const a of this.agents) {
        const e1 = a.event();
        if (e1 === null) {
          continue;
        }
        if (e === null || e.time > e1.time) {
          e = e1;
        }
      }
      if (e === null) break;
      e.invoke();
    } while (true);
  }
}

In the software development simulation model the developer implements requirements by updating the source code though time. The list of requirements, the source code repository, and the current time are shared by all the developers. The IEvent and IAgent can be implemented in different ways to match this design. One of the approaches, when the event encapsulates the changing action and the agent is generate this action, is shown below:

class Data {
  time: number = 0;
  requirements: number = 100;
  code: number = 0;
}

class Event implements IEvent {
  time: number;
  private _action: () => void;

  constructor(time: number, action: () => void) {
    this.time = time;
    this._action = action;
  }

  invoke() { this._action(); }
}

class Developer implements IAgent {
  private _event: Event | null;
  private _data: Data;

  constructor(data: Data) {
    this._data = data;
    this._event = new Event(0, () => this._run(0));
  }

  event() {
    return this._event;
  }

  private _run(time: number): void {
    this._data.time = time
    this._event = null;
    if (this._data.requirements === 0) return;
    this._data.requirements--;
    const implTime = -1 * Math.log(1 - Math.random());
    const newTime = this._data.time + implTime;
    this._event = new Event(this._data.time + implTime, () => this._run(newTime));
  }
}

Generation of the samples:

function run() {
  const samples: number[] = [];
  for (let i = 0; i < 10000; i++) {
    const data = new Data();
    let e = new Environment();
    e.agents.push(new Developer(data));
    e.run();
    samples.push(data.time);
  }
  return samples;
}

The probability density estimated with histograms:

function pdf(data: number[]) {
  const intervals = 100, r: number[] = [];
  const max = Math.max(...data), min = Math.min(...data);
  const step = (max - min) / intervals;
  for (let i = 0; i < intervals; i++) {
    r[i] = { x: step * i + min, y: 0 };
  }
  for (const value of data) {
    const index = Math.min(intervals - 1, Math.ceil((value - min) / step));
    const point = r[index];
    point.y += 1 / data.length / step;
  }
  return r;
}