Duet Programming
Duet programming is a protocol for collaborative programming that provides
structure and organization for computer science learners. The purpose is
to define roles that allow both students to actively engage in the
design and implementation process at all times, albeit on different aspects
of the same program.
This is accomplished by modifying the Test-Driven Development
programming process.
Test-Driven Development is inspired by the idea that if tests are written
after the code has been designed and implemented, then it is harder to
integrate new functionality into the code than if the tests were identified
first. By designing tests and code together, many mistakes can be prevented
from entering the implementation.
This approach provides symmetry in the process, in which the same stage
of different tasks may be performed in parallel. That is, students design
their tests and code simultaneously, discussing any discrepancies and
making sure they are consistent before both moving on to the implementation
stage. For larger problems / projects or multi-question assignments, students
trade off between the tester and developer roles in order to learn both
important aspects of program development.
This protocol has been created to address a disadvantage of Pair Programming,
the dominant protocol for collaborative computer science learning. In that
protocol, only one person actively develops at a time (the driver), with
the second member assisting when necessary (the navigator).
This protocol can make it difficult for the navigator to stay engaged.
There are two main aspects to collaborative work. The first is how to
communicate with each other respectfully and constructively in order to
provide a supportive learning environment.
The second is to follow specific protocols that provide
phases of working independently and working together
in order to build skills towards completing software development entirely
independently.
Maintaining a supportive learning environment
Do:
-
communicate when either partner has questions, even if it is during the
independent work phases.
-
isten to your partner, especially when he/she has different ideas than
you do. There are many correct answers in programming - the interesting part
is exploring the advantages and disadvantages of different decisions. So ask
the other person why they did something before suggesting an alternative.
Together understand what the tradeoffs are and choose the best solution (if
a different solution is actually better - do not quibble over equivalent
solutions).
-
Rotate roles often. Once problems get sufficiently large, make sure you cut
them up into pieces. Switch roles for each piece.
-
Be patient. Different ideas are normal, and some students have more difficulty
communicating the ideas and reasons behind them than others. Do not mistake
quality of argument for quality of solution. Think about ways your partner is
correct rather than jumping to conclusions. Ask questions when you disagree.
-
Respect your partner. Everyone at U of Chicago has earned academic respect
by being accepted to this university. Even if you have more experience than
your partner, the purpose of these activities is to learn, not to be correct.
Respect your partner's ideas and provide positive feedback when merited, even
if you might have an idea you think is better.
-
Take breaks. If you work too long, you will be more easily frustrated, and you
might accidentally take that frustration out on your partner. You should not
work for more than an hour without a break.
-
Prepare. Make sure you have attended class and/or read the relevant resources
so that your partner does not need to teach you what you should have already
seen.
-
Practice good hygiene. This includes showering daily, wearing deodorant, and
wearing laundered clothing. Different cultures have different practices, but
when working in close physical proximity, it is important to be mindful of
local practices.
-
Have fun. Don't be afraid to make jokes, unless they could be interpreted
as insulting to the other person.
Don't:
-
control the other person. Make sure that each person has the chance to
explore solutions, even if they do not match your solution. If you are
making suggestions about what code the other partner produces, be very careful
that your ideas are enough better to merit the suggestion. It's important
that each student be able to try their ideas and get feedback from the system.
If you impose your ideas on your partner, you are robbing him/her of that
important learning opportunity.
-
be intimidated. Some of the material is quite challenging, which is what
partners are for. All of the material is chosen to match your experience.
-
be quiet. Yes, you should allow for independent work periods. But if you
have a question, that's what the collaboration is all about!
-
suffer in silence. If there are issues in your collaborative partnership,
please speak with an instructor to help resolve them. This experience should
enhance your learning, not detract from it.
Duet Programming Protocol
Duet Programming is divided into four phases. Each phases begins with
independent working and ends with a discussion.
From a high level, partner A will be the Implementer, and
partner B will be the Strategist. The Strategist has three jobs:
Think through all design decisions for their logic (rather than trial and
error), serve as the pair's spokesperson (all questions to instructors must
go through the Strategist), and design and implement the tests for this
function. The basic roles are augmented with mechanisms
to mitigate two potential disadvantages of such a split: load imbalance
and missed learning opportunities.
Load imbalance occurs if one partner finishes before the other. In
this case, the finished partner needs to find productive work to
assist the second partner. We have several suggestions below.
Missed learning opportunities occur if each partner only ever
learns the content of his or her own code. It is critical that students
understand how the entire code works, not just his or her own portion.
Therefore, students will debug the code the other partner wrote.
Before beginning duet programming, make sure you have completed the
preparatory work. This will make your collaborative programming more
efficient. Specifically, you need to have a version of main.c, x.h, and
x.c that compiles. main.c should contain a single function call to every
function in x.c. x.h provides function declarations for any function that
is defined in the assignment and/or main.c needs to call. x.c contains a
skeleton implementation for every function - for functions that return a
value, they return a value of the proper type.
One round of duet programming is performed for each problem. A problem
is defined as a single unit of completable work, typically a function.
In early labs, each function will involve a completely separate execution
of the protocol. It is conceivable that with more complex applications,
a family of functions would be created to solve a single problem. These would
be discussed together at the beginning and then solved separately during
the protocol.
Phase 0: Problem Clarification
Discuss the problem at hand. Make sure both of you have a shared
understanding of the task. Then begin to structure the solution, clarifying
the following aspects:
Questions you should discuss and agree on:
-
What does valid input look like?
-
What does invalid input look like (if applicable)?
-
What should it do with valid input?
-
What should it do with invalid input?
-
Is there only one category of valid input, or are there different
sets of valid input that should be treated differently?
-
How are you going to break the problem down (if applicable)?
Outcome: The result of this phase is a shared understanding of
the problem and what you need to do to solve it.
Phase 1: Solution Co-Design
Implementer:
In this phase, you will design your code and record that design in the form
of code comments (though you are also welcome to create diagrams to
either aid in the design development or to better communicate your
ideas to your partner).
Strategist: Identify input ranges (valid inputs and non-valid inputs with
borders), identify which inputs should be test cases, and calculate their
expected results.
Categorize test cases as "normal," "boundary," or "error."
Sketch out your tests as comments in main.c.
These tests are called black box tests because you know only the
interfaces, not the actual code, when you design them.
Load Imbalance Mitigation: If one partner is done substantially earlier
than the other, here are some ways to stay productive:
-
Look at your partner's existing design and see if it exposes anything you forgot.
-
Inspect boundary cases. If you are the implementer, check to make sure the
boundary values are correct.
-
Assist your partner by beginning pair programming. You will begin as the
navigator, which is much different from a back seat driver.
First familiarize yourself with what your partner is doing. Then think
through how you would do the design. Then ask questions about anything you
do not understand, being careful not to interrupt your partner’s train of
thought.
Once you are both finished, you are ready to discuss your work. Read
each other's code.
Discussion:
The purpose of this discussion is to see if either design
informs the other design. Check to see if the function design satisfies all of
the tests, and check to see if the tests test all uses of the function
design. Look for flaws or limitations in both designs so that you can solve
them before implementation. In addition, the
Strategist needs check for the solution making logical sense.
Outcomes: At the conclusion of this phase both students will have a
tangible plan, recorded in code comments, for the implementation phase of the
protocol.
Phase 3: Implementation
Implementer: Implement the function(s) you just designed. (in the .c file).
Compile only your code (using the -c flag).
Strategist: Implement the tests for the core function(s). Remember to
create test functions that encapsulate verification of specific tests.
Compile only your code (using the -c flag).
Load Imbalance Mitigation: If one partner is done substantially earlier
than the other, here are some ways to stay productive:
-
If you are the strategist, check the implementer's
if statements, for loops, etc., for proper boundaries. Check all
comparison operations to make sure they are correct (< vs > vs <= vs >=).
-
Check for common mistakes. Look for '=' where '==' should have been.
Check for a semi-colon after a for loop: for(..;..;..);
-
Assist your partner by beginning pair programming. You will begin as the
navigator, which is much different from a back seat driver.
First familiarize yourself with what your partner is doing. Then think
through how you would do the design. Then ask questions about anything you
do not understand, being careful not to interrupt your partner’s train of
thought.
Once you are both finished, you are ready to discuss your work.
Discussion part 1: Inspect the tester's code.
Look at input ranges from the black box tests. Is there
separate code to handle each case? If not, are the different ranges equivalent?
Inspect the code for the logic and reasons behind design choices.
Discussion part 2: Inspect implementer's code.
Do the boundaries for if and loops match the boundaries for the test cases?
Do the test cases exercise all paths in the code?
Does the tester understand the coding decisions the implementer made?
Inspect the code for the logic and reasons behind design choices.
Outcome: The result of this phase is a complete function(s) and full
set of test cases for the function(s) that compile individually.
Phase 3: Compilation and testing
This phase is performed together, however, primary responsibility for editing
files swaps in order to allow both students to familiarize themselves with the
code their partner worked. The tester debugs the function code, and
the implementer debugs the test code.
In the final phase of the protocol, the final compilation occurs followed by running of test cases on the implemented function(s). If any errors or omissions are found, the pair jointly debugs the solution. For this phase, the two code bases are swapped, so the Strategist is in charge of editing the Implementer’s code and vice versa.
Responsibility: Both are responsible for the completion of a functioning program and complete set of passing test cases.
Discussion: Throughout this phase, the pair works together to debug the program and write new test cases if needed. Concluding prompts may be included for specific problems by the instructor.
Outcomes: The result of the phase is a correctly functioning program and a comprehensive set of passing test cases.