Skip to main content
D, despite being a powerful and versatile language, has several common anti-patterns that can lead to performance issues, maintainability problems, and bugs. Here are the most important anti-patterns to avoid when writing D code.
Avoid excessive use of runtime type information (RTTI) with cast operations. This approach is slow, error-prone, and leads to brittle code. Instead, use polymorphism, interfaces, or templates to handle different types in a more type-safe and efficient manner.
Use D’s safety attributes (@safe, @trusted, and @system) to clearly indicate the safety level of your code. Mark unsafe code as @system, wrap it with carefully reviewed @trusted functions, and aim to make most of your codebase @safe. This helps prevent memory safety issues and makes your code more robust.
Avoid excessive use of shared mutable state. Shared mutable state makes code harder to reason about and can lead to race conditions in concurrent programs. Instead, prefer immutability, message passing, or proper synchronization mechanisms when shared state is necessary.
Embrace Uniform Function Call Syntax (UFCS), which allows free functions to be called using method syntax. This makes your code more readable, especially when chaining operations, and helps create a more consistent API style.
Use D’s powerful range-based algorithms from std.algorithm and related modules instead of manual iteration and transformation. Range-based algorithms are more expressive, less error-prone, and often more efficient than manual loops.
Be mindful of excessive use of dynamic arrays, which allocate on the garbage-collected heap. For small, fixed-size arrays, use stack arrays. For large arrays or performance-critical code, consider using custom allocators from std.experimental.allocator to have more control over memory management.
Use D’s contract programming features (in and out blocks) to specify preconditions and postconditions for functions. Contracts make your code more robust by clearly documenting and enforcing expectations about inputs and outputs.
Implement proper error handling in your code. D uses exceptions for error handling, but you should consider using return values or std.typecons types like Nullable or Tuple to represent success/failure states when appropriate, especially for operations that might reasonably fail.
Use templates effectively to create generic code that works with multiple types. Add constraints using static if and template constraints to ensure type safety and provide clear error messages when templates are misused.
Implement proper memory management, especially when using manual memory allocation. Use destructors, the scope statement, or RAII (Resource Acquisition Is Initialization) patterns to ensure resources are properly cleaned up.
Use immutability (const, immutable) to make your code more robust and easier to reason about. Mark function parameters as const when they shouldn’t be modified, and use immutable for data that should never change.
Organize your code into proper modules instead of putting everything in one file. This improves maintainability, compilation times, and allows for better encapsulation of implementation details.
Use proper documentation comments for your code. D supports DDoc, a documentation generator that processes specially formatted comments to create documentation. Include information about parameters, return values, exceptions, and examples to make your code more accessible to others.
Include unit tests in your code using D’s built-in unittest blocks. Unit tests help ensure your code works correctly and continues to work as you make changes. They also serve as executable documentation showing how your code is intended to be used.
Use properties (getters and setters) instead of public fields for class and struct members that need validation or might change implementation in the future. Properties allow you to encapsulate the internal representation while providing a simple interface.
Use proper concurrency patterns when writing multithreaded code. Prefer message passing using std.concurrency over shared mutable state. When shared state is necessary, use proper synchronization mechanisms like atomic operations, mutexes, or reader-writer locks.