Liberándose de los valores predefinidos

Liberándose de los valores codificados: Estrategias más inteligentes para el software moderno

Hardcoded values look like shortcuts. A developer needs a database URL, types it directly into a connection string, and moves on. Three months later, the URL changes, the change is made in one file but missed in four others, and production breaks at 2 AM. A developer needs an API key, types it into the source file, commits, and pushes. Six months later, the repository is cloned, the key appears in a public fork, and the account is compromised before anyone notices. These are not edge cases. They are the standard trajectory of hardcoded values in software systems: harmless-looking shortcuts that compound into maintenance debt, security incidents, and deployment failures.

Find & Eliminate Hardcoded Values

Eliminar y deshacerse de los valores codificados

Aprende más

The fix is not complicated. Environment variables, configuration files, dependency injection, and centralized constants are all well-understood patterns available in every major language and framework. The challenge is applying them systematically across existing codebases, enforcing them for new code, and building the discipline to catch them before they reach production. Every technique required to do that is covered below, with working code examples for Python, Java, and JavaScript.

What Is a Hardcoded Value?

A hardcoded value is a literal constant embedded directly in source code rather than being supplied through configuration, environment variables, a database, or runtime input. The defining characteristic is absence of indirection: if changing the value requires modifying source code, recompiling, or redeploying an application, it is hardcoded.

Ejemplos comunes:

  • A database connection string typed directly into a configuration class
  • An API key or access token in a source file
  • A service URL pointing to a specific environment
  • A numeric threshold (if (amount > 5000)) with no explanation or external source
  • A file path assuming a specific server layout
  • A user role string ("admin") scattered across authorization checks

Hardcoded values appear in every language and every codebase. Legacy mainframe applications encode environment-specific dataset names, region codes, and business thresholds directly in COBOL programs. Modern microservices accumulate hardcoded timeout values, retry counts, and service discovery URLs in application classes. The form changes with the language; the problem is the same.

Hardcoded Values vs Constants: What Is the Difference?

Hardcoded values are often confused with constants, but the distinction is important. A constant represents a stable, intentional, domain-level fact that is correct by definition and unlikely to change: Math.PI, HTTP_STATUS_OK = 200, MAX_RETRY_ATTEMPTS = 3 (if three is genuinely correct for all contexts). Constants are appropriate in code. They add clarity, prevent typos, and make intent explicit.

A hardcoded value encodes an assumption about deployment context, infrastructure, or business rules that is expected to vary. A production database URL, an API key, a region-specific tax rate, a feature flag state: these are hardcoded values masquerading as constants. The test is straightforward: if the value needs to be different in a different environment, customer, or release, it is not a constant and should not be in source code.

What Is Soft Coding?

Soft coding is the practice of making values configurable at runtime rather than fixed at compile time. A soft-coded value can be changed through a configuration file, an environment variable, a database entry, or an administrative interface, without modifying source code or redeploying the application. Soft coding is the correct approach for environment-specific settings, business parameters, feature flags, and any value that might need to differ between development, staging, and production.

The difference between hardcoding and soft coding is the difference between a system that requires a code change and a deployment to update a tax rate, and one that allows an operations team member to update it in a configuration file and restart a service. At scale, that difference represents thousands of engineer-hours and significant operational risk.

Why Hardcoding Is Bad Practice

It Destroys Maintainability

Hardcoded values multiply. A service URL that is hardcoded in one file becomes hardcoded in five files when the module is copied for a new integration. A magic number that appears in one calculation appears in three when the same logic is replicated across slightly different contexts. Each instance is a separate maintenance obligation: find it, update it, test the update, and hope no instances were missed.

The maintainability problem is not just about effort. It is about risk. A search-and-replace across a codebase to update a hardcoded value is a refactoring operation with production impact. Any missed instance is a bug. Any incorrect replacement is a bug. The only way to be confident the change is complete is to have automated tests that would fail if any instance remained, but automated tests are exactly what hardcoded values make difficult to write.

It Blocks Testing and CI/CD

Automated tests need to run in controlled environments. A test that connects to a production database because the connection string is hardcoded is not a test; it is a production operation that happens to run during a build. A CI pipeline that breaks because a hardcoded service URL is inaccessible from the build server is not a test infrastructure problem; it is a hardcoding problem.

Environment variables and configuration injection are what make the same test suite runnable across local development, CI, staging, and production environments with different backing services. Without them, tests become environment-specific, flaky, and eventually abandoned.

It Creates Serious Security Vulnerabilities

Hardcoded credentials are one of the most common and most exploited vulnerability categories in software security. A hardcoded API key, database password, or access token that is committed to version control persists in the repository’s history even after it is deleted from the current branch. Automated scanners search public repositories for these patterns continuously. A key found in a commit from eighteen months ago is still valid if it was never rotated.

OWASP explicitly identifies hardcoded passwords as a critical security flaw (CWE-259, CWE-798). NIST guidelines require that credentials never be stored in source code. Despite this, credential leaks through hardcoded values remain among the most frequent causes of cloud security incidents.

The risk extends beyond credentials. Hardcoded internal service URLs reveal infrastructure topology. Hardcoded user role strings reveal authorization logic. Hardcoded validation thresholds can be circumvented once an attacker knows their exact values. None of these are immediately obvious as security risks, which is why they accumulate without being addressed.

Hardcoded Credentials and API Keys: The Highest-Risk Category

Hardcoded secrets deserve their own treatment because the consequences of getting them wrong are fundamentally different from other hardcoding problems. A hardcoded timeout value causes a bug. A hardcoded API key causes a breach.

What Hardcoded Credentials Look Like

pitón

# Python -- hardcoded database credentials (never do this)
connection = psycopg2.connect(
    host="prod-db.internal.example.com",
    database="accounts",
    user="app_service",
    password="s3cr3tP@ssword!"   # hardcoded secret in source code
)

# Python -- correct approach: load from environment
import os
connection = psycopg2.connect(
    host=os.environ["DB_HOST"],
    database=os.environ["DB_NAME"],
    user=os.environ["DB_USER"],
    password=os.environ["DB_PASSWORD"]
)

Java

// Java -- hardcoded API key (never do this)
private static final String API_KEY = "sk-prod-a1b2c3d4e5f6g7h8i9j0";

// Java -- correct approach: load from environment
private static final String API_KEY = System.getenv("OPENAI_API_KEY");

How to Fix Hardcoded Credentials

Variables de entorno are the standard solution for secrets in deployed applications. The secret is set in the deployment environment (server, container, Kubernetes secret, or cloud secret manager) and accessed at runtime. The source code contains no secret value, only the key name used to retrieve it.

Secret management services, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Secret Manager, are the production-grade approach for rotating, auditing, and distributing secrets across services. The application retrieves the secret at startup (or on demand) from the vault rather than from an environment variable, providing rotation without redeployment and a full audit log of every access.

.env archivos (using libraries like python-dotenv or dotenv in Node.js) are appropriate for local development. They provide the same interface as environment variables but load from a local file. The critical rule: .env files must be in .gitignore and must never be committed.

Detecting secrets already committed: if a secret has been hardcoded and committed, it should be considered compromised and rotated immediately. Deleting the value from the current branch does not remove it from git history. Tools like git-filter-branch or the BFG Repo Cleaner can scrub history, but the safest assumption after a commit is that the secret is exposed.

How to Prevent Hardcoded API Keys

For third-party API keys, the alternatives to hardcoding depend on the context:

  • Aplicaciones del lado del servidor: environment variables or secret management services
  • pipelines de CI / CD: pipeline secrets variables (GitHub Actions secrets, GitLab CI variables)
  • Aplicaciones móviles: API keys should never be in client-side code; use a backend proxy that calls the API with the key stored server-side
  • AI agents and LLM integrations: load API keys from environment at agent initialization, never pass them as hardcoded strings in prompts or function calls

RPG secure coding on IBM i follows the same principle: external data areas, data queues, or system values should hold environment-specific connection parameters rather than encoding them in program source.

How to Prevent Hardcoded Values: Complete Patterns with Code

Variables de entorno

Environment variables are the most universal solution. Every major operating system, cloud platform, containerization system, and deployment framework supports them.

pitón

# Python: load all configuration from environment
import os
from dotenv import load_dotenv

load_dotenv()  # loads .env file in development; ignored in production

DATABASE_URL = os.environ["DATABASE_URL"]
API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.example.com")
MAX_RETRIES  = int(os.environ.get("MAX_RETRIES", "3"))
DEBUG_MODE   = os.environ.get("DEBUG", "false").lower() == "true"

javascript

// Node.js: access environment variables
require('dotenv').config();  // load .env in development

const config = {
  dbUrl:      process.env.DATABASE_URL,
  apiKey:     process.env.API_KEY,
  port:       parseInt(process.env.PORT, 10) || 3000,
  debug:      process.env.NODE_ENV !== 'production',
};

Java

// Java: environment variables via System.getenv()
public class AppConfig {
    public static final String DB_URL  = System.getenv("DATABASE_URL");
    public static final String API_KEY = System.getenv("THIRD_PARTY_API_KEY");
    public static final int    TIMEOUT = Integer.parseInt(
        Optional.ofNullable(System.getenv("REQUEST_TIMEOUT_MS")).orElse("5000")
    );
}

Archivos de configuración

Configuration files externalize values that are environment-specific but not secret, service names, feature flags, pagination sizes, cache TTLs.

yaml

# config/production.yaml
database:
  host: prod-db.internal
  port: 5432
  pool_size: 20

api:
  base_url: https://api.partner.com/v2
  timeout_ms: 3000
  max_retries: 3

features:
  new_checkout: true
  beta_dashboard: false

pitón

# Load at startup; never hardcode the values it contains
import yaml

with open(f"config/{os.environ['APP_ENV']}.yaml") as f:
    config = yaml.safe_load(f)

timeout = config["api"]["timeout_ms"]

Inyección de dependencia

Dependency injection passes configured values into components rather than having components create or retrieve their own configuration. This makes components testable in isolation and configurable per environment.

Java

// Spring Boot: inject configuration via @Value
@Service
public class PaymentService {
    @Value("${payment.api.url}")
    private String apiUrl;

    @Value("${payment.api.timeout-ms}")
    private int timeoutMs;

    // No hardcoded URL or timeout anywhere in this class
    public PaymentResult process(Payment payment) {
        // uses apiUrl and timeoutMs injected from application.properties
    }
}

pitón

# Python: simple constructor injection
class EmailService:
    def __init__(self, smtp_host: str, smtp_port: int, api_key: str):
        self.smtp_host = smtp_host
        self.smtp_port = smtp_port
        self.api_key   = api_key

# Wire at application startup, reading from environment
email_service = EmailService(
    smtp_host=os.environ["SMTP_HOST"],
    smtp_port=int(os.environ["SMTP_PORT"]),
    api_key=os.environ["EMAIL_API_KEY"],
)

Centralized Constants and Enums

Values that are genuinely fixed by design, HTTP status codes, domain-specific states, protocol identifiers, belong in a single, named constants module rather than scattered as string literals.

pitón

# Python: centralised constants
from enum import Enum

class OrderStatus(Enum):
    PENDING   = "pending"
    CONFIRMED = "confirmed"
    SHIPPED   = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

# Usage: no magic strings scattered across modules
if order.status == OrderStatus.CONFIRMED:
    notify_warehouse(order)

Java

// Java: enum with business meaning
public enum UserRole {
    ADMIN("admin"),
    EDITOR("editor"),
    VIEWER("viewer");

    private final String value;
    UserRole(String value) { this.value = value; }
    public String getValue() { return value; }
}

// Replaces scattered "admin", "editor", "viewer" strings
if (user.getRole() == UserRole.ADMIN) {
    grantFullAccess(user);
}

Hardcoding in Specific Languages

What Is Hardcoding in Python?

In Python, hardcoding most commonly appears as string literals for URLs and credentials, numeric constants in business logic, and file paths assuming a specific directory structure. Python’s os.environ y python-dotenv are the standard mechanisms for environment-based configuration. The configparser module handles INI-format configuration files. pydantic-settings provides type-validated configuration from environment variables with default values, which is the recommended approach for production Python services.

Detecting hardcoded values in Python: static analysis tools including Bandit (for security-related hardcoding like passwords and keys), Pylint, and Semgrep can identify hardcoded credentials and magic numbers automatically.

What Is Hardcoding in Java?

In Java, hardcoded values often appear as private static final String campos, @Value annotations with inline defaults that should be externalized, and literals in business logic. Spring Boot’s application.properties y application.yml files are the standard externalization mechanism. @ConfigurationProperties-annotated classes provide type-safe, validated configuration objects from YAML or properties files.

Detecting hardcoded values in Java: SpotBugs with the Find Security Bugs plugin detects hardcoded credentials. SonarQube flags magic numbers, hardcoded URLs, and hardcoded passwords. SMART TS XL’s enterprise analysis covers Java codebases alongside COBOL and other legacy languages.

Hardcoding in Legacy and Mainframe Code

COBOL, RPG, and PL/I programs on IBM mainframes and midrange systems have specific hardcoding patterns: dataset names embedded in DD statements, environment identifiers in program logic, and business thresholds compiled directly into programs. Externalizing these values requires mainframe-aware tooling: system parameters (SYSPARM), external data areas, configuration tables in DB2, and parameterized JCL. Static analysis tools that understand mainframe languages are required to detect these patterns at scale.

Real-World Refactoring: From Hardcoded to Configurable

The Three-Phase Refactoring Approach

Phase 1: Discover. Run static analysis across the entire codebase to produce an inventory of hardcoded values, grouped by type (credentials, URLs, business logic, magic numbers) and by risk level. Prioritize credentials and production-specific values.

Phase 2: Externalize by category. Start with credentials (highest risk): move all secrets to environment variables or a secret manager. Then address environment-specific URLs and service names. Then address business logic thresholds. Finally address magic numbers by replacing them with named constants or configuration values.

Phase 3: Enforce. Add static analysis checks to the CI pipeline that fail on new hardcoded credentials. Add linting rules that flag magic numbers. Add code review checklist items. The goal is to make adding a hardcoded value harder than using the correct pattern.

Identifying Hardcoded Values in Legacy Projects

In legacy projects where hardcoded values have accumulated over years:

  • Usar grep or repository search for common patterns: connection strings, password, apikey, IP address patterns, and well-known service names
  • Run static analysis tools (Bandit, SonarQube, Semgrep, SMART TS XL) to get a complete inventory without manual file-by-file review
  • Check git history for secrets that were previously committed and deleted, they are still accessible in the repository history
  • Look for values that appear identically in multiple files, these are candidates for centralization

Preventing Hardcoded Values from Being Introduced

  • Ganchos de pre-confirmación: run credential scanning (e.g., detect-secrets, git-secrets, truffleHog) on every commit before it reaches the repository
  • CI pipeline gates: fail builds that contain newly introduced static analysis findings for credentials or magic numbers
  • Code review checklists: explicit items for configuration externalization in the team’s review process
  • Developer onboarding: make the correct patterns the defaults, provide a project template that already uses environment variables and a .env.example listo para importar

Cómo SMART TS XL Eliminates Hardcoded Values at Scale

Manual search and grep are adequate for finding obvious cases in small codebases. In enterprise systems spanning millions of lines across COBOL, Java, Python, .NET, RPG, and SQL, comprehensive hardcoded value detection requires automated structural analysis. SMART TS XL performs static analysis across all these languages simultaneously, building a unified cross-reference model that identifies:

  • Literal string values in connection parameters and authentication calls, flagged as potential credential exposure
  • Magic numbers in business logic compared against a configurable baseline, identifying values that are inconsistent across modules or that appear with different values in different places
  • Duplicate literal values that appear across multiple files but serve the same logical purpose, indicating centralization opportunities
  • Values in COBOL programs that encode environment-specific identifiers typically managed through JCL parameters
  • Data flow paths from hardcoded values through the system, showing which downstream operations depend on them before a refactoring decision is made

El análisis de código estático capability provides the inventory. The análisis de impacto capability shows what will be affected when a hardcoded value is replaced. The modernización heredada capability extends this to cross-language and cross-platform scenarios where the same logical value is hardcoded independently in a mainframe program and in a Java service, requiring coordinated remediation rather than isolated fixes.

For teams managing security debt specifically, SMART TS XL’s detection of hardcoded credentials integrates into CI/CD pipelines as a build gate: new commits that introduce hardcoded secrets fail the build automatically, before the code reaches a repository where it could be exposed.

Hardcoding Is a Discipline Problem, Not a Knowledge Problem

Most developers who hardcode values know they are not supposed to. The knowledge that environment variables exist, that configuration files are the correct approach, and that credentials should never be in source code is widely available and widely understood. The problem is not knowledge: it is discipline under pressure.

Delivery deadlines make environment variable setup feel like overhead. Legacy codebases make externalization feel riskier than leaving values in place. Large teams with inconsistent onboarding make it easy for a new developer to copy an existing pattern that happens to be wrong. Addressing hardcoding at organizational scale therefore requires more than documentation: it requires automated enforcement that makes the wrong pattern harder than the right one.

Pre-commit hooks that reject commits containing secrets, CI pipelines that fail on new magic numbers, code review checklists that explicitly ask about configuration externalization, and static analysis tools that surface the full scope of existing hardcoded values in a codebase: these are the mechanisms that close the gap between knowing the right approach and consistently applying it. The code examples and patterns in this article provide the technical foundation. The enforcement mechanisms are what make them stick.