Introduction
In this post, we will review a refactoring process for loading application configuration in Go.
The original implementation (referred to as Version 2) directly used hard-coded environment variable names inside the Load function.
The refactored version (Version 1) introduced constant definitions for environment variable keys and default values, improving both readability and maintainability.
Original Code (Version 2)
In the initial implementation, environment variable keys were inline literals within the Load function:
func Load() *Config {
return &Config{
EnvKeys: os.Getenv("INPUT_ENV_KEY"),
EnvValues: os.Getenv("INPUT_ENV_VALUE"),
OutputKeys: os.Getenv("INPUT_OUTPUT_KEY"),
OutputValues: os.Getenv("INPUT_OUTPUT_VALUE"),
GithubEnv: os.Getenv("GITHUB_ENV"),
GithubOutput: os.Getenv("GITHUB_OUTPUT"),
Delimiter: getEnvWithDefault("INPUT_DELIMITER", ","),
FailOnEmpty: getBoolEnv("INPUT_FAIL_ON_EMPTY", true),
TrimWhitespace: getBoolEnv("INPUT_TRIM_WHITESPACE", true),
CaseSensitive: getBoolEnv("INPUT_CASE_SENSITIVE", true),
ErrorOnDuplicate: getBoolEnv("INPUT_ERROR_ON_DUPLICATE", true),
MaskSecrets: getBoolEnv("INPUT_MASK_SECRETS", false),
MaskPattern: getEnvWithDefault("INPUT_MASK_PATTERN", ""),
ToUpper: getBoolEnv("INPUT_TO_UPPER", false),
ToLower: getBoolEnv("INPUT_TO_LOWER", false),
EncodeURL: getBoolEnv("INPUT_ENCODE_URL", false),
EscapeNewlines: getBoolEnv("INPUT_ESCAPE_NEWLINES", true),
MaxLength: getIntEnv("INPUT_MAX_LENGTH", 0),
AllowEmpty: getBoolEnv("INPUT_ALLOW_EMPTY", false),
DebugMode: getBoolEnv("DEBUG_MODE", false),
}
}
- Issues with This Approach
- Magic Strings: Each environment variable name appears as a literal string, which increases the risk of typos.
- Maintainability: If the environment variable name changes, it needs to be updated in multiple places.
- Lack of Central Documentation: No clear central reference point for all supported environment variables.
Refactored Code (Version 1)
The refactored version introduces central constants for environment variable names and default values:
// Environment Variable Names Constants
const (
// Input Environment Variables
EnvKeyInput = "INPUT_ENV_KEY"
EnvValueInput = "INPUT_ENV_VALUE"
OutputKeyInput = "INPUT_OUTPUT_KEY"
OutputValueInput = "INPUT_OUTPUT_VALUE"
DelimiterInput = "INPUT_DELIMITER"
FailOnEmptyInput = "INPUT_FAIL_ON_EMPTY"
TrimWhitespaceInput = "INPUT_TRIM_WHITESPACE"
CaseSensitiveInput = "INPUT_CASE_SENSITIVE"
ErrorOnDuplicateInput = "INPUT_ERROR_ON_DUPLICATE"
MaskSecretsInput = "INPUT_MASK_SECRETS"
MaskPatternInput = "INPUT_MASK_PATTERN"
ToUpperInput = "INPUT_TO_UPPER"
ToLowerInput = "INPUT_TO_LOWER"
EncodeURLInput = "INPUT_ENCODE_URL"
EscapeNewlinesInput = "INPUT_ESCAPE_NEWLINES"
MaxLengthInput = "INPUT_MAX_LENGTH"
AllowEmptyInput = "INPUT_ALLOW_EMPTY"
DebugModeInput = "DEBUG_MODE"
// GitHub Environment Variables
GithubEnvVar = "GITHUB_ENV"
GithubOutputVar = "GITHUB_OUTPUT"
)
// Default Values Constants
const (
DefaultDelimiter = ","
DefaultFailOnEmpty = true
DefaultTrimWhitespace = true
DefaultCaseSensitive = true
DefaultErrorOnDuplicate = true
DefaultMaskSecrets = false
DefaultMaskPattern = ""
DefaultToUpper = false
DefaultToLower = false
DefaultEncodeURL = false
DefaultEscapeNewlines = true
DefaultMaxLength = 0
DefaultAllowEmpty = false
DefaultDebugMode = false
)
// Config holds the application configuration
type Config struct {
// Input/Output Keys and Values
EnvKeys string
EnvValues string
OutputKeys string
OutputValues string
// GitHub File Paths
GithubEnv string
GithubOutput string
// Input Processing Options
Delimiter string
FailOnEmpty bool
TrimWhitespace bool
CaseSensitive bool
ErrorOnDuplicate bool
AllowEmpty bool
// Value Transformation Options
ToUpper bool
ToLower bool
EncodeURL bool
EscapeNewlines bool
MaxLength int
// Security Options
MaskSecrets bool
MaskPattern string
// Debug Options
DebugMode bool
}
// Load loads configuration from environment variables
func Load() *Config {
return &Config{
// Input/Output Keys and Values
EnvKeys: os.Getenv(EnvKeyInput),
EnvValues: os.Getenv(EnvValueInput),
OutputKeys: os.Getenv(OutputKeyInput),
OutputValues: os.Getenv(OutputValueInput),
// GitHub File Paths
GithubEnv: os.Getenv(GithubEnvVar),
GithubOutput: os.Getenv(GithubOutputVar),
// Input Processing Options
Delimiter: getEnvWithDefault(DelimiterInput, DefaultDelimiter),
FailOnEmpty: getBoolEnv(FailOnEmptyInput, DefaultFailOnEmpty),
TrimWhitespace: getBoolEnv(TrimWhitespaceInput, DefaultTrimWhitespace),
CaseSensitive: getBoolEnv(CaseSensitiveInput, DefaultCaseSensitive),
ErrorOnDuplicate: getBoolEnv(ErrorOnDuplicateInput, DefaultErrorOnDuplicate),
AllowEmpty: getBoolEnv(AllowEmptyInput, DefaultAllowEmpty),
// Value Transformation Options
ToUpper: getBoolEnv(ToUpperInput, DefaultToUpper),
ToLower: getBoolEnv(ToLowerInput, DefaultToLower),
EncodeURL: getBoolEnv(EncodeURLInput, DefaultEncodeURL),
EscapeNewlines: getBoolEnv(EscapeNewlinesInput, DefaultEscapeNewlines),
MaxLength: getIntEnv(MaxLengthInput, DefaultMaxLength),
// Security Options
MaskSecrets: getBoolEnv(MaskSecretsInput, DefaultMaskSecrets),
MaskPattern: getEnvWithDefault(MaskPatternInput, DefaultMaskPattern),
// Debug Options
DebugMode: getBoolEnv(DebugModeInput, DefaultDebugMode),
}
}
Question
I modified the code as above, but I think it got too long? Is it a lot? I'm a DevOps engineer and I'm also studying development.
Top comments (0)