Enterprise Development
What makes code enterprise-grade
Enterprise code is written once but read, changed, and operated for years by many people. Three qualities dominate every design decision: reliability (a defect must not lose data or money), observability (when something fails, you must be able to see why), and maintainability (the next engineer must change it safely). Everything below serves one of those three goals.
Reliability and correctness
Correctness comes from invariants enforced by the compiler, not from hoping. Prefer a compile-time
guarantee to a runtime check, and a runtime check to nothing. const, strong types, and
static_assert cost nothing at run time yet eliminate whole classes of bug.
#include <cstdint>
#include <type_traits>
// A strong type stops you from mixing up two "int" arguments by accident.
struct UserId { std::int64_t value; };
void transfer(UserId from, UserId to, double amount); // cannot pass the amount as an id
// Encode an assumption the compiler can check for you.
template <typename T>
void fixedWidthBuffer() {
static_assert(sizeof(T) >= 8, "this buffer requires a 64-bit element");
}
Resource management (RAII)
The cornerstone of safe C++ is RAII: acquire a resource in a constructor, release it in the destructor, and never expose a raw resource. Files, sockets, locks, and buffers all become objects that clean themselves up — even when an exception unwinds the stack.
#include <cstdio>
#include <stdexcept>
class FileGuard { // owns a FILE*; cannot be leaked or forgotten
public:
explicit FileGuard(const char *path, const char *mode)
: file_(std::fopen(path, mode)) {
if (!file_) throw std::runtime_error("cannot open file");
}
~FileGuard() { if (file_) std::fclose(file_); }
// A file owns a unique resource: copying would double-close. Forbid it.
FileGuard(const FileGuard &) = delete;
FileGuard &operator=(const FileGuard &) = delete;
std::FILE *get() const { return file_; }
private:
std::FILE *file_;
};
void writeReport() {
FileGuard out("report.txt", "w"); // opened here
std::fputs("ok\n", out.get());
// even if an exception is thrown below, ~FileGuard closes the file
}
Smart pointers (unique_ptr, shared_ptr) are RAII for memory. The same
idea generalises to every resource, which is why "rule of zero" classes need no destructor at all —
their members already manage themselves.
Error handling strategy
There is no single "correct" way to report failure — there is a consistent way. Pick one strategy per boundary of your system and apply it uniformly, converting at the edges. Mixing exceptions and error codes at random is what makes error handling unreadable.
| Mechanism | Best for | Cost / caution |
|---|---|---|
| Exceptions | rare, truly exceptional failures a caller cannot ignore | unwinding cost; keep them out of hot loops and destructors |
std::optional<T> | "value or nothing" where absence is normal | carries no error reason |
std::error_code | library-level, non-throwing error reporting | easy to ignore if the caller does not check |
std::expected<T,E> (C++23) | "value or a typed error" without exceptions | newer standard; check availability |
Regardless of mechanism, guarantee the basic exception-safety guarantee: if a function
throws, the program is still valid and no resource leaks — which RAII gives you for free. Mark the
functions that never throw noexcept; the compiler optimises around that promise and
will terminate if you break it.
#include <optional>
#include <string>
// Absence is a normal outcome here, so return optional — no exception, no sentinel.
std::optional<int> findAge(const std::string &name) {
if (name == "Ada") return 36;
return std::nullopt; // "no age known"
}
void use() {
if (auto age = findAge("Ada"))
std::cout << "age = " << *age << "\n";
else
std::cout << "unknown\n";
}
Logging & observability
A running service must explain itself. Structured logs with severity levels let operators filter
noise from signal, and an RAII timing guard records how long each unit of work took — automatically,
even on early returns. The demo collection includes a
working logger with a ScopedTiming guard.
enum class Level { Debug, Info, Warning, Error };
class Logger {
public:
explicit Logger(Level minLevel) : minLevel_(minLevel) {}
void log(Level level, const std::string &message) const {
if (level < minLevel_) return; // filter below the threshold
std::cout << "[" << toTag(level) << "] " << message << "\n";
}
private:
Level minLevel_;
};
Good logs are event-oriented ("order 1024 committed"), not narration ("now we do X"). Never log secrets, and prefer a logging interface you can inject — so tests can assert on what was logged.
Design patterns
Patterns are a shared vocabulary for common structures, not a checklist to apply everywhere. A few recur constantly in C++ services:
- Observer / Event bus — decouple producers from consumers with callbacks; see demo 14.
- Factory — build objects through one function so the concrete type stays hidden.
- Pimpl — hide implementation behind a pointer to keep the public header stable and rebuilds fast.
- Strategy — swap an algorithm at run time by storing a
std::function.
#include <functional>
// Strategy: the pricing rule is data, injectable and replaceable at run time.
class Checkout {
public:
using Pricing = std::function<double(double)>;
explicit Checkout(Pricing pricing) : pricing_(std::move(pricing)) {}
double total(double subtotal) const { return pricing_(subtotal); }
private:
Pricing pricing_;
};
// Checkout plain([](double s){ return s; });
// Checkout discount([](double s){ return s * 0.9; });
Build & dependencies
If a build is not reproducible, it is not enterprise. Modern C++ uses CMake with a
target-based model: each library is a target with explicit PUBLIC/PRIVATE
usage requirements. Third-party code comes from a package manager (vcpkg or
Conan) with pinned versions, never from a random checkout.
cmake_minimum_required(VERSION 3.20)
project(billing CXX) # C++ project named "billing"
add_library(core src/core.cpp) # a reusable library target
target_compile_features(core PUBLIC cxx_std_17)
target_include_directories(core PUBLIC include)
target_compile_options(core PRIVATE -Wall -Wextra -Wpedantic) # warnings as policy
add_executable(billing_app src/main.cpp)
target_link_libraries(billing_app PRIVATE core) # explicit dependency
Warnings are not optional: -Wall -Wextra -Wpedantic (or MSVC /W4) catch
real defects before they ship. Treat warnings as errors in CI so nobody can merge new ones.
Testing & CI
Automated tests are what let you change code without fear. Start with a framework such as Catch2 or GoogleTest, and run the same suite in continuous integration on every push. Because C++ lets you shoot yourself in the foot, layer dynamic analysers on top.
#define CATCH_CONFIG_MAIN // or GoogleTest's main
#include <catch2/catch.hpp>
std::optional<int> findAge(const std::string &name); // the function under test
TEST_CASE("findAge reports known and unknown names") {
REQUIRE(findAge("Ada").value() == 36);
REQUIRE(!findAge("Nobody").has_value()); // absence is a valid result
}
| Tool | Catches | Typical flag / command |
|---|---|---|
| AddressSanitizer | buffer overflows, use-after-free | -fsanitize=address |
| UndefinedBehaviorSanitizer | integer overflow, bad casts | -fsanitize=undefined |
| ThreadSanitizer | data races | -fsanitize=thread |
| clang-tidy | style and bug-prone patterns | clang-tidy src/*.cpp |
| valgrind | leaks (Linux/macOS) | valgrind ./app |
Run sanitizer builds in CI as a separate, slower job. A sanitizer build that is green is far more convincing than any number of manual tests.
Practice
- Take one class you wrote and make it RAII-safe: no raw
new, no manual cleanup. - Give a function two return styles — one throwing, one returning
std::optional— and argue which boundary each belongs to. - Write a one-file CMake project that builds a library plus an executable and enables
-Wall -Wextra. - Add three
TEST_CASEs for a function you wrote earlier and run them. - Compile a demo with
-fsanitize=address,undefinedand fix anything it reports.