Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    D

    D is a general-purpose programming language with static typing, systems-level access, and C-like syntax. It combines the performance and safety of compiled languages with the expressive power and convenience of modern languages.

    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.

    // Anti-pattern: Excessive use of RTTI
    void processObject(Object obj) {
        if (auto stringObj = cast(String)obj) {
            // Handle string
            writeln("String: ", stringObj);
        } else if (auto intObj = cast(Integer)obj) {
            // Handle integer
            writeln("Integer: ", intObj.value);
        } else if (auto arrayObj = cast(Array)obj) {
            // Handle array
            writeln("Array with ", arrayObj.length, " elements");
        }
        // And so on for many types...
    }
    
    // Better approach: Use polymorphism or templates
    interface Processable {
        void process();
    }
    
    class String : Processable {
        string value;
        this(string value) { this.value = value; }
        void process() { writeln("String: ", value); }
    }
    
    class Integer : Processable {
        int value;
        this(int value) { this.value = value; }
        void process() { writeln("Integer: ", value); }
    }
    
    void processObject(Processable obj) {
        obj.process();
    }
    
    // Or using templates
    void processObject(T)(T obj) {
        static if (is(T == string)) {
            writeln("String: ", obj);
        } else static if (is(T : int)) {
            writeln("Integer: ", obj);
        } else static if (isArray!T) {
            writeln("Array with ", obj.length, " elements");
        } else {
            static assert(false, "Unsupported type: " ~ T.stringof);
        }
    }

    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.

    // Anti-pattern: Not using safety attributes
    void* dangerousFunction(int* ptr) {
        void* result = ptr;  // Unsafe pointer conversion
        result += 10;       // Unsafe pointer arithmetic
        return result;
    }
    
    // Better approach: Use safety attributes
    @system void* unsafeOperation(int* ptr) {
        void* result = ptr;  // Unsafe pointer conversion
        result += 10;       // Unsafe pointer arithmetic
        return result;
    }
    
    @trusted void* wrappedUnsafeOperation(int* ptr) {
        // Call unsafe code but ensure it's used safely
        return unsafeOperation(ptr);
    }
    
    @safe void safeFunction() {
        // Only calls @safe or @trusted functions
        int[5] arr = [1, 2, 3, 4, 5];
        auto result = wrappedUnsafeOperation(&arr[0]);
        // Use result safely...
    }

    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.

    // Anti-pattern: Excessive shared mutable state
    int globalCounter = 0;
    
    void incrementCounter() {
        globalCounter++;
    }
    
    void processData() {
        // Multiple threads might access globalCounter
        incrementCounter();
        // More processing...
    }
    
    // Better approach: Use immutability and message passing
    immutable int CONSTANT_VALUE = 42;
    
    // Or use thread-local storage
    __gshared int globalCounter = 0;  // Explicitly mark as global shared
    
    // Or use proper synchronization
    import core.sync.mutex : Mutex;
    
    class ThreadSafeCounter {
        private int _value = 0;
        private Mutex _mutex;
        
        this() {
            _mutex = new Mutex();
        }
        
        void increment() {
            synchronized(_mutex) {
                _value++;
            }
        }
        
        int value() {
            synchronized(_mutex) {
                return _value;
            }
        }
    }

    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.

    // Anti-pattern: Not using UFCS
    string convertToUpper(string input) {
        return toUpper(input);  // Traditional function call
    }
    
    // Better approach: Embrace UFCS
    string convertToUpper(string input) {
        return input.toUpper();  // Using UFCS
    }
    
    // This allows for chaining operations
    string processString(string input) {
        return input
            .strip()
            .toUpper()
            .replace("OLD", "NEW");
    }

    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.

    // Anti-pattern: Manual iteration and transformation
    int[] doubleValues(int[] arr) {
        int[] result = new int[arr.length];
        foreach (i, v; arr) {
            result[i] = v * 2;
        }
        return result;
    }
    
    // Better approach: Use range-based algorithms
    import std.algorithm : map;
    import std.array : array;
    
    int[] doubleValues(int[] arr) {
        return arr.map!(a => a * 2).array;
    }
    
    // Even more complex operations become cleaner
    import std.algorithm : filter, map, sum;
    
    int sumOfEvenDoubles(int[] arr) {
        return arr
            .filter!(a => a % 2 == 0)  // Keep only even numbers
            .map!(a => a * 2)          // Double each value
            .sum;                      // Sum the results
    }

    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.

    // Anti-pattern: Excessive use of dynamic arrays
    void processLargeData() {
        int[] data = new int[1_000_000];  // Allocates on GC heap
        
        // Fill and process data
        foreach (i; 0..data.length) {
            data[i] = i;
        }
        
        // More processing...
    }
    
    // Better approach: Use stack arrays or custom allocators when appropriate
    void processSmallData() {
        int[100] data;  // Stack allocation, no GC
        
        // Fill and process data
        foreach (i; 0..data.length) {
            data[i] = i;
        }
        
        // More processing...
    }
    
    // For larger data, consider custom allocators
    import std.experimental.allocator;
    import std.experimental.allocator.mallocator;
    
    void processLargeDataWithCustomAllocator() {
        // Use Mallocator instead of GC
        auto data = Mallocator.instance.makeArray!int(1_000_000);
        scope(exit) Mallocator.instance.dispose(data);
        
        // Fill and process data
        foreach (i; 0..data.length) {
            data[i] = i;
        }
        
        // More processing...
    }

    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.

    // Anti-pattern: Manual parameter checking
    int divide(int a, int b) {
        if (b == 0) {
            throw new Exception("Division by zero");
        }
        return a / b;
    }
    
    // Better approach: Use contracts
    int divide(int a, int b)
    in {
        assert(b != 0, "Division by zero");
    }
    do {
        return a / b;
    }
    
    // More complex example with pre and post conditions
    int[] sort(int[] arr)
    in {
        assert(arr !is null, "Array cannot be null");
    }
    out (result) {
        // Verify the result is sorted
        for (size_t i = 1; i < result.length; i++) {
            assert(result[i-1] <= result[i], "Array not sorted");
        }
    }
    do {
        import std.algorithm : sort;
        return arr.dup.sort().array;
    }

    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.

    // Anti-pattern: Poor error handling
    void processFile(string filename) {
        auto file = File(filename, "r");  // Might throw if file doesn't exist
        auto content = file.readln();     // Might throw on I/O error
        // Process content...
    }
    
    // Better approach: Use proper error handling
    import std.exception : collectException;
    
    bool processFile(string filename, out string errorMessage) {
        try {
            auto file = File(filename, "r");
            auto content = file.readln();
            // Process content...
            return true;
        } catch (Exception e) {
            errorMessage = e.msg;
            return false;
        }
    }
    
    // Alternative: Use Nullable or std.typecons.Tuple for return values
    import std.typecons : Tuple, tuple;
    import std.typecons : Nullable;
    
    Tuple!(bool, string) processFile(string filename) {
        auto e = collectException!Exception({
            auto file = File(filename, "r");
            auto content = file.readln();
            // Process content...
        }());
        
        if (e) {
            return tuple(false, e.msg);
        }
        return tuple(true, "");
    }

    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.

    // Anti-pattern: Duplicated code for different types
    int findMaxInt(int[] arr) {
        if (arr.length == 0) throw new Exception("Empty array");
        int max = arr[0];
        foreach (v; arr[1..$]) {
            if (v > max) max = v;
        }
        return max;
    }
    
    double findMaxDouble(double[] arr) {
        if (arr.length == 0) throw new Exception("Empty array");
        double max = arr[0];
        foreach (v; arr[1..$]) {
            if (v > max) max = v;
        }
        return max;
    }
    
    // Better approach: Use templates
    T findMax(T)(T[] arr) {
        if (arr.length == 0) throw new Exception("Empty array");
        T max = arr[0];
        foreach (v; arr[1..$]) {
            if (v > max) max = v;
        }
        return max;
    }
    
    // Even better: Use constraints and std.algorithm
    import std.algorithm : max, reduce;
    import std.traits : isOrderingComparable;
    
    T findMax(T)(T[] arr)
    if (isOrderingComparable!T) {
        if (arr.length == 0) throw new Exception("Empty array");
        return arr.reduce!max;
    }

    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.

    // Anti-pattern: Memory leaks with manual memory management
    class Resource {
        private void* _data;
        
        this() {
            _data = malloc(1024);  // Allocate memory
        }
        
        // No destructor to free memory
    }
    
    // Better approach: Implement proper cleanup
    class Resource {
        private void* _data;
        
        this() {
            _data = malloc(1024);  // Allocate memory
        }
        
        ~this() {
            if (_data) {
                free(_data);  // Free memory
                _data = null;
            }
        }
    }
    
    // Even better: Use RAII with scope(exit)
    void processResource() {
        void* data = malloc(1024);
        scope(exit) free(data);  // Will be called when function exits
        
        // Use data...
        // No need to manually call free at each return point
    }

    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.

    // Anti-pattern: Excessive mutability
    struct Point {
        int x;
        int y;
    }
    
    void processPoint(Point p) {
        // p can be modified, even if that's not intended
        p.x += 10;  // Oops, unintended modification
    }
    
    // Better approach: Use immutability
    void processPoint(const Point p) {
        // p cannot be modified
        // p.x += 10;  // Compilation error
    }
    
    // For class references
    void processObject(const Object obj) {
        // obj's fields cannot be modified
    }
    
    // For deeply immutable data
    void processData(immutable int[] data) {
        // data cannot be modified at all
    }

    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.

    // Anti-pattern: Everything in one module
    // myapp.d
    module myapp;
    
    // Hundreds of classes, functions, and variables all in one file
    
    // Better approach: Organize code into modules
    // myapp/package.d
    module myapp;
    
    public import myapp.core;
    public import myapp.utils;
    public import myapp.models;
    
    // myapp/core.d
    module myapp.core;
    
    // Core functionality
    
    // myapp/utils.d
    module myapp.utils;
    
    // Utility functions
    
    // myapp/models.d
    module myapp.models;
    
    // Data models

    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.

    // Anti-pattern: Poor or no documentation
    int calculate(int a, int b) {
        return a * b + 42;
    }
    
    // Better approach: Use proper documentation comments
    /**
     * Calculates a special value based on two inputs.
     *
     * The calculation multiplies the inputs and adds a magic number.
     *
     * Params:
     *     a = First input value
     *     b = Second input value
     *
     * Returns: The calculated result
     *
     * Throws: Nothing
     *
     * Examples:
     * ---
     * assert(calculate(2, 3) == 48);
     * ---
     */
    int calculate(int a, int b) {
        return a * b + 42;
    }

    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.

    // Anti-pattern: No unit tests
    int add(int a, int b) {
        return a + b;
    }
    
    // Better approach: Include unit tests
    int add(int a, int b) {
        return a + b;
    }
    
    unittest {
        assert(add(2, 3) == 5);
        assert(add(-1, 1) == 0);
        assert(add(0, 0) == 0);
        
        // Test edge cases
        import std.math : isClose;
        assert(isClose(add(int.max, 1), int.min));  // Overflow
    }

    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.

    // Anti-pattern: Direct field access
    class Person {
        string name;  // Public field
        int age;      // Public field
    }
    
    // Better approach: Use properties
    class Person {
        private string _name;
        private int _age;
        
        @property string name() const { return _name; }
        @property void name(string value) { _name = value; }
        
        @property int age() const { return _age; }
        @property void age(int value) {
            if (value < 0) throw new Exception("Age cannot be negative");
            _age = value;
        }
    }
    
    // Usage remains simple
    auto person = new Person();
    person.name = "John";  // Calls the setter
    auto name = person.name;  // Calls the getter

    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.

    // Anti-pattern: Unsafe shared data access
    shared int counter = 0;
    
    void incrementCounter() {
        import core.thread : Thread;
        
        auto threads = new Thread[10];
        foreach (i; 0..10) {
            threads[i] = new Thread({
                foreach (j; 0..1000) {
                    counter++;  // Race condition!
                }
            });
            threads[i].start();
        }
        
        foreach (t; threads) {
            t.join();
        }
    }
    
    // Better approach: Use proper synchronization
    import core.atomic : atomicOp;
    
    shared int counter = 0;
    
    void incrementCounter() {
        import core.thread : Thread;
        
        auto threads = new Thread[10];
        foreach (i; 0..10) {
            threads[i] = new Thread({
                foreach (j; 0..1000) {
                    atomicOp!"+="(counter, 1);  // Atomic operation
                }
            });
            threads[i].start();
        }
        
        foreach (t; threads) {
            t.join();
        }
    }
    
    // Even better: Use message passing
    import std.concurrency;
    
    void counterThread() {
        int counter = 0;
        
        while (true) {
            receive(
                (string msg) {
                    if (msg == "increment") {
                        counter++;
                    } else if (msg == "get") {
                        send(ownerTid, counter);
                    } else if (msg == "quit") {
                        break;
                    }
                }
            );
        }
    }
    
    void useCounter() {
        auto tid = spawn(&counterThread);
        
        foreach (i; 0..1000) {
            send(tid, "increment");
        }
        
        send(tid, "get");
        auto count = receiveOnly!int();
        writeln("Counter: ", count);
        
        send(tid, "quit");
    }

    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.

    CrystalDelphi
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.