Structuring Large Software Projects: Philosophy and Approach

This talk explores structuring large software projects for dependability, extendability, and team scalability, using examples like a video editor, healthcare system, and jet fighter to illustrate a philosophy of creating small, insulated modules for efficient development.

Structuring Large Software Projects: Philosophy and Approach

Large software projects fail not from technical complexity, but from poor architectural decisions made early in development. The key to building dependable, extendable systems lies in creating small, insulated modules that work together through well-defined interfaces.

The Core Philosophy: Everything is a Module

The fundamental principle is simple: every piece of software should be writable by one person. This doesn’t mean one person builds the entire system—it means you break the system into modules small enough for individual ownership.

Think of modules as black boxes. Each module exposes its functionality through APIs or protocols. You interact with the interface without knowing the implementation details. This isolation allows different people to work on different modules simultaneously without coordination overhead.

When someone leaves your team, you can rewrite their module from scratch while keeping the same API. All existing code continues working unchanged.

Building Your Foundation Stack

Start with platform independence. Wrap everything you don’t control—operating systems, graphics libraries, input systems. Even if you use reliable open-source libraries like SDL, wrap them. You don’t control their development direction or release schedule.

Create a simple test application while building your platform layer. This minimal app—opening windows, reading input, drawing basic shapes—becomes invaluable when porting to new platforms. Instead of porting millions of lines of code, you port one small, well-understood application first.

Your drawing layer sits above the platform layer. Keep it simple initially—basic shapes, images, maybe shaders. You can always enhance the implementation later while keeping the same interface.

Text rendering follows the same pattern. Start with bitmap fonts if needed, but design your API for future requirements like Unicode support and complex typography. Other developers can build interfaces using simple text while you perfect the rendering engine behind the scenes.

Choosing Your Primitives

Every system needs a fundamental data type—its primitive. This choice shapes your entire architecture.

For a video editor, the primitive is clips on a timeline with animated parameters. Everything becomes a clip: video files, effects, transitions, titles. This generalization enables powerful compositing while keeping the core system simple.

For a healthcare system, events work better than static records. Birth, doctor visits, prescriptions, appointments—all become timestamped events. This structure supports both historical analysis and future scheduling through the same interface.

For a jet fighter, the primitive is world state—current sensor readings, contact positions, fuel levels, weapon status. The system cares about now, not history.

Choose one primitive and stick with it. Supporting multiple primitives (like polygons and NURBS in 3D software) forces every component to handle all types, multiplying complexity across your entire system.

Building Your Core

Your core module stores and manages your primitives. It enforces business rules—clips can’t have negative timestamps, healthcare events require valid patient IDs, sensor data includes confidence levels.

The core provides undo/redo automatically by tracking changes between states. UI components request changes; the core handles the mechanics of state management.

Most importantly, the core knows nothing about your specific domain beyond the primitives. A video editor core doesn’t understand MP4 files or color correction—those are plugins.

Plugin Architecture

Build a plugin system where external code registers capabilities. An MP4 plugin says “I can decode this file format and provide these parameters.” A color correction plugin says “I can process video with these adjustable settings.”

Plugins describe themselves through metadata—input types, output types, parameter lists, UI hints. Your core and UI can work with any plugin that follows the interface, even plugins that don’t exist yet.

This approach scales to any number of contributors. Each plugin developer works in isolation, following documented interfaces. They don’t need to understand your core implementation or coordinate with other plugin authors.

The Power of Tooling

Once you have a core with a stable API, build extensive tooling around it. For the jet fighter example: data recorders, playback systems, simulators, loggers, visualizers. These tools never ship with the final product, but they’re essential for development.

Contractors building missiles can test against recorded flight data. Radar developers can simulate various scenarios. Display manufacturers can visualize data streams. Everyone works with real interfaces and realistic data before hardware integration begins.

Format Design: The Hidden Skill

Most software development is format design—creating APIs, file formats, network protocols, even programming languages. These are all formats for storing and transmitting information.

Keep formats simple and implementable. Complex formats lead to incomplete implementations and compatibility problems. It’s better to choose one approach and implement it well than to support multiple approaches poorly.

Separate semantics from structure. JSON provides structure but no meaning. The metric system provides meaning but no storage format. Design your formats to be either semantic standards or structural standards, rarely both.

Managing Complexity

Avoid feature creep in your interfaces. When ten people each want a feature, you get ten features that everyone must implement. Sometimes it’s better to choose one approach that makes some people unhappy than to support multiple approaches that make everyone’s job harder.

Think about interactions carefully. How many timelines does your video editor support? How many world views can your fighter jet maintain? These decisions compound throughout your system.

Provide implementation freedom. Your API should specify what happens, not how it happens. Don’t expose SQL queries if you might switch databases. Don’t expose file paths if you might move to cloud storage.

The Long View

Large software projects live for decades. Video editors from the 1980s still run today. Fighter jets serve for 50 years. Healthcare systems outlast the governments that build them.

Design for this longevity. Use stable languages and avoid dependencies on trendy platforms. Write code that won’t need fixing in five years. Plan for requirements you can’t imagine today.

The modular approach scales with time. As new requirements emerge, you add new modules. As old modules become obsolete, you replace them without touching the rest of the system. Your investment in good interfaces pays dividends for decades.

Start Small, Think Big

Begin with the simplest possible implementation that follows your target interface. Ship working software quickly, then improve the implementation behind stable APIs. Your users get immediate value while you perfect the system incrementally.

This philosophy works whether you’re building video editors, healthcare systems, or fighter jets. The scale changes, but the principles remain: small modules, clear interfaces, stable primitives, and relentless focus on simplicity.