Independent comprehension
- Lesson 15
- 45 min
- Module 5: Deliver maintainable work
Lesson 15 of 15 · AI-Assisted Software DevelopmentIndependent comprehension
1. Establish a safe working setup
2. Write actionable tasks
3. Review generated work
4. Debug and test with assistance
5. Deliver maintainable work
What you will be able to do
Before you ask anyone to review a change, explain every line that changes behaviour, without help. Show which test guards each line, open a pull request that a reviewer can check alone, and name what only a person can still decide.
Before you startWhat to know and to have ready
What you need to know
- Pull requests and their description (“Problem, Change, How I checked”), from Pull request sequence in Git and Team Development Essentials.
- A remote as a bare repository in a folder, from Synchronize deliberately.
- A fresh copy as the proof of a handoff, from Quality and handoff in Python for Practical AI Engineering.
What you need on your computer
- The project
~/projects/ticket-cleaner-assistafter the last lesson, with its environment active.python -m pytestprints42 passed, andgit status --shortprints nothing. mainhas only the course’s start. Check it:git diff --stat main...feature/category-option -- ticket_cleaner/ends with3 files changed, 34 insertions(+), 7 deletions(-). If it does not, follow the reset steps under Module check.- About 40 MB of free space, for a second copy of the project with its own environment.
- No account, no website and no money. The remote is a folder on your computer.
The cook who serves the dish
Section titled “The cook who serves the dish”A guest at a restaurant asks: “Is there any nut in this sauce?” A helper made the sauce, not the cook. “I did not make it” is not an answer that the guest can use. The cook who serves the dish answers for every ingredient. So a careful cook knows what went in before the plate leaves the kitchen.
A pull request is the plate leaving the kitchen. The assistant was the helper.
The problem: “the tests pass, merge it”
Section titled “The problem: “the tests pass, merge it””Your branch has the whole change of this course: the option, the fix, the refactor and the documents. All 42 tests pass. Tomás reads the result:
“Forty-two green tests. What else is there to explain? Open the pull request.”
Here is one thing that no test and no task mentioned. Run the sample file’s pending report on main, then on your branch:
git switch mainpython -m ticket_cleaner data/tickets.csv --status pendinggit switch feature/category-optionpython -m ticket_cleaner data/tickets.csv --status pendingOpen reports/summary.json after each run. The author’s results:
main: {"LOGIN": 1, "billing": 1}feature/category-option: {"billing": 1, "login": 1}The sample file has one ticket stored as LOGIN (T-1010, line 11 of data/tickets.csv). The fix of Module 4 changed how every category is stored, so this report changed too. That is correct: it is the bug fix doing its job. But Grace’s team may compare this month’s report with last month’s. A LOGIN line that disappears will look like a lost ticket, unless someone tells them.
The baseline test did not catch it, because it pins only the default open report. So “the tests pass” did not tell you about it. Only reading the line, and asking what else it touches, did.
The idea: explain every consequential line
Section titled “The idea: explain every consequential line”Independent comprehension means that you can explain your final change without the assistant, line by line, to a reviewer who asks. Not every line needs the same attention. A consequential line changes behaviour, a contract, stored data or security: a condition, a default, a key in the report, a cleaning rule. A blank line, a docstring or a reformatted call is not consequential, although you still read it.
For each consequential line, you can answer four questions:
- What does it do, with a concrete input?
- Why is it there: which requirement or bug needs it?
- Which test fails without it?
- What else does it touch?
The first two go into a traceability table: each requirement, the change that meets it, and the evidence. You met the first half of it in Problem and acceptance criteria. The pull request completes it with the diff and the tests.
Text description of the diagram
| Requirement | Change (file) | Evidence (test) |
|---|---|---|
| 1. Count only the chosen categories | report.py: build_summary(..., categories) |
test_one_category |
| 2. The option can be given more than once | cli.py: action="append" |
test_a_ticket_in_any_chosen_category_counts, test_report_for_some_categories |
| 3. Capital letters and spaces do not matter | report.py: clean_category(name) |
test_category_name_ignores_capital_letters_and_spaces |
4. categories key only with the option |
report.py: if chosen is not None |
test_categories_key_only_with_the_option, tests/test_baseline.py |
| 5. Whole-file counts unchanged | report.py: filters after the totals |
test_categories_do_not_change_the_whole_file_counts |
| 6. Unknown category is not an error | (no code needed) | test_unknown_category_selects_nothing |
| 7. Help and README describe the option | cli.py help, README.md |
test_help_describes_the_category_option, tests/test_readme.py |
| Bug: one line per team | parsing.py: clean_category cleans first |
test_capital_letters_do_not_make_a_new_category (it failed before the fix), test_web_form_export_has_one_line_per_team |
A tiny example: break one line on purpose
Section titled “A tiny example: break one line on purpose”How do you know that a test really guards a line? Change the line on purpose, run the tests, and put it back. If no test fails, the line has no guard, or the line does nothing. Testers call a systematic form of this mutation testing. By hand, for a few lines, it takes minutes.
Take requirement 2. In ticket_cleaner/cli.py, delete one line from the --category option, and save:
parser.add_argument( "--category", action="append", metavar="NAME",Predict which tests fail. Then run them:
python -m pytestFAILED tests/test_cli.py::test_report_for_some_categories - ...========================= 1 failed, 41 passed in 0.05s =========================One test. Without action="append", args.category is one text instead of a list, and the last --category wins. The unit tests in test_report.py call build_summary() with a list, so they cannot see it. Only the test that runs the whole command can. Put the line back, and check that nothing else changed:
git restore ticket_cleaner/cli.pygit status --shortgit status --short prints nothing. The author did the same for six consequential lines:
Text description of the diagram
| Line changed on purpose | Result |
|---|---|
parsing.py: text = clean_text(value) → str(value).strip() (undo the fix) |
10 failed, 32 passed |
report.py: clean_category(name) → name |
4 failed, 38 passed |
report.py: clean_category(name) → name.strip().lower() (the rule of Module 4) |
1 failed, 41 passed: only test_web_form_names_work_in_the_category_option |
report.py: the categories key in every report |
2 failed, 40 passed: the baseline test and test_categories_key_only_with_the_option |
report.py: "valid_records": len(selected) |
4 failed, 38 passed |
cli.py: no action="append" |
1 failed, 41 passed: only test_report_for_some_categories |
Larkfield: the practice pull request
Section titled “Larkfield: the practice pull request”This is the module’s practice task: submit the feature and the defect fix as a reviewable practice pull request. As in the Git course, it is a local simulation. The remote is a bare repository in a folder, and you play both people: Amira, who opens it, and Kwame, who reviews it.
-
See the whole change at once. Two dots list the commits on the branch; three dots show the changes since the branch started:
Terminal window git log --oneline main..feature/category-optiongit diff --stat main...feature/category-option -- ticket_cleaner/Output (the author's run) 51b4ddf Document the cleaning rule, the limitations and the decisions23d515b Clean the --category names with the same rule as the ticketsb52a2f9 Copy the snapshot end-of-m04ticket_cleaner/cli.py | 8 +++++++-ticket_cleaner/parsing.py | 9 ++++++---ticket_cleaner/report.py | 24 +++++++++++++++++++++---3 files changed, 34 insertions(+), 7 deletions(-)The author’s run made Modules 1 to 4 as one commit, as the reset steps do. Your list has eleven commits: nine from Modules 1 to 4, and two from this module. The
--statline is the same. -
Read the code part, with the table above next to it:
Terminal window git diff main...feature/category-option -- ticket_cleaner/For each changed line, say aloud: what it does, why, which test, and what else. Lines 31 to 36 of
cli.pyadd the option. Line 71 passes it on. Lines 23 and 24 ofparsing.pyare the fix. Inreport.py, line 1 imports the one cleaning rule, lines 38 to 41 choose and filter, and lines 50 and 51 add thecategorieskey. The rest are docstrings and the new signature. -
Break one line on purpose, as in the tiny example. Choose a line from the table, but not the one of the example. Predict, run
python -m pytest, thengit restorethe file. Write down the line and the test that caught it. You will use it in the completion check. -
Write the pull request description. It lives in the project, as
docs/pull-request.md, so that it travels with the branch. Copy it from the snapshot, then read every row against your own runs:Terminal window cp ../nextia-learning-labs/C15/snapshots/end-of-m05/docs/pull-request.md docs/Terminal window Copy-Item ..\nextia-learning-labs\C15\snapshots\end-of-m05\docs\pull-request.md docs\It has the parts of the Git course, and three more: the table, the use of an assistant, and what a reviewer should look at. The full text is below these steps.
-
Run the checks that the description promises, and commit it:
Terminal window python -m pytestruff check .ruff format --check .python -m ticket_cleaner data/web-form-export.csv --category Sign-ingit add docs/pull-request.mdgit commit -m "Describe the pull request"Output (part) ============================== 42 passed in 0.04s ==============================All checks passed!24 files already formatted[feature/category-option bd7c066] Describe the pull request1 file changed, 45 insertions(+)create mode 100644 docs/pull-request.mdThe report of the
Sign-incommand has"by_category": {"login": 4}. -
Make the remote next to the project. Use a new name:
team-remote.gitfrom the Git course holdsticket-notes, a different project.Terminal window cd ..git init --bare ticket-cleaner-remote.gitcd ticket-cleaner-assistgit remote add origin ../ticket-cleaner-remote.gitOutput Initialized empty Git repository in ~/projects/ticket-cleaner-remote.git/Your output shows the full path of your folder.
-
Push both branches. The target
mainmust exist on the remote, so that a reviewer can compare:Terminal window git push -u origin maingit push -u origin feature/category-optionOutput To ../ticket-cleaner-remote.git* [new branch] main -> mainbranch 'main' set up to track 'origin/main'.To ../ticket-cleaner-remote.git* [new branch] feature/category-option -> feature/category-optionbranch 'feature/category-option' set up to track 'origin/feature/category-option'.The pull request is
feature/category-option → main, and its description isdocs/pull-request.md.
Here is the description. Read it as a reviewer would, with the diff open:
# Pull request: report by category, and one line per team
Branch: `feature/category-option` → `main`
## What and why
- Grace asked for the report of one or a few categories (`docs/tasks/category-option.md`).- Grace's bug: with the web form's export, one team appeared under several names (`docs/tasks/category-names-bug.md`).
## Requirement → change → evidence
| Requirement | Change (file) | Evidence ||---|---|---|| 1. Count only the chosen categories | `report.py` `build_summary(..., categories)` | `test_one_category` || 2. The option can be given more than once | `cli.py` `action="append"` | `test_a_ticket_in_any_chosen_category_counts`, `test_report_for_some_categories` || 3. Capital letters and spaces do not matter | `report.py` uses `clean_category` | `test_category_name_ignores_capital_letters_and_spaces` || 4. `categories` key only with the option | `report.py` | `test_categories_key_only_with_the_option`, `test_baseline.py` || 5. Whole-file counts unchanged | `report.py` filters after the totals | `test_categories_do_not_change_the_whole_file_counts` || 6. Unknown category is not an error | (no code needed) | `test_unknown_category_selects_nothing` || 7. Help and README describe the option | `cli.py` help, `README.md` | `test_help_describes_the_category_option`, `test_readme.py` || Bug: one line per team | `parsing.py` `clean_category` cleans first | `test_capital_letters_do_not_make_a_new_category` (fails before the fix), `test_web_form_export_has_one_line_per_team` |
## How I checked it
```shpython -m pytest # 42 passedruff check .ruff format --check .python -m ticket_cleaner data/web-form-export.csv --category Sign-in```
## Use of a coding assistant
An assistant suggested the first version of the option and of the fix. I readevery line, ran the tests myself, and changed or rejected some suggestions:see `DECISIONS.md`. The regression test was written first and failed on the oldcode.
## What a reviewer should look at
- `parsing.py`: the fix changes how every category is stored. Reports of older files can change where a category had capital letters (`LOGIN` → `login`).- `CATEGORY_ALIASES` is owned by the web form's team.Two parts are new compared with the Git course. Use of a coding assistant says plainly what the assistant did and what you did. It does not hide the assistant, and it does not blame it. What a reviewer should look at points to the pending report change of the problem above. You found it; now the reviewer does not need to.
On GitHub, the steps are the same up to the push. Then Compare & pull request opens a form: paste the description, set base to main, and select Create pull request. That is optional, and not needed for this course.
Kwame reviews: a fresh clone, a fresh environment
Section titled “Kwame reviews: a fresh clone, a fresh environment”Kwame does not trust “42 passed” in a description, even from Amira. He runs the checks himself, in a new clone, with a new environment. That is the completion check of this module, and you do it now as Kwame.
-
Clone the remote into a new folder, next to the project, and switch to the branch:
Terminal window cd ~/projectsgit clone ticket-cleaner-remote.git kwame-reviewcd kwame-reviewgit switch feature/category-optionOutput Cloning into 'kwame-review'...done.Switched to a new branch 'feature/category-option'branch 'feature/category-option' set up to track 'origin/feature/category-option'. -
Make a fresh environment from the lock file. Deactivate the old one first, with
deactivate:Terminal window python3 -m venv .venvsource .venv/bin/activatepython -m pip install -r requirements-lock.txtTerminal window python -m venv .venv.venv\Scripts\Activate.ps1python -m pip install -r requirements-lock.txt -
Run every check of the description, from this clone:
Terminal window python -m pytestruff check .ruff format --check .python -m ticket_cleaner data/web-form-export.csv --category Sign-inpython -m ticket_cleaner data/web-form-export.csvOutput (the author's run, the end of each check) ============================== 42 passed in 0.06s ==============================All checks passed!24 files already formattedThe first report has
"by_category": {"login": 4}and"categories": ["login"]. The second, without the option, has{"account": 1, "billing": 3, "login": 4, "shipping": 2}: one line per team. -
Read the change as a reviewer:
git diff main...feature/category-option. Kwame asks his usual question: “What can this change touch?” Answer it from the diff. It touches two report keys and one cleaning rule that every ticket goes through. It adds no file access and no package, and it sends nothing outside.
The same results in a fresh clone prove that every needed file is committed, and that nothing depends on your own environment.
What still needs a human or domain review
Section titled “What still needs a human or domain review”Some questions no test can answer, because the answer is outside the code. Name them in the pull request and in DECISIONS.md, with the person who can answer:
Text description of the diagram
Amira pushes the branch to ticket-cleaner-remote.git. Kwame clones it, makes a new environment from the lock file, and gets 42 passed and {"login": 4} for Sign-in.
Three questions still need a person:
- Older reports change. The sample file’s
pendingreport goes from{"LOGIN": 1, "billing": 1}to{"billing": 1, "login": 1}. Grace decides how to explain it to her team. - The alias list.
CATEGORY_ALIASESmust match the web form. Grace’s team owns the form. - Real exports. The tests use made-up data. A real export can have other spellings, such as
Log inwith a space.
A domain review is honest about where the code’s knowledge ends. Kwame approves the code, and Grace answers the three questions. Then the branch can be merged, as in the Git course.
When to do this, and what it costs
Section titled “When to do this, and what it costs”Explain every consequential line before every pull request, whoever wrote it. With an assistant, nobody else has read the code. If you cannot explain a line, learn it or remove it.
Break lines on purpose for the lines that matter most: a fix, a condition, a key of the output. It shows which lines have only one guard.
Ask for a domain review when the answer depends on people or data outside the project. Write the question and the person’s name. Do not answer it with a guess.
The cost is time before the review, and it saves more time during it. A traceability table lets a reviewer check a change in minutes. “Fixed stuff, tests pass” sends the reviewer back to you with questions.
Common mistake
git push: does not appear to be a git repository
Symptom: you push the branch for the first time, and Git refuses:
fatal: '../ticket-cleaner-remote.git' does not appear to be a git repositoryfatal: Could not read from remote repository.
Please make sure you have the correct access rightsand the repository exists.Cause: the bare repository is in the wrong place. Here, git init --bare ran inside the project, because cd .. was missed. The remote’s path, ../ticket-cleaner-remote.git, points to a folder that does not exist.
Diagnose: git status --short shows the bare repository as a new folder inside the project:
?? ticket-cleaner-remote.git/Fix: delete that folder inside the project, after you check its name: rm -rf ticket-cleaner-remote.git (Windows: Remove-Item -Recurse -Force ticket-cleaner-remote.git). Then do step 6 again, with cd .. first. Never commit a bare repository into a project.
Completion check
Section titled “Completion check”The module’s completion check: run the checks independently, and explain every consequential change. Show these:
- In
kwame-review, a fresh clone with a fresh environment, the checks pass. You see42 passed,All checks passed!and24 files already formatted, and the two reports of the Kwame steps. - In your own words, for five consequential lines of
git diff main...feature/category-option -- ticket_cleaner/, the four answers: what, why, which test, what else. Use at least one line from each of the three files. - The line that you broke on purpose in step 3, and the test that caught it.
- One question for a domain review that is not in the pull request yet, with the person who can answer it.
Show a worked solution
Five lines, four answers each:
| Line | What | Why | Which test | What else |
|---|---|---|---|---|
parsing.py 23: text = clean_text(value) |
" LOGIN " → "login" before the alias lookup |
Grace’s bug: one line per team | test_capital_letters_do_not_make_a_new_category (10 tests fail without it) |
every category of every file, so older reports change (LOGIN → login) |
cli.py 33: action="append" |
--category billing --category shipping gives a list of both |
criterion 2 | test_report_for_some_categories (the only one) |
without it, the last option wins, silently |
report.py 40: chosen = sorted({clean_category(name) ...}) |
"Sign-in" → ["login"]; duplicates removed, sorted |
criteria 3 and 4, and the web form’s names | test_web_form_names_work_in_the_category_option and 3 others |
the categories key shows the cleaned names, not what the user typed |
report.py 41: selected = [... if ticket.category in chosen] |
keeps only the tickets of the chosen categories | criterion 1 | test_one_category |
it runs after filter_by_status(), so --status still applies |
report.py 50–51: if chosen is not None: |
adds categories only with the option |
criterion 4, and Grace’s spreadsheet | test_categories_key_only_with_the_option, tests/test_baseline.py |
the report without the option stays the same as before |
A domain question: “Does anything else read reports/summary.json, for example a script of the finance team? If so, its owner must know about the categories key.” The person: Grace, who knows who receives the report.
Module check
Section titled “Module check”You have finished the module when you can show these on your own project:
-
git log --onelineshows thirteen commits. The last three areDescribe the pull request,Document the cleaning rule, the limitations and the decisionsandClean the --category names with the same rule as the tickets. After a reset, you have fewer commits, and that is fine. -
python -m pytestprints42 passed, in your project and inkwame-review. -
git branch -vvshows both branches with an upstream onorigin. -
On macOS and Linux, this command prints nothing, because your project matches the snapshot
end-of-m05:Terminal window diff -rq . ../nextia-learning-labs/C15/snapshots/end-of-m05 -x .git -x .venv -x reports -x __pycache__ -x .pytest_cache -x .ruff_cache
Reset. If your project is broken, follow the reset steps of Context and boundaries with the snapshot end-of-m04 in place of end-of-m01. They keep main at the course’s start, so the pull request shows the whole change. The last line of python -m pytest is then 39 passed: the project at the end of Module 4, ready for Refactor selectively. To skip to the end of this module, use end-of-m05 instead: 42 passed. If ticket-cleaner-remote.git or kwame-review already exist, rename them too before step 6.
Knowledge check
Section titled “Knowledge check”Check your understanding
What makes a line of a change 'consequential'? Give one consequential and one non-consequential line from the code part of this course's change.
Show the answer
A consequential line changes behaviour, a contract, stored data or security. text = clean_text(value) in parsing.py is consequential: it changes how every category is stored. The docstring of build_summary() is not: it changes no behaviour, although a wrong docstring would mislead the next reader, so you still read it. A useful test: if you break the line on purpose, should a test fail? For a consequential line, yes.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Predict: you change line 40 of report.py back to name.strip().lower(). How many tests fail, and what would you conclude?
Show the answer
One: test_web_form_names_work_in_the_category_option, and 1 failed, 41 passed. The other tests use names that both rules clean the same way. So this line has exactly one guard. Keep that test, and name it in the pull request and in DECISIONS.md, as decision 3 does. If a future change deletes it as “duplicate”, the two-rules bug can come back with all other tests green.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
All 42 tests passed in Amira's folder. Why does Kwame run them again in a fresh clone with a fresh environment, and what could differ?
Show the answer
A passing run in the author’s folder can depend on things that are not in the commits. Examples are a file that was never added, or an edit that was never committed. Another is a package that is in the author’s environment but not in the lock file. A fresh clone has only what was pushed, and a fresh environment has only what the lock file lists. If the same 42 tests pass there, the change is complete and reproducible. It is the same proof as the fresh copy in the Python course.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
This lesson
- Independent comprehension: you explain every consequential line without the assistant. What it does, why, which test fails without it, and what else it touches.
- A traceability table links each requirement to its change and its evidence. It is the heart of
docs/pull-request.md. - Break a line on purpose to see its guard. Two lines of this change have exactly one test.
- The fix also changes the sample file’s
pendingreport (LOGIN→login). No test caught it; reading did. It is now in the pull request. - Kwame’s fresh clone and fresh environment:
42 passed. The alias list, real exports and older reports still need a person.
Module 5: deliver maintainable work
- Refactor only where the change needs it: one cleaning rule made
--category Sign-inwork. Everything else became a separate task. - Document decisions with their evidence, and write no claim that you did not check.
tests/test_readme.pykeeps two README claims true. - Open a pull request that a reviewer can check alone: the table, the checks, the use of an assistant, and the open questions.
- Your project matches
end-of-m05, the course-end project:42 passed.
The course
You started with a project, a feature request and a bug report. You prepared a safe setup and wrote a task that someone can verify. You reviewed generated changes line by line, debugged from evidence, and delivered a change that you can explain. Next, the case studies apply the same habits to two small projects that are new to you, written for the course. One has a bug in unfamiliar code; the other needs a dependency upgrade. One of them is required for the certificate, with the final assignment.