Milestone 4 · Software Technology

The final product: clean code, tests, release

Milestone 4: finish against the Definition of Done – clean code, edge- and failure-case tests, high model coverage – then release through a release branch and a tag, and present a demo and a gameplay video.

10 min readDefinition of DoneClean code refactoringEdge and failure casesCoverage targetRelease and hotfix flowDemo and gameplay video

The last milestone, in week 13, is the finished game: every feature works without errors, the code is clean, the unit tests are thorough – including edge and failure cases – and there is a short gameplay video. Three weeks is not much time, so this milestone is mostly about finishing and fixing, not about new features.

What "finished" means

A feature is finished when it meets the team's Definition of Done – not when the code runs on the author's machine. At M4 every feature of the game is held to it:

Definition of Done

- [ ] the feature works and the issue's acceptance criteria are met
- [ ] it has unit tests (model layer) and they pass
- [ ] the pipeline is green
- [ ] the MR was reviewed, its threads resolved, and it is merged into develop
- [ ] the wiki is updated if the design changed
- [ ] the issue closed automatically
If anything on the list is missing, the issue is not done – even if "the code already works".

Clean code

Clean code reads like a description of what it does. Short methods with one job, names from the game's language (Board, isWalkable, collectItemAt), no magic numbers, no duplicated blocks, comments only where the why isn't obvious.

Before – what does d == 1 mean? what is 2?

public void step(int d) {
    if (d == 0 && p.y > 0 && m[p.y - 1][p.x] != 1) p.y--;
    else if (d == 1 && p.x < m[0].length - 1 && m[p.y][p.x + 1] != 1) p.x++;
    // ... two more branches like these
    if (m[p.y][p.x] == 2) { s += 10; m[p.y][p.x] = 0; }
}

After – the same behaviour, readable

public void step(Direction direction) {
    Position target = player.position().next(direction);
    if (!board.isWalkable(target)) return;
    player.moveTo(target);
    collectItemAt(target);
}
SmellFix
A method longer than a screenExtract the steps into named methods; the original becomes a list of calls.
Magic numbers: == 2, += 10Enums and named constants: TileType.ITEM, ITEM_SCORE.
The same block in three placesOne method, called three times.
Commented-out code, debug printsDelete them – git remembers the old version.
A model class importing the UIMove the UI part to the view; the model notifies through a listener.
Compiler or analyser warningsFix them, don't suppress them. The pipeline should already block them.

Tests that find bugs

Teams that only test the happy path ship games with spectacular bugs in the final demo: the player walks off the map, a second click on "Save" crashes, the game continues after game over. For every model method that can go wrong, think in three groups (Fig. 1).

move(player, direction)Happy pathfree tile ahead → player movesstate changes exactly oncelistener notified onceEdge casesstanding on the map borderthe only free tile lefttwo players, same targetFailure caseswall ahead → stays putoff the map → stays putafter game over → exceptionMost bugs in a final demo live in the middle and right boxes.
Fig. 1 · One method, three groups of cases. The happy path proves the feature exists; the edge and failure cases prove it holds.

A parameterised test covers a family of edge cases in one method:

Every corner, every direction off the map

@ParameterizedTest
@CsvSource({ "0, 0, LEFT", "0, 0, UP", "9, 5, RIGHT", "9, 5, DOWN" })
void moveOffTheMapLeavesPlayerInPlace(int x, int y, Direction direction) {
    Game game = Games.emptyBoard(10, 6).withPlayerAt(x, y);
    game.step(direction);
    assertEquals(new Position(x, y), game.player().position());
}

A failure case: the call is rejected

[Fact]
public void StepAfterGameOverIsRejected()
{
    var game = Games.Finished();
    Assert.Throws<InvalidOperationException>(() => game.Step(Direction.Up));
}

Coverage target

For the final product, aim for about 90% line coverage of the model, shown by the coverage badge in the README. The view and the entry point are excluded from the measurement (see milestone 3), so the number speaks about the code that holds the rules. The template's coverage gate starts at 80%; raise it once you're above it, so coverage can't slide back in the last week.

The release flow

Each milestone is released the same way: a release branch from develop gets only release preparation, is merged into main, main gets a tag, and main is merged back into develop (Fig. 2). The tag starts the pipeline's package and release jobs, which put a downloadable build on the project's Releases page.

mainhotfix/ABC-99release/v3.0developv20d1d2r1r2v30h1mb1v31mb2v3.0v3.1version, README, small fixes1 release branch from develop → 2 MR into main → 3 tag on main → 4 MR main → developHotfix: branch from main, MR into main, tag v3.1, then the same MR main → develop.
Fig. 2 · The release branch lets the team keep working on develop while the release is finished. Every change that reaches main – release or hotfix – goes back into develop.
MilestoneTag
M1 Requirements and design– (documentation only)
M2 Prototype Iv1.0
M3 Prototype IIv2.0
M4 Final productv3.0
A hotfix after a demov1.1, v3.1 …

The tag is vMAJOR.MINOR: MAJOR is the milestone, MINOR the hotfix. A different member does the release at each milestone, in the days before the demo – not during the session.

Releasing v3.0

git switch develop && git pull
git switch -c release/v3.0
# version in the build file, README, small fixes only
git commit -am "bump version to 3.0"           # the hook makes it "release: bump version to 3.0"
git push -u origin release/v3.0
# MR release/v3.0 → main, titled "Release v3.0": approve, green pipeline, merge

git switch main && git pull
git tag -a v3.0 -m "Release v3.0"
git push origin v3.0                            # the pipeline packages the game and creates the Release
# MR main → develop, so the version bump and the fixes reach develop

On the release branch only release preparation is allowed: the version in the build file, the README and wiki, small fixes found while trying the build. A bigger bug gets its own issue and a bugfix/ branch from the release branch. Never merge develop into the release branch – it would bring in unfinished work.

A hotfix after the demo

git switch main && git pull
git switch -c hotfix/ABC-99-crash-on-empty-save
# fix, commit, push, MR → main, approve, merge
git switch main && git pull
git tag -a v3.1 -m "Hotfix v3.1" && git push origin v3.1
# MR main → develop – put "Closes #99" in this one: issues close only on merges into develop

The demo and the gameplay video

Each team gets 5–10 minutes. Show the game working, then show how it was built – and split the parts between members; the same person can't present every milestone (Fig. 3).

Goal + scopemember ALive gameplaymember B plays, A narratesBoard, MRs, CImember CTests + coveragemember AQ&Aall0 min2 min4 min6 min8 min10 minDifferent members present at different milestones. The M4 gameplay video shows the same flow without live risk.
Fig. 3 · A ten-minute demo plan: most of the time on the running game, a slice on the process, and different voices for different parts.

Summary

  • Finished means the Definition of Done is met – criteria, tests, green pipeline, reviewed and merged, wiki updated.
  • Clean code: short methods, names from the game, no magic numbers, no dead code, no warnings.
  • Test the happy path, the edge cases and the failure cases; aim for about 90% coverage of the model.
  • Release: release branch → MR into main → tag → MR main → develop. Hotfix from main, tag vX.1, merge back.
  • Demo in 5–10 minutes with rotating presenters; record the gameplay video from the released build.