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

    Crystal

    Crystal is a programming language with Ruby-like syntax and static type checking. It aims to have the elegance and productivity of Ruby combined with the speed, efficiency, and type safety of a compiled language.

    Crystal, despite being a modern language with many safeguards, 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 Crystal code.

    # Anti-pattern: Using generic Object type
    def process(data : Array(Object))
      data.each do |item|
        if item.is_a?(String)
          puts item.upcase
        elsif item.is_a?(Int32)
          puts item + 1
        end
      end
    end
    
    # Better approach: Use union types
    def process(data : Array(String | Int32))
      data.each do |item|
        case item
        when String
          puts item.upcase
        when Int32
          puts item + 1
        end
      end
    end
    
    # Even better: Use generics with constraints
    def process(data : Array(T)) forall T
      data.each do |item|
        puts item
      end
    end

    Leverage Crystal’s type system instead of using generic Object types. Use union types, generics, and type restrictions to make your code more type-safe and efficient. This helps catch errors at compile time rather than runtime.

    # Anti-pattern: Using exceptions for control flow
    def find_user(id)
      begin
        user = User.find(id)
        return user
      rescue
        return nil
      end
    end
    
    # Better approach: Use nil or Result types
    def find_user(id) : User?
      User.find?(id)  # Returns nil if not found
    end
    
    # Alternative: Use a Result type
    record Success(T), value : T
    record Failure, error : String
    
    alias Result(T) = Success(T) | Failure
    
    def find_user(id) : Result(User)
      user = User.find?(id)
      if user
        Success.new(user)
      else
        Failure.new("User not found")
      end
    end

    Avoid using exceptions for normal control flow. Exceptions should be reserved for exceptional conditions. Instead, use nil or Result types to represent the possibility of failure in a type-safe way.

    # Anti-pattern: Unsafe nil handling
    def get_username(user_id)
      user = User.find(user_id)  # Might raise if not found
      return user.username       # Might raise if username is nil
    end
    
    # Better approach: Use safe navigation operator
    def get_username(user_id)
      user = User.find?(user_id)
      return user.try &.username
    end
    
    # Even better: Use explicit nil handling
    def get_username(user_id) : String?
      user = User.find?(user_id)
      return nil unless user
      
      user.username
    end

    Use proper nil handling techniques like the safe navigation operator (&.), nil checks, or the try method. This makes your code more robust and prevents nil-related runtime errors.

    # Anti-pattern: Using mutable global variables
    $config = {"api_key" => "secret"}
    
    def update_config(key, value)
      $config[key] = value
    end
    
    def use_config
      puts $config["api_key"]
    end
    
    # Better approach: Use class variables with controlled access
    class Config
      @@instance = {"api_key" => "secret"}
      
      def self.get(key)
        @@instance[key]?
      end
      
      def self.set(key, value)
        @@instance[key] = value
      end
    end
    
    # Usage
    Config.set("api_key", "new_secret")
    puts Config.get("api_key")

    Avoid using mutable global state. Global variables make code harder to reason about, test, and maintain. Instead, use dependency injection, class variables with controlled access, or dedicated configuration objects.

    # Anti-pattern: Using positional arguments for complex methods
    def create_user(name, email, age, admin, active)
      # Create user with the given parameters
    end
    
    # Usage - unclear what each argument means
    create_user("John Doe", "john@example.com", 30, true, false)
    
    # Better approach: Use named arguments
    def create_user(name : String, email : String, age : Int32, admin : Bool = false, active : Bool = true)
      # Create user with the given parameters
    end
    
    # Usage - much clearer
    create_user(
      name: "John Doe",
      email: "john@example.com",
      age: 30,
      admin: true,
      active: false
    )

    Use named arguments for methods with many parameters or boolean flags. This makes your code more readable and less prone to errors from mixing up argument order.

    # Anti-pattern: Inefficient string concatenation
    def build_report(items)
      report = ""
      items.each do |item|
        report += "Item: #{item.name}, Price: #{item.price}\n"
      end
      report
    end
    
    # Better approach: Use string interpolation or String.build
    def build_report(items)
      String.build do |str|
        items.each do |item|
          str << "Item: #{item.name}, Price: #{item.price}\n"
        end
      end
    end
    
    # Alternative: Use array join
    def build_report(items)
      items.map { |item| "Item: #{item.name}, Price: #{item.price}" }.join("\n")
    end

    Avoid inefficient string concatenation in loops. Instead, use String.build, array joining, or string interpolation for better performance and readability.

    # Anti-pattern: Misusing fibers without synchronization
    def process_data(items)
      results = [] of String
      
      items.each do |item|
        spawn do
          result = process_item(item)
          results << result  # Unsafe concurrent access
        end
      end
      
      Fiber.yield
      results
    end
    
    # Better approach: Use channels for communication
    def process_data(items)
      channel = Channel(String).new
      
      items.each do |item|
        spawn do
          result = process_item(item)
          channel.send(result)
        end
      end
      
      results = [] of String
      items.size.times do
        results << channel.receive
      end
      
      results
    end

    Use Crystal’s concurrency features properly. When using fibers with spawn, ensure proper synchronization for shared resources using channels, mutexes, or other synchronization primitives.

    # Anti-pattern: Ignoring errors
    def save_user(user)
      user.save
      puts "User saved"
    end
    
    # Better approach: Handle errors explicitly
    def save_user(user)
      begin
        user.save
        puts "User saved"
      rescue ex : ValidationError
        puts "Validation error: #{ex.message}"
        false
      rescue ex
        puts "Unexpected error: #{ex.message}"
        false
      else
        true
      end
    end

    Implement proper error handling in your code. Don’t ignore exceptions; catch and handle them appropriately. Use specific rescue clauses for different error types to provide better error messages and recovery strategies.

    # Anti-pattern: Reinventing the wheel
    def parse_json(json_string)
      # Custom JSON parsing logic
    end
    
    # Better approach: Use the standard library
    require "json"
    
    def parse_json(json_string)
      JSON.parse(json_string)
    end

    Leverage Crystal’s rich standard library instead of reinventing the wheel. The standard library provides efficient, well-tested implementations for common tasks like JSON parsing, HTTP requests, file I/O, and more.

    # Anti-pattern: Missing type annotations where helpful
    def process_data(data)
      # What type is data? What does this return?
      data.map { |item| item.to_s.upcase }
    end
    
    # Better approach: Add type annotations for clarity
    def process_data(data : Array(Int32)) : Array(String)
      data.map { |item| item.to_s.upcase }
    end

    Add type annotations to method signatures when it helps with clarity, especially for public APIs. While Crystal can often infer types, explicit annotations make your code more self-documenting and can catch type errors earlier.

    # Anti-pattern: Duplicated code without abstractions
    def process_users
      users = User.all
      users.each do |user|
        # Complex processing logic
      end
    end
    
    def process_orders
      orders = Order.all
      orders.each do |order|
        # Similar complex processing logic
      end
    end
    
    # Better approach: Use abstractions
    module Processable
      abstract def process
    end
    
    class User
      include Processable
      
      def process
        # User-specific processing
      end
    end
    
    class Order
      include Processable
      
      def process
        # Order-specific processing
      end
    end
    
    def process_items(items : Array(Processable))
      items.each &.process
    end

    Use proper abstractions like modules, inheritance, and generics to avoid code duplication. This makes your code more maintainable and extensible.

    # Anti-pattern: Excessive mutability
    class User
      property name : String
      property email : String
      
      def initialize(@name, @email)
      end
      
      def update_email(new_email)
        @email = new_email
      end
    end
    
    # Better approach: Use immutability where appropriate
    class User
      getter name : String
      getter email : String
      
      def initialize(@name, @email)
      end
      
      def with_email(new_email) : User
        User.new(name, new_email)
      end
    end

    Consider using immutability for your data structures when appropriate. Immutable objects are easier to reason about, especially in concurrent contexts. Use methods that return new instances instead of modifying existing ones.

    # Anti-pattern: Using strings for fixed sets of values
    def process_status(status : String)
      case status
      when "pending"
        # Handle pending
      when "processing"
        # Handle processing
      when "completed"
        # Handle completed
      else
        raise "Invalid status: #{status}"
      end
    end
    
    # Better approach: Use enums
    enum Status
      Pending
      Processing
      Completed
    end
    
    def process_status(status : Status)
      case status
      when Status::Pending
        # Handle pending
      when Status::Processing
        # Handle processing
      when Status::Completed
        # Handle completed
      end
    end

    Use enums for fixed sets of related values instead of strings or integers. Enums provide type safety, better documentation, and prevent invalid values.

    # Anti-pattern: Overusing macros for simple cases
    macro define_getter(name, value)
      def {{name.id}}
        {{value}}
      end
    end
    
    define_getter :api_url, "https://api.example.com"
    
    # Better approach: Use simpler constructs when possible
    API_URL = "https://api.example.com"
    
    def api_url
      API_URL
    end
    
    # Appropriate use of macros for complex metaprogramming
    macro define_json_mappings(properties)
      {% for key, type in properties %}
        property {{key.id}} : {{type.id}}
      {% end %}
      
      def self.from_json(json : String)
        data = JSON.parse(json)
        instance = new
        {% for key, type in properties %}
          instance.{{key.id}} = data[{{key.stringify}}].as_s
        {% end %}
        instance
      end
    end

    Use macros judiciously. While Crystal’s macro system is powerful, overusing macros can make code harder to understand and debug. Use simpler constructs like methods, constants, or classes when they suffice, and reserve macros for cases where metaprogramming is truly needed.

    # Anti-pattern: Insufficient testing
    class Calculator
      def add(a, b)
        a + b
      end
    end
    
    # Better approach: Write proper tests
    require "spec"
    
    describe Calculator do
      describe "#add" do
        it "adds two positive numbers" do
          calc = Calculator.new
          calc.add(2, 3).should eq(5)
        end
        
        it "adds a positive and a negative number" do
          calc = Calculator.new
          calc.add(2, -3).should eq(-1)
        end
        
        it "adds two negative numbers" do
          calc = Calculator.new
          calc.add(-2, -3).should eq(-5)
        end
      end
    end

    Write comprehensive tests for your code. Crystal’s spec framework makes it easy to write and run tests. Test different scenarios, edge cases, and error conditions to ensure your code works correctly in all situations.

    # Anti-pattern: Insufficient documentation
    class User
      def initialize(@name, @email)
      end
      
      def valid?
        @email.includes?("@") && @name.size > 0
      end
    end
    
    # Better approach: Add proper documentation
    # A class representing a user in the system.
    class User
      # Creates a new user with the given name and email.
      #
      # ## Parameters
      # * name : The user's full name
      # * email : The user's email address
      def initialize(@name : String, @email : String)
      end
      
      # Checks if the user has valid information.
      #
      # Returns true if the email contains '@' and the name is not empty.
      def valid? : Bool
        @email.includes?("@") && @name.size > 0
      end
    end

    Document your code properly, especially public APIs. Include descriptions of classes, methods, parameters, return values, and any exceptions that might be raised. Good documentation makes your code more accessible to others and to your future self.

    # Anti-pattern: Not closing resources properly
    def read_file(path)
      file = File.open(path)
      content = file.gets_to_end
      # file is never closed
      content
    end
    
    # Better approach: Use blocks for automatic resource management
    def read_file(path)
      File.open(path) do |file|
        file.gets_to_end
      end
    end

    Use proper resource management techniques. When working with resources like files, database connections, or network sockets, use blocks that automatically handle closing the resource, or ensure you close them manually in a begin/ensure block.

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