Refining C++: 10 Principles for Code Simplicity and Testability from My First Year Working with C++
I recently had my C++ work anniversary! Hooray! Throughout my C++ journey, I've had the privilege of diving deep into C++ development with the "Effective C++", "Exceptional C++", and "Design Patterns" books and learning from very experienced engineers. The experiences l've gained have left me with invaluable insights that have shaped my approach to C+ + programming.
Here, I'd like to share some of the main takeaways that have shaped my approach to software development. These lessons revolve around making code readable, unit-testable, and independent, while also embracing the principles of Test-Driven Design.
1. Consider the Use of Abstract Interfaces
Sometimes instead of exposing concrete class declarations in public headers, it is more beneficial to use abstract interfaces. Imagine you're building a plugin system for a video game. Instead of directly including the concrete class for each plugin, define an interface that all plugins adhere to. This way, you reduce dependencies and allow for easier mocking and implementation changes without affecting the main codebase.
Additionally, they improve compile times and make the code more readable. However, sometimes there could be a small performance downside to using abstract classes - so they are not appropriate for all use cases. Nevertheless in many applications, the benefits of using abstract interfaces clearly outweigh their disadvantages.
2. Smart Pointers for Resource Management
Say goodbye to raw pointers and welcome smart pointers. Imagine you have a resource-intensive application, and you want to ensure proper memory management. By using shared pointers or unique pointers, you automatically handle resource cleanup, reducing the risk of memory leaks. For example:
```cpp
std::shared_ptr<Resource> resource = std::make_shared<Resource>();
```
3. Namespace Your Code
Just as you organize your workspace or toolbox, encapsulate your code within namespaces. Namespaces prevent naming clashes and make your code more organized. For instance, if you're working on a graphics library, you might use:
```cpp
namespace Graphics {
// Your code here
}
```
4. Leverage Structs for Data
When defining accessible data, think of structs with const members as your go-to tool. Imagine you're modeling a 2D point. A struct simplifies it beautifully:
```cpp
struct Point {
? ? const int x;
? ? const int y;
};
```
5. Harness Return Value Optimization (RVO)
Why pass output values as parameters when RVO can optimize your returns? Suppose you're writing a function to calculate the sum of two numbers. Use RVO for an efficient and more readable result:
领英推荐
```cpp
int CalculateSum(int a, int b) {
? ? int sum = a + b;
? ? return sum; // RVO takes care of optimization
}
```
6. Modularize Your Code
Avoid extending complex source files endlessly. Instead, break functionality into separate files. Imagine you're developing a game engine. Keep rendering, physics, and input handling in separate modules to maintain code clarity and ease of maintenance.
7. The Power of Dependency Injection
By having concrete implementations hold all data dependencies as member variables, you embrace the power of dependency injection. Imagine you're building a weather app. Instead of tightly coupling the weather data retrieval to the display component, inject the data source, making it easy to switch between sources without altering the display code. This will also allow you to unit test nearly all your classes.
8. Keep Functions Lean and Readable
Imagine you're writing a function to validate email addresses. Keep it simple and focused on a single task. For example:
```cpp
bool IsValidEmail(const std::string& email) {
? ? // Validation logic here
? ? return isValid;
}
```
9. Avoid Using Tuples
Tuples can make code less readable. Consider using structured data types instead. For instance, instead of returning a tuple of (name, age), use a struct:
```cpp
struct Person {
? ? std::string name;
? ? int age;
};
```
10. Think About Unit Testing Upfront
Before you start coding, think about how you'll test your code. Imagine you're developing a sorting algorithm. Plan your unit tests in advance to cover various scenarios, ensuring your code is robust from the beginning.
Working as a junior engineer in a big company has exposed me to these critical software development principles. They've not only improved the quality of my code but also enhanced my ability to collaborate effectively within a team.
Remember, the journey of an engineer is about continuous learning. Embrace these principles, adapt to new technologies, and never stop seeking opportunities to grow as a developer. #TechJourney #SoftwareEngineering
Delivery Professional
1 年Kicking goals, challenging yourself and helping others!! Amazing work Jamila Sabazova
Division Director at Macquarie Group
1 年Nice post, Jamila! I vote for Principle #10! When you think carefully upfront about how you are going to write unit tests, this should trigger you to apply many of your other principles (such as dependency injection).
Careers Educator at the University of Adelaide/ Workshop Facilitation/ Professional Development / Careers Counselling/ L&D
1 年Amazing Jamila !