Module 37: Best Practices
Introduction
Writing code that works is only the first step. Professional software should also be:
- Readable
- Maintainable
- Efficient
- Reusable
- Testable
- Scalable
Following best practices helps teams collaborate, reduces bugs, and makes applications easier to extend over time.
This module covers the essential practices followed by professional C++ developers.
Learning Objectives
After completing this module, you will understand:
- Naming Conventions
- Code Formatting
- Documentation
- Error Handling
- Performance Optimization
- Clean Code Principles
- SOLID Principles
Software Development Workflow
1Requirements 2 │ 3 ▼ 4Design 5 │ 6 ▼ 7Write Clean Code 8 │ 9 ▼ 10Testing 11 │ 12 ▼ 13Code Review 14 │ 15 ▼ 16Deployment 17 │ 18 ▼ 19Maintenance
1. Naming Convention
Good names make code self-explanatory.
Bad Example
1int a; 2int b; 3int c; 4 5c = a + b;
Nobody knows what the variables represent.
Good Example
1int firstNumber = 10; 2int secondNumber = 20; 3 4int totalSum = firstNumber + secondNumber; 5 6std::cout << totalSum;
Class Naming
Use PascalCase.
1class StudentManager 2{ 3};
Function Naming
Use camelCase.
1void calculateSalary() 2{ 3}
Variable Naming
1int totalMarks; 2double accountBalance;
Constant Naming
1constexpr double PI = 3.141592653589793; 2constexpr int MAX_USERS = 100;
Boolean Naming
1bool isLoggedIn; 2bool hasPermission; 3bool canEdit;
Naming Convention Summary
| Item | Convention |
|---|---|
| Class | PascalCase |
| Function | camelCase |
| Variable | camelCase |
| Constant | UPPER_CASE or constexpr variables |
| Namespace | lowercase |
| Enum | PascalCase |
2. Code Formatting
Consistent formatting improves readability.
Poor Formatting
1#include<iostream> 2using namespace std; 3int main(){int x=10;cout<<x;}
Good Formatting
1#include <iostream> 2 3int main() 4{ 5 int number = 10; 6 7 std::cout << number << std::endl; 8 9 return 0; 10}
Indentation
1if (score >= 50) 2{ 3 std::cout << "Pass"; 4} 5else 6{ 7 std::cout << "Fail"; 8}
Use 4 spaces consistently (or follow your project's agreed style). Avoid mixing tabs and spaces.
3. Documentation
Good documentation explains why, not just what.
Function Documentation
1/** 2 * Calculates the area of a rectangle. 3 * 4 * @param length Rectangle length. 5 * @param width Rectangle width. 6 * @return Area of the rectangle. 7 */ 8double calculateArea(double length, double width) 9{ 10 return length * width; 11}
Class Documentation
1/** 2 * Represents a bank account. 3 */ 4class BankAccount 5{ 6};
Inline Comments
1// Read configuration before starting the server. 2loadConfiguration();
Avoid obvious comments like:
1// Increment i 2i++;
4. Error Handling
Programs should handle unexpected situations gracefully.
Using Exceptions
1#include <iostream> 2#include <stdexcept> 3 4double divide(double a, double b) 5{ 6 if (b == 0) 7 throw std::invalid_argument("Division by zero"); 8 9 return a / b; 10} 11 12int main() 13{ 14 try 15 { 16 std::cout << divide(10, 2); 17 } 18 catch (const std::exception& e) 19 { 20 std::cout << e.what(); 21 } 22}
Input Validation
1#include <iostream> 2 3int main() 4{ 5 int age; 6 7 std::cin >> age; 8 9 if (age < 0) 10 { 11 std::cout << "Invalid age"; 12 return 1; 13 } 14 15 std::cout << "Valid age"; 16}
File Error Handling
1#include <fstream> 2#include <iostream> 3 4int main() 5{ 6 std::ifstream file("data.txt"); 7 8 if (!file) 9 { 10 std::cerr << "Unable to open file\n"; 11 return 1; 12 } 13 14 std::cout << "File opened successfully"; 15}
5. Performance Optimization
Optimisation should come after writing correct code.
Pass by Reference
Bad
1void print(std::string text) 2{ 3}
Good
1void print(const std::string& text) 2{ 3}
Avoid unnecessary copies.
Use emplace_back
1#include <vector> 2#include <string> 3 4int main() 5{ 6 std::vector<std::string> names; 7 8 names.emplace_back("Alice"); 9 names.emplace_back("Bob"); 10}
Range-Based Loop
1#include <iostream> 2#include <vector> 3 4int main() 5{ 6 std::vector<int> values = {1,2,3,4}; 7 8 for (int value : values) 9 { 10 std::cout << value << " "; 11 } 12}
Reserve Vector Capacity
1#include <vector> 2 3int main() 4{ 5 std::vector<int> values; 6 7 values.reserve(1000); 8}
Use constexpr
1constexpr int square(int x) 2{ 3 return x * x; 4}
6. Clean Code
Clean code is easy to understand and maintain.
Small Functions
Bad
1void processEverything() 2{ 3 // Hundreds of lines 4}
Good
1void readData() 2{ 3} 4 5void processData() 6{ 7} 8 9void saveData() 10{ 11}
Meaningful Function Names
Bad
1void doWork() 2{ 3}
Good
1void calculateMonthlySalary() 2{ 3}
Avoid Magic Numbers
Bad
1salary = salary * 1.18;
Good
1constexpr double TAX_RATE = 1.18; 2 3salary *= TAX_RATE;
DRY (Don't Repeat Yourself)
Bad
1std::cout << "Welcome\n"; 2std::cout << "Welcome\n"; 3std::cout << "Welcome\n";
Good
1for (int i = 0; i < 3; ++i) 2{ 3 std::cout << "Welcome\n"; 4}
7. SOLID Principles
SOLID is a set of five object-oriented design principles.
S — Single Responsibility Principle (SRP)
A class should have one responsibility.
Bad
1class Employee 2{ 3public: 4 void calculateSalary() {} 5 void saveToDatabase() {} 6 void sendEmail() {} 7};
Good
1class SalaryCalculator 2{ 3public: 4 void calculateSalary() {} 5}; 6 7class EmployeeRepository 8{ 9public: 10 void save() {} 11}; 12 13class EmailService 14{ 15public: 16 void sendEmail() {} 17};
O — Open/Closed Principle (OCP)
Open for extension, closed for modification.
1class Shape 2{ 3public: 4 virtual double area() const = 0; 5 virtual ~Shape() = default; 6}; 7 8class Circle : public Shape 9{ 10public: 11 Circle(double radius) : radius(radius) {} 12 13 double area() const override 14 { 15 return 3.141592653589793 * radius * radius; 16 } 17 18private: 19 double radius; 20};
L — Liskov Substitution Principle (LSP)
Derived classes should be usable wherever the base class is expected.
1class Bird 2{ 3public: 4 virtual void move() = 0; 5 virtual ~Bird() = default; 6}; 7 8class Sparrow : public Bird 9{ 10public: 11 void move() override 12 { 13 std::cout << "Flying"; 14 } 15};
I — Interface Segregation Principle (ISP)
Prefer small, focused interfaces.
1class Printable 2{ 3public: 4 virtual void print() = 0; 5 virtual ~Printable() = default; 6}; 7 8class Scannable 9{ 10public: 11 virtual void scan() = 0; 12 virtual ~Scannable() = default; 13};
D — Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete implementations.
1class Logger 2{ 3public: 4 virtual void log(const std::string& message) = 0; 5 virtual ~Logger() = default; 6}; 7 8class ConsoleLogger : public Logger 9{ 10public: 11 void log(const std::string& message) override 12 { 13 std::cout << message << std::endl; 14 } 15}; 16 17class Application 18{ 19public: 20 Application(Logger& logger) 21 : logger(logger) 22 { 23 } 24 25 void start() 26 { 27 logger.log("Application started"); 28 } 29 30private: 31 Logger& logger; 32};
Modern C++ Best Practices
- Prefer
std::unique_ptrover raw pointers. - Use
std::vectorinstead of dynamic arrays. - Use
constexprwhere possible. - Prefer
enum classover traditional enums. - Use
nullptrinstead ofNULL. - Use range-based
forloops. - Minimise global variables.
- Prefer RAII for resource management.
- Avoid unnecessary copies by using references and move semantics.
Common Mistakes
Long Functions
Split large functions into smaller reusable functions.
Deep Nesting
Bad
1if (a) 2{ 3 if (b) 4 { 5 if (c) 6 { 7 } 8 } 9}
Better
1if (!a || !b || !c) 2{ 3 return; 4} 5 6// Main logic
Global Variables
Avoid
1int counter = 0;
Prefer encapsulation within classes or functions.
Ignoring Compiler Warnings
Compile with warnings enabled.
1g++ -Wall -Wextra -Wpedantic main.cpp
Best Practices Checklist
| Practice | Recommendation |
|---|---|
| Naming | Use meaningful names |
| Formatting | Consistent indentation and spacing |
| Documentation | Document public APIs and complex logic |
| Error Handling | Validate input and use exceptions appropriately |
| Performance | Profile before optimising |
| Clean Code | Keep functions short and focused |
| SOLID | Design loosely coupled, maintainable classes |
| Memory | Prefer RAII and smart pointers |
| Testing | Write unit tests for critical functionality |
| Build | Use CMake and enable compiler warnings |
Interview Questions
1. What is clean code?
Code that is easy to read, understand, test, and maintain.
2. Why are naming conventions important?
They improve readability, consistency, and collaboration across teams.
3. What is the DRY principle?
"Don't Repeat Yourself"—avoid duplicating code by extracting reusable logic.
4. When should exceptions be used?
For exceptional or unexpected situations that cannot be handled through normal program flow.
5. What is RAII?
Resource Acquisition Is Initialization—a C++ idiom where resources are tied to object lifetimes, ensuring automatic cleanup.
6. What are the SOLID principles?
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
7. Why should you profile before optimising?
Because optimisation should target real bottlenecks rather than assumptions.
8. What is the advantage of const references?
They avoid unnecessary copying while preventing modification of the passed object.
Module Summary
In this module, you learned:
- Professional naming conventions for C++ projects
- Consistent code formatting practices
- Writing useful documentation and comments
- Robust error handling using validation and exceptions
- Performance optimisation techniques for modern C++
- Clean Code principles such as DRY, small functions, and meaningful names
- The five SOLID principles for object-oriented design
- Modern C++ practices including RAII, smart pointers,
constexpr, and range-based loops
These best practices are used throughout the software industry and help produce reliable, maintainable, and scalable C++ applications suitable for production environments, open-source projects, and technical interviews.