TimoBy Amotion AI

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 listsWhat it means in practice
REST APIsThe Claude API is HTTPS and JSON: POST /v1/messages, headers and status codes. The SDK layer is covered in Technical Fundamentals
JSONRequest and response bodies, tool input schemas, structured outputs
Asynchronous programmingAsyncAnthropic, asyncio, bounded concurrency
Version controlPrompts, tool schemas and model IDs in git; Claude Code commits, pull requests and worktrees
SDLC integrationclaude -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 refactoringPlan 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_use block whose input is a JSON object that should match your input_schema. When streaming, it arrives as partial strings in input_json_delta events; join them before you parse. Add strict: true to a tool to enforce its schema.
  • Structured outputs. Set output_config.format to {"type": "json_schema", "schema": {...}} for a reply in a fixed shape. In Python, client.messages.parse() takes a Pydantic model as output_format and returns parsed_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.

SituationChooseWhy
Web service with many users at onceAsyncAnthropic with awaitDoes not block the event loop
Hundreds of independent calls needed nowAsync calls with a semaphoreParallel, but within rate limits
A large job that can wait hoursMessage Batches APILower cost; see Claude API Mechanics
A one-off scriptSync Anthropic clientSimpler, 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 json returns the text in a result field with the session ID and cost estimate; add --json-schema to get a structured_output field.
  • --allowedTools pre-approves tools using permission rule syntax, for example "Bash(git diff *)".
  • --bare skips local hooks, skills, plugins, MCP servers and CLAUDE.md files, so every machine gets the same result. Without it, claude -p loads what an interactive session would, including the hooks in a project's .claude/settings.json and 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:

LevelWhat it checksWhat it cannot see
UnitA single function, for example a parser or a tool wrapperHow the pieces fit together
FunctionalA single Claude call gives back the expected shape for a given inputThe code around that call
IntegrationA handoff between two components, such as retrieval feeding the promptBehaviour that only appears end to end
End to endThe whole flow as a user runs itWhich 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-review reviews your branch's commits ahead of its upstream plus uncommitted changes, in a background subagent with its own context. --fix applies the findings; --comment posts 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.md at 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 read git diff yourself.
  • Make the tests a gate. A Stop hook 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. A PostToolUse hook 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 -p exits 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 --allowedTools and 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.

Question 1

A team migrated 1,200 Python files to a new logging client by looping claude -p over every file in one run. All files changed, but 200 now fail their tests and the rest use three different patterns. What should the team have done before the full run?

Answer: A. A small pilot shows the prompt's gaps while they are cheap to fix. B fills one context window with thousands of file reads. C removes the limits on an unattended run. D removes the check that tells Claude a file is done.

Question 2

A Python web service built on an async framework calls Claude from its request handlers using the synchronous Anthropic client. Under load the whole service stalls, though Claude's response times are normal. Which change fixes the cause?

Answer: D. A sync call blocks the event loop, so every other request waits; the async client frees it. A does not change blocking. B makes chat replies arrive hours later. C retries calls that are not failing.

Build exercise

  1. Write a script that makes 50 calls with the sync client, then rewrite it with AsyncAnthropic and a semaphore. Time both.
  2. Move your system prompt into a file in git, change one line on a branch and run /code-review before opening the pull request.
  3. Add a CI step that pipes git diff main into claude --bare -p with --output-format json and saves the result field as a build artefact.
  4. 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

Sources