CV Courseversity

Software Engineering for AI

Covers modular software design, API design, documentation conventions, automated testing, and version control as the engineering discipline that keeps AI research code maintainable and trustworthy.

“Six months after your team ships a promising prototype model, a new hire is asked to add a feature to it — and discovers the code is one 800-line file, has no tests, no docstrings, and a commit history of forty messages that all just say "fix." How much of that six months of research value is actually usable now, and what practices back at the start would have preserved it? This module covers the software engineering habits that keep AI code maintainable past the demo.”

Modular Design and Software Architecture · 15 min

The IEEE Software Engineering Body of Knowledge (SWEBOK) treats software design as its own knowledge area, distinct from requirements and construction, precisely because how a system's pieces are divided and connected determines whether it can be understood, changed, and reused later without ever touching correctness in isolation. A well-modularized design separates a system into components, each responsible for one clear piece of functionality, communicating through defined interfaces rather than by reaching into one another's internals. Consider a concrete worked example: an AI training script that loads data, cleans it, trains a model, and evaluates it, all inline in one function, versus the same logic split into four modules — `load_data()`, `clean_data()`, `train_model()`, and `evaluate_model()` — each independently testable and each replaceable (swap in a different data source, or a different model) without rewriting the rest.

Two properties from classical software design vocabulary make this concrete: cohesion, how closely related the responsibilities within a single module are, and coupling, how much one module depends on another's internal details rather than its public interface. High cohesion within `clean_data()` (it does one thing — cleaning) and low coupling between it and `train_model()` (the latter only needs the cleaned data it receives, not any knowledge of how cleaning happened) together produce a design where a bug in data cleaning cannot silently corrupt the training logic, and where either module can be tested with the small, deterministic unit tests introduced in the programming module, using a handful of known inputs and expected outputs.

This matters more, not less, for AI systems than for typical software, because AI code churns constantly — a new dataset, a new model architecture, a new preprocessing step — and code that started as a one-off research script is routinely pressed into production service. SWEBOK's framing of design as a distinct discipline with its own principles and the ACM/IEEE CS2013 curricular guidelines' emphasis on software design fundamentals as a core competency for computer science graduates both reflect the same underlying reality: a system built as loosely coupled, independently testable modules survives that churn, while an 800-line monolithic script has to be understood in its entirety before any single piece can be changed safely.

APIs and Documentation · 15 min

An application programming interface (API) is the boundary a module exposes to the rest of a system — the functions, classes, or network endpoints other code is allowed to call, deliberately hiding everything else. Google's API design guide, developed for the company's own networked services since 2014 and now public, centers its recommendations on resource-oriented design: organizing an API around named resources (such as `users` or `models`) and applying a small, consistent set of standard methods to them — Get, List, Create, Update, and Delete — rather than inventing a differently shaped, one-off endpoint for every operation. Worked example: a model-serving API exposing `GET /models/{id}` to retrieve one model's metadata and `POST /models` to register a new one is immediately predictable to any developer familiar with the pattern, whereas an endpoint named `/doTheModelThing` is not, no matter how well it works.

Documentation at the code level follows a parallel discipline. PEP 257 defines a docstring as a string literal that is the first statement in a module, function, class, or method, and specifies that a one-line docstring should fit on a single line, keep its opening and closing triple quotes on that same line, and describe the function's effect as an imperative command — "Return the cleaned dataset" rather than "Returns the cleaned dataset" — while a multi-line docstring adds a blank line after the summary before a longer description of arguments, return values, and side effects, with the closing quotes on their own line. Applied to the `clean_data()` module from the previous lesson, a compliant docstring might read `"""Remove readings below zero from a list of sensor values."""` as its summary line — short, imperative, and immediately answering "what does calling this do," which is exactly what a teammate needs before reading the implementation.

Together, a resource-oriented API and PEP 257-style docstrings solve the same underlying problem from two different angles: they let another developer — or a future version of the same developer — use a module correctly without first reading its entire implementation. This is not a cosmetic nicety; SWEBOK's inclusion of software construction and quality practices alongside design reflects that undocumented, ad hoc interfaces are a leading cause of the maintenance cost that eventually makes a codebase too risky to change, which is precisely the failure mode this module's essential question describes.

Version Control and Maintainable AI Systems · 15 min

Version control is, in its simplest definition, a system that records changes to a set of files over time so that specific past versions can be recalled, compared, and restored later; the Git documentation traces its evolution from local systems that tracked patch sets on a single machine, through centralized systems like Subversion where one server holds the only complete history (a single point of failure if that server is lost), to distributed version control systems (DVCSs) like Git, where every clone of a repository is a full mirror including its entire history. This distributed design means that if the central hosting server disappears, any collaborator's local clone can restore the project's complete history — a guarantee no centralized system before it offered.

For AI codebases specifically, version control does more than protect against lost files. Because AI experiments routinely branch — one teammate tries a new preprocessing step, another experiments with a different model architecture — the ability to isolate each line of work in its own branch, test it independently, and merge only what proves out is what keeps a shared codebase from becoming a tangle of commented-out alternatives inside a single file. A commit history with clear, specific messages ("switch data cleaning to drop nulls instead of zero-filling them" rather than a bare "fix") turns the repository itself into a record of why the code looks the way it does — information that plain code, however well-documented at the function level, cannot carry on its own.

Modularity, clear APIs, PEP 257-compliant documentation, and disciplined version control are not four independent best practices to be adopted piecemeal; they compound. A well-modularized `clean_data()` function with a clear docstring is easy to unit-test in isolation (as covered in the programming module); a clean commit history makes it possible to find exactly when and why its behavior changed if a downstream test starts failing; and a resource-oriented API around the whole pipeline means a new team member can start calling it correctly within minutes rather than needing a guided tour through 800 lines of undifferentiated script. SWEBOK's treatment of these as interlocking knowledge areas rather than isolated tips is the professional-practice basis for treating AI research code with the same engineering rigor as any other production software from the start, rather than retrofitting it after the prototype has already become load-bearing.

Practice

Layers of Maintainability

Modular design (cohesion, low coupling) Clear API + PEP 257 docstrings Automated tests (unittest) Version control history (git) Maintainable AI software

Maintainable AI software rests on modular design, with a clear API and documentation, automated tests, and version-control history each building on the layer below.

  • High cohesion inside a module and low coupling between modules let a bug in one piece stay contained instead of silently corrupting another.
  • A resource-oriented API with standard methods (Get, List, Create, Update, Delete) is predictable to a new developer without reading the implementation.
  • A distributed version control system means every clone is a full backup of the project's history, not just the central server.

Recall Practice

ModularityClick to reveal
A single 800-line script loads, cleans, trains, and evaluates in one function. What design change would let a bug in data cleaning be caught without touching the training logic?
Splitting the script into separate, independently testable modules — load_data(), clean_data(), train_model(), evaluate_model() — each with a single clear responsibility, so cohesion is high within each and coupling between them is low.
DocumentationClick to reveal
Per PEP 257, how should the one-line docstring for a function that removes negative readings from a list be phrased?
As an imperative command on a single line with the quotes on that same line, such as """Remove readings below zero from a list of sensor values.""", not a descriptive sentence like "Removes readings...".
Version controlClick to reveal
If the server hosting a team's Git repository is destroyed, why can the project's full history still be recovered?
Because Git is a distributed version control system in which every clone is a full mirror of the repository including its entire history, so any collaborator's local copy can restore it.
API designClick to reveal
Why is an endpoint named GET /models/{id} more usable to an unfamiliar developer than one named /doTheModelThing?
Because it follows resource-oriented design with a standard method (Get) applied to a named resource (models), matching a predictable, consistent pattern rather than an arbitrary one-off name.

Glossary

Modularity
Dividing a system into components, each with one clear responsibility, that communicate through defined interfaces.
Coupling
The degree to which one module depends on another module's internal details rather than its public interface.
API
The set of functions, classes, or endpoints a module or service exposes for other code to call.
Docstring
A string literal, per PEP 257, that documents a module, function, class, or method as its first statement.
Version control
A system that records changes to files over time so specific past versions can be recalled and restored.
Distributed version control system (DVCS)
A version control system, like Git, in which every clone holds a full copy of the project's entire history.
Practical Activity

Review a Module's API and Docstrings

Given a supplied short Python module excerpt (a few function signatures, one non-compliant docstring, and a printed commit-log excerpt with vague messages), learners identify on paper where modularity, PEP 257 docstring conventions, and commit-message clarity fall short of the standards covered in the lessons, and write out corrected versions. This is a virtual, reading-and-annotation exercise; no live repository or code is actually created or executed.

Ready to test yourself?

5 questions on this module.

Start Quiz