Software engineering foundations: CCDV-F study guide
CCDV-F · Applications and Integration, topic weight 7.4% of the exam
Software Engineering Foundations is a 7.4% topic in Applications and Integration, which makes up 33.1% of the CCDV-F exam. It tests everyday engineering practice applied to Claude work: calling the API asynchronously, handling JSON, keeping prompts in version control, running Claude Code in the delivery pipeline, and using it to review and refactor code.
What the official guide covers
The Claude Certified Developer Foundations exam guide (version 1.0, effective July 2026) describes this topic as core engineering principles, including REST APIs, JSON, asynchronous programming, version control, SDLC integration, code review and "small- and large-scale refactoring".
| What the guide lists | What it means in practice |
|---|---|
| REST APIs | The Claude API is HTTPS and JSON: POST /v1/messages, headers and status codes. The SDK layer is covered in Technical Fundamentals |
| JSON | Request and response bodies, tool input schemas, structured outputs |
| Asynchronous programming | AsyncAnthropic, asyncio, bounded concurrency |
| Version control | Prompts, tool schemas and model IDs in git; Claude Code commits, pull requests and worktrees |
| SDLC integration | claude -p in CI and the Claude Code GitHub Action |
| Code review | /code-review locally, Code Review on pull requests, a fresh session as reviewer |
| Small- and large-scale refactoring | Plan mode and tests for small changes; fan-out across files for migrations |
JSON in a Claude application
Everything you send and receive is JSON. Three places matter most:
- Tool inputs. Claude returns a
tool_useblock whoseinputis a JSON object that should match yourinput_schema. When streaming, it arrives as partial strings ininput_json_deltaevents; join them before you parse. Addstrict: trueto a tool to enforce its schema. - Structured outputs. Set
output_config.formatto{"type": "json_schema", "schema": {...}}for a reply in a fixed shape. In Python,client.messages.parse()takes a Pydantic model asoutput_formatand returnsparsed_output. - Your own records. Store prompts, eval cases and results as JSON or JSONL so they diff cleanly in git.
Validating and parsing Claude's output defensively is covered in Output Handling.
Asynchronous calls
The Python SDK has a sync client, Anthropic, and an async client, AsyncAnthropic, with the same methods. In an async web service, a sync call blocks the event loop and every other request waits. For many independent calls, run them concurrently but cap how many are in flight:
import asyncio
import os
from anthropic import AsyncAnthropic
client = AsyncAnthropic()
MODEL = os.environ["CLAUDE_MODEL"]
in_flight = asyncio.Semaphore(8) # cap concurrent requests
async def summarise(ticket: str) -> str:
async with in_flight:
response = await client.messages.create(
model=MODEL,
max_tokens=300,
messages=[{"role": "user", "content": f"Summarise this ticket in two sentences:\n\n{ticket}"}],
)
return "".join(b.text for b in response.content if b.type == "text")
async def main(tickets: list[str]) -> list[str]:
return await asyncio.gather(*(summarise(t) for t in tickets)) # keeps input order
summaries = asyncio.run(main(load_tickets()))
The semaphore keeps you under your rate limits; the SDK also retries a 429 automatically. Pass return_exceptions=True to gather if one failed ticket should not cancel the rest.
| Situation | Choose | Why |
|---|---|---|
| Web service with many users at once | AsyncAnthropic with await | Does not block the event loop |
| Hundreds of independent calls needed now | Async calls with a semaphore | Parallel, but within rate limits |
| A large job that can wait hours | Message Batches API | Lower cost; see Claude API Mechanics |
| A one-off script | Sync Anthropic client | Simpler, nothing else is waiting |
Version control for prompts and code
Treat the system prompt, tool schemas, model ID and eval cases as source code: store them in git and review changes in pull requests. In Claude Code, ask Claude to commit with a descriptive message or to "create a pr". Run claude --worktree <name> to start a session in its own git worktree, so two sessions can edit the same repository without colliding (the repository needs at least one commit). Claude Code checkpoints let you rewind its file edits, but they do not track changes made through Bash and are not a replacement for git.
Claude Code in the delivery pipeline
claude -p "<prompt>" runs Claude Code without the interactive interface and exits non-zero when the run fails. For CI:
--output-format jsonreturns the text in aresultfield with the session ID and cost estimate; add--json-schemato get astructured_outputfield.--allowedToolspre-approves tools using permission rule syntax, for example"Bash(git diff *)".--bareskips local hooks, skills, plugins, MCP servers and CLAUDE.md files, so every machine gets the same result. Without it,claude -ploads what an interactive session would, including the hooks in a project's.claude/settings.jsonand the servers in its.mcp.json, with no trust prompt. That matters when the pipeline checks out code from an untrusted pull request.
For GitHub, the Claude Code GitHub Action (anthropics/claude-code-action@v1) responds to @claude mentions or runs a fixed prompt on any workflow event. Run /install-github-app in Claude Code to set it up, and keep the API key in a repository secret such as ANTHROPIC_API_KEY, never in the workflow file.
Testing a Claude feature at four levels
Claude's output varies from run to run, so tests assert properties that must hold (the JSON parses, a field exists, a value sits in range), not exact wording. Each level catches a failure the others miss:
| Level | What it checks | What it cannot see |
|---|---|---|
| Unit | A single function, for example a parser or a tool wrapper | How the pieces fit together |
| Functional | A single Claude call gives back the expected shape for a given input | The code around that call |
| Integration | A handoff between two components, such as retrieval feeding the prompt | Behaviour that only appears end to end |
| End to end | The whole flow as a user runs it | Which step broke |
Most silent failures sit at the integration level. A typical case: retrieval hands back a list of chunk objects, the prompt builder expects a plain string, and the model receives malformed context and answers from memory. The parser's unit test and the model call's functional test both pass. Only a test that runs retrieval and prompt building together on real data catches it. Log a trace of each step (prompt, tool calls, intermediate outputs, timing) so a failed end-to-end case points at the step that broke.
Code review with Claude
- Locally.
/code-reviewreviews your branch's commits ahead of its upstream plus uncommitted changes, in a background subagent with its own context.--fixapplies the findings;--commentposts them on the pull request. - On every pull request. The managed Code Review service (Team and Enterprise plans) posts inline comments tagged Important, Nit or Pre-existing. Its check run always completes as neutral, so it never blocks a merge on its own. Put review-only rules in
REVIEW.mdat the repository root. - Writer and reviewer. A second session reviews better than the one that wrote the code, because it is not biased toward its own work.
Checking work Claude did unattended
Verify in proportion to how little you watched. For a run nobody supervised, such as a CI job or a long autonomous session:
- Read the diff, not the summary. A tidy summary can hide an edit to a file outside the plan. Run
/code-review, then readgit diffyourself. - Make the tests a gate. A
Stophook that runs the tests and exits with code 2 stops Claude from ending the turn while they fail, and its error output goes back to Claude. APostToolUsehook can lint after each edit; exit code 2 there shows the errors to Claude, though the edit has already happened. - Check headless runs by exit code and JSON result.
claude -pexits non-zero when the run fails. - Get a cold second opinion. A fresh session reviews the change without the reasoning that produced it.
Making a Claude build reusable
A build that works once is not yet an asset the next team can use. Before it goes into a shared repository:
- Parameterise what changes per customer: repository paths, model ID, thresholds, prompt fragments and credential references, each with a documented default.
- Document what the code cannot show: environment assumptions, expected inputs and the failure modes already handled.
- Ship the eval with it, so the next team can confirm the asset still works in their context.
The same discipline gets a contribution accepted upstream: one focused change, an example someone can run, a test showing the behaviour works, a short statement of assumptions, and confirmation that you have the right to share code written during a customer engagement. A maintainer merges what they can verify without rebuilding your context.
Refactoring, small and large
For a small refactor, state the behaviour that must not change, make sure tests cover it, and run them before and after. When the change touches several files or you are unsure of the approach, start in plan mode (claude --permission-mode plan, or Shift+Tab in a session) so Claude reads and proposes before it edits.
For a migration across hundreds of files, fan out one claude -p call per file. Test the prompt on two or three files first, then run the full list:
for file in $(cat files.txt); do
claude -p "Migrate $file from the v1 logging client to v2. Keep behaviour identical. Run its tests. Return OK or FAIL." \
--allowedTools "Edit,Bash(pytest *)" \
--permission-mode dontAsk
done
dontAsk denies anything not pre-approved, which matters when nobody is watching. Inside a session, /batch <instruction> splits a change across subagents, each in its own worktree.
Rules that decide exam answers
- Cap concurrency. Unbounded parallel calls hit rate limits. Use an async client with a semaphore, or batch if results can wait.
- Prompts are code. Version them, review them in pull requests and test them with evals before release.
- Tests define a safe refactor. Without a check Claude can run, "looks done" is the only signal.
- Pilot a migration before scaling it. Refine the prompt on a few files, then fan out.
- Make CI runs repeatable.
--bare, explicit--allowedToolsand JSON output give the same behaviour on every runner. - Verify unattended work from evidence. Read the diff, gate on tests that run as hooks, and review in a fresh context; a summary is not proof.
Where it appears in the exam
Software Engineering Foundations is 7.4% of the exam, inside Applications and Integration (33.1%), so expect about four of the 53 items. The guide lists REST, JSON, async programming, version control, SDLC integration, code review and refactoring, so questions describe a service, a pipeline or a codebase change and ask for the sound engineering choice.
Two sample questions
These are original Timo practice questions. They are not official exam questions.
Build exercise
- Write a script that makes 50 calls with the sync client, then rewrite it with
AsyncAnthropicand a semaphore. Time both. - Move your system prompt into a file in git, change one line on a branch and run
/code-reviewbefore opening the pull request. - Add a CI step that pipes
git diff mainintoclaude --bare -pwith--output-format jsonand saves theresultfield as a build artefact. - Pick a small refactor, write the tests first, plan it in plan mode, then let Claude implement it and run the tests.
Practise this topic
- Claude Certified Developer practice exam: free, 20 questions, no sign-up
- CCDV-F study guide: all topics
- Previous topic: Claude API Mechanics
- Next topic: Claude Application Design
Sources
- Claude Certified Developer Foundations Exam Guide, version 1.0, effective July 2026 (Anthropic), Domain 2 topic: Software Engineering Foundations
- Anthropic documentation: Python SDK
- Claude Code documentation: Run Claude Code programmatically
- Claude Code documentation: Code Review
- Claude Code documentation: Common workflows
By Amotion AI