Agile software development models in Python 3

Monday 10th of May 2010 09:35:07 PM


  Toggle Advanced Options



Managing development teams

Some links

Overview

Trying to effectively manage several teams of developers is a challenge, to say the least.

Obviously, in a typical software development process there are quite a few different actors and entities, including (in no specific order):

  • backlog items
  • bugs
  • developers
  • iterations (in the Agile/Scrum sense of the word)
  • (project) portfolio
  • projects
  • products
  • release criteria
  • requirements
  • risks
  • stakeholders
  • tasks
  • teams

Each one of the above actors or entities have their own properties and/or include references to the other actors/entities. For example, a bug has a description, a severity and a priority. A bug in itself, is linked back to a specific backlog-item. Backlog-items, however, also have a list of associated tasks, and so on.

Anyway, code speaks louder than words, so take a look at the (unfinished, Python 3) source below.

Source code

entity.py:

import uuid

from datetime import datetime

#===============================================================================

class Entity():
    
    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        self.__identifier = (uuid.uuid1() if identifier is None else identifier)
        self.__creation_date = datetime.now()
        self.name = name
        self.comment = comment
        self.active = active
        
    @property
    def identifier(self):
        return self.__identifier
    
    @property
    def creation_date(self):
        return self.__creation_date

developer.py:

import uuid

from entity import Entity

#===============================================================================

class Developer(Entity):
    
    def __init__(self, 
            identifier=uuid.uuid1(), 
            name='undefined', 
            comment='', 
            position='undefined'):
        super().__init__(identifier, name, comment)
        
        self.__position = position
        self.__knowledge_areas = []
        
    @property
    def position(self):
        return self.__position
    
    @property
    def knowledge_area(self):
        return self.__knowledge_areas
    
    def add_knowledge_area(self, knowledge_area):
        self.__knowledge_areas.append(knowledge_area)
        
    def remove_knowledge_area(self, knowledge_area):
        self.__knowledge_areas = 
            list(item for item in self.__knowledge_areas if item != knowledge_area)

task.py:

from entity import Entity
from developer import Developer

#===============================================================================

class Task(Entity):
    
    def __init__(self,
            description,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        super().__init__(identifier, name, comment, active)
        self.description = description
        # one of: not started, in progress, impeded or done
        self.__status = 'not started'
        # list of Developer objects of which the first one is the point person
        self.__developers = []  
        # minimum amount of time that a task can take
        self.__estimated_hours = 1
        
    @property
    def status(self):
        return self.__status
    
    @status.setter
    def status(self, value):
        states = {'not started', 'in progress', 'impeded', 'done'}
        if len(states - {value.lower()}) == 4:
            # temporary code, should raise an appropriate exception, 
            # i.e., unknown status
            pass  
        else:
            self.__status = value
    
    @property
    def developers(self):
        return self.__developers
    
    @property
    def estimated_hours(self):
        return self.__estimated_hours
    
    @property
    def point_person(self):
        return self.__developers[0]
    
    @point_person.setter
    def point_person(self, value):
        # check to see if the point person (passed through as a parameter) 
        # is already in the list of developers assigned to this task and 
        # if she is, set her as the first person in the list of developers
        # effectively making her the point person for this task 
        # (see the 'point_person' property). If the point person is not 
        # in the list of developers (i.e., assigning a new developer to 
        # the task), then prepend her to the list of developers.
        pass  
    
    def add_developer(self, developer):
        self.__developers.append(developer)
        
    def remove_developer(self, identity):
        pass

backlogitem.py:

from entity import Entity
from task import Task

#===============================================================================

class BacklogItem(Entity):
    
    def __init__(self,
            description,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        super().__init__(identifier, name, comment, active)
        
        self.description = description
        self.__tasks = []  # list of Task objects
        self.__bugs = []  # list of Bug objects
        
    @property
    def tasks(self):
        return self.__tasks
    
    @property
    def bugs(self):
        return self.__bugs
    
    def add_task(self, task):
        self.__tasks.append(task)
        
    def remove_task(self, identity):
        pass
    
    def add_bug(self, bug):
        self.__bugs.append(bug)
        
    def remove_bug(self, identity):
        pass

Updated on May 03, 2010

team.py:

from entity import Entity

class Team(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True):

        super().__init__(identifier, name, comment, active)
        self.__developers = []  # list of Developer objects

    @property
    def developers(self):
        return self.__developers

    def add_developer(self, developer):
        self.__developers.append(developer)

    def remove_developer(self, identity):
        pass

stakeholder.py:

from entity import Entity

#===============================================================================

class Stakeholder(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        super().__init__(identifier, name, comment, active)

risk.py:

from entity import Entity

#===============================================================================

class Risk(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True,        
            description='undefined',
            probability=1,
            impact=0,
            response_plan=False):
        super().__init__(identifier, name, comment, active)
        self.description = description
        self.probability = probability
        self.impact = impact
        self.response_plan = response_plan

    @property
    def score(self):
        return self.probability * self.impact

requirement.py:

from entity import Entity
from stakeholder import Stakeholder

#===============================================================================

class Requirement(Entity):

    def __init__(self,
            description,
            identifier=None,
            name='undefined',
            comment='',
            active=True,
            requestor=None):
        super().__init__(identifier, name, comment, active)
        self.description = description
        self.__requestor = requestor  # reference to a Stakeholder object

    @property
    def requestor(self):
        return self.__requestor

    @requestor.setter
    def requestor(self, value):
        # Do we want to limit the requestor to being the product owner? If that is the case,
        # then we can remove this property completely from the Requirement class as the 
        # product owner is already tracked in the Product class.
        self.__requestor = value

releasecriterion.py:

#===============================================================================

class ReleaseCriterion:
  
    def __init__(self,
            description,
            comment=''):
        self.description = description
        self.comment = comment
        self.specific = True
        self.measurable = True
        self.attainable = True
        self.relevant = True
        self.trackable = True

    @property
    def is_valid(self):
        return self.specific 
            and self.measurable 
            and self.attainable 
            and self.relevant 
            and self.trackable

projectscope.py:

from product import Product

#===============================================================================

class ProjectScope:

    def __init__(self, description):
        self.description = description
        self.__products = []  # list of Product objects
        self.__dependencies = []  # list of other Project objects, i.e., dependencies
        self.__release_critera = []  # list of ReleaseCriterion objects

    @property
    def products(self):
        return self.__products

    @property
    def dependencies(self):
        return self.__dependencies

    @property
    def release_criteria(self):
        return self.__release_criteria

    @property
    def is_valid(self):
        result = True
        for release_criterion in self.__release_criteria:
            if release_criterion.is_valid == False:
                result = False
                break
        return result

project.py:

from entity import Entity
from projectscope import ProjectScope
from risk import Risk
from iteration import Iteration

#===============================================================================

class Project(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True,
            description='undefined'):
        super().__init__(identifier, name, comment, active)

        self.__scope = ProjectScope(description)
        self.__stakeholders = []  # list of Stakeholder objects
        self.__risks = []  # list of Risk objects
        self.__iterations = []  # list of Iteration/Sprint objects

        # What about the 'project driver' matrix? That is:
        # Priority          Rank
        # ======================
        # Release date      1
        # Feature set       2
        # Low defects       3

    @property
    def scope(self):
        return self.__scope

    @property
    def stakeholders(self):
        return self.__stakeholders

    @property
    def risks(self):
        return self.__risks

    @property
    def iterations(self):
        return self.__iterations

    def add_stakeholder(self, stakeholder):
        pass

    def remove_stakeholder(self, identity):
        pass
   
    def add_risk(self, risk):
        pass

    def remove_risk(self, identity):
        pass

    def add_iteration(self, iteration):
        pass

    def remove_iteration(self, identity):
        pass

product.py:

import uuid

from entity import Entity

#===============================================================================

class Product(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True,
            description='undefined',
            product_owner=None):
        super().__init__(identifier, name, comment, active)

        self.description = description
        self.__requirements = []  # list of Requirement objects
        self.__product_owner = product_owner

    @property
    def requirements(self):
        """Return a list of the software functionality."""
        return self.__requirements

    @property
    def product_owner(self):
        return self.__product_owner

    @product_owner.setter
    def product_owner(self, value):
        self.__product_owner = value

    def add_requirement(self, requirement):
        self.__requirements.append(requirement)

    def remove_requirement(self, identity):
        pass

portfolio.py:

from entity import Entity
from project import Project
from team import Team

#===============================================================================

class Portfolio(Entity):

    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        super().__init__(identifier, name, comment, active)

        self.__projects = []  # list of Project objects (sinks)
        self.__teams = []  # list of Team objects (sources)

    @property
    def projects(self):
        return self.__projects

    @property
    def teams(self):
        return self.__teams

    def add_project(self, project):
        self.__projects.append(project)

    def remove_project(self, identity):
        pass

    def add_team(self, team):
        self._teams.append(team)

    def remove_team(self, identity):
        pass

iteration.py:

from entity import Entity
from backlogitem import BacklogItem

#===============================================================================

class Iteration(Entity):

    def __init__(self,
            start_date,
            end_date,
            identifier=None,
            name='undefined',
            comment='',
            active=True):
        super().__init__(identifier, name, comment, active)

        self.start_date = start_date
        self.end_date = end_date
        self.__backlog_items = []  # list of (committed) BacklogItem objects

    @property
    def backlog_items(self):
        return self.__backlog_items

    def add_backlog_item(backlog_item):
        self.__backlog_items.append(backlog_item)

    def remove_backlog_item(backlog_item):
        pass

bug.py:

from entity import Entity

#===============================================================================

class Bug(Entity):
   
    def __init__(self,
            identifier=None,
            name='undefined',
            comment='',
            active=True,
            description='undefined',
            severity='critical',
            priority='now'):
        super().__init__(identifier, name, comment, active)

        self.description = description
        self.__severity = severity
        self.__priority = priority

    @property
    def severity(self):
        return self.__severity

    @property
    def priority(self):
        return self.__priority

    @severity.setter
    def severity(self, value):
        # Severity is Technical but Absolute: an assessment of the impact of the bug
        # without regard to other work in the queue or the current schedule.
        states = {'critical', 'high', 'medium', 'low'}
        if len(states - {value.lower()}) == 4:
            # temporary code, should raise an appropriate exception, e.g., unknown severity
            pass
        else:
            self.__severity = value

    @priority.setter
    def priority(self, value):
        # Priority is Business, but Relative: a subjective evaluation of how important
        # an issue is, given other tasks in the queue and the current schedule.
        states = {'now', 'p1', 'p2', 'p3'}
        # Now: drop everything and take care of it as soon as you see this (usually
        # for blocking bugs) 
        # P1: fix before next build to test 
        # P2: fix before final release 
        # P3: we probably won't get to these, but we want to track them anyway 
        if len(states - {value.lower()}) == 4:
            # temporary code, should raise an appropriate exception, e.g., unknown priority
            pass  
        else:
            self.__priority = value

To be continued







Google