DEV Community

Henrique Sagara
Henrique Sagara

Posted on

Setting Up Tools for Code Quality

When developing ReadmeGenie, I aimed to ensure consistent code quality with an automated setup for linting and formatting. After considering several tools, I selected Ruff as the linter and Black as the code formatter. Although Ruff can also handle both linting and formatting, I decided to set up Black as a separate formatter to gain experience with the configuration of both tools. Below, I’ll share why I chose these tools, how I configured them for my project, the challenges I faced, and the lessons I learned along the way.


1. Tool Selection

Why Ruff?

Ruff is a fast linter for Python that supports various linting rules from other linters (like Flake8 and Pyflakes) and offers significant performance improvements. It’s highly customizable, which allowed me to specify a mix of rules while ensuring compatibility with Black for formatting. Ruff’s design for speed and extensibility is ideal for projects that prioritize efficiency without sacrificing quality.

Why Black?

Black is a Python formatter that strictly enforces one formatting style, helping reduce discussions and inconsistencies over code styling. While Ruff offers basic formatting capabilities, Black’s dedicated approach provides a few advantages:

  • Consistency: Black enforces a strict, standard style that minimizes debates over code formatting.
  • Broad Adoption: Black is widely used, making it easier to integrate into most development workflows, especially in collaborative projects.

  • Black Documentation: https://github.com/psf/black

2. Project Setup

To ensure that Ruff and Black worked seamlessly in ReadmeGenie, I configured them in both pyproject.toml and
.pre-commit-config.yaml, allowing developers to automatically format and lint code when making commits.

Configuration for Ruff and Black in pyproject.toml

This setup ensures Ruff is used solely for linting and Black for formatting:

# pyproject.toml
# Set up black as formatter
[tool.black]
line-length = 88
target-version = ["py311"]

# Set up ruff as linter only
[tool.ruff]
# Exclude directories that don’t need linting (e.g., virtual environments)
exclude = [
    "venv/",
    "__pycache__/"
]
fix = true


# Enable specific linting rules
select = ["F", "E", "W", "B", "I", "S"]  # Example codes: F=flake8, E=errors, W=warnings, B=bugbear, I=import, S=safety
# Exclude Black-compatible rules to avoid conflicts with Black's formatting.
ignore = ["E501", "E203", "E231"]  # Exclude Black-incompatible style issues
Enter fullscreen mode Exit fullscreen mode
  • ignore: Black handles specific styling, so we excluded these rules in Ruff.
  • fix: Enables Ruff to fix issues where possible, leaving formatting to Black.

Adding Pre-commit Hook for Ruff and Black

Using pre-commit hooks, I configured .pre-commit-config.yaml to enforce linting and formatting on every commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.1.0
    hooks:
      - id: black
  - repo: https://github.com/charliermarsh/ruff-pre-commit
    rev: v0.7.1
    hooks:
      - id: ruff

Enter fullscreen mode Exit fullscreen mode

3. Running Ruff and Black from the Command Line

With the above setup, you can use the following commands:

  • Run Ruff:
  ruff check . --fix
Enter fullscreen mode Exit fullscreen mode
  • Run Black:
  black .
Enter fullscreen mode Exit fullscreen mode

These commands apply fixes to all Python files, ensuring consistent styling and quality checks.

4. VS Code Integration

To automate Ruff and Black on save, I added the following configuration in .vscode/settings.json:

{
  // Enable Black as the default formatter
  "python.formatting.provider": "black",
  "editor.formatOnSave": true,

  // Set up Ruff as the linter
  "python.linting.enabled": true,
  "python.linting.ruffEnabled": true,

  // Ensure only Ruff is used as the linter
  "python.linting.flake8Enabled": false,
  "python.linting.pylintEnabled": false,

  // Format and lint on save
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.organizeImports": true
  }
}

Enter fullscreen mode Exit fullscreen mode

This setup makes Black the default formatter and Ruff the only active linter in VS Code, allowing both to run
automatically upon saving.

5. Findings and Fixes

Once configured, Ruff and Black identified several issues:

  • Line Length (E501): Ruff initially flagged long lines, which Black auto-formatted.
  • Unused Imports and Variables: Ruff caught several unused imports and variables.
  • Indentation and Styling Consistency: Black applied consistent spacing and indentation, enhancing readability.

6. Challenges

One notable challenge was understanding that some styles are incompatible between Ruff and Black. For example:

  • Line Length (E501): Ruff initially flagged long lines exceeding 88 characters, which Black handles by wrapping lines. To prevent conflicts, I added E501 to Ruff’s ignore list. Despite this, Ruff sometimes flagged E501 errors if Black didn’t apply the expected breakpoints. These discrepancies underscored the importance of adjusting each tool’s configuration and understanding where they may overlap.

7. Lessons Learned

Using Ruff and Black together has been a great way to improve code quality. Here’s what I learned:

  • Consistency: Black’s opinionated style reduces ambiguity in code styling.
  • Automation: Pre-commit hooks save time and ensure consistent formatting.
  • Editor Integration: Configuring Ruff and Black to run on save within VS Code streamlined development.
  • Compatibility: Learning how to resolve conflicts like E501 taught me about tool configurations and helped fine-tune project workflows.

Top comments (0)