New Story

Refactor Like a Pro: Ditch Hardcoded Inputs for Good

by Maximiliano ContieriApril 7th, 2025
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

That little input() call might seem harmless, but it's quietly sabotaging your ability to test, debug, and grow your project.

People Mentioned

Mention Thumbnail
featured image - Refactor Like a Pro: Ditch Hardcoded Inputs for Good
Maximiliano Contieri HackerNoon profile picture
0-item

Transform manual hard-coded inputs into testable functions


TL;DR: Extract input logic into separate functions to make your code testable, with regressions and more maintainable.

Problems Addressed 😔

  • Hard-coded inputs
  • Testing difficulty
  • Poor reusability
  • Hidden dependencies
  • Rigid and coupling implementation
  • Untestable code
  • Unnecessary input validation
  • Hardcoded values
  • Console side effects
  • Poor regression

https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xxxviii

https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xlvii

https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-i-xqz3evd

Steps 👣

  1. Identify code that uses direct input() statements
  2. Create a new function with a meaningful name
  3. Move input logic into the function with parameter options
  4. Add external validation and error handling
  5. Create unit tests for the new function


(If you follow Test-Driven Development, the step 5 becomes step 0)

Sample Code 💻

Before 🚨

n = int(input("Enter a positive integer: "))
# You need to make accidental castings 
# And deal with obscure data types valitaciones
# which are a distraction for new programming students
if n <= 0:
    print("Please enter a positive integer.")
else: 
    print(f"Prime factors of {n}:")
    i = 2
    while i * i <= n:
        if n % i:
            i += 1
        else:
            n //= i
            print(i)
            # You use global resources like the console
            # And your code gets coupled from day one
    if n > 1:
        print(n)
# This example mixes data input and validation
# With algorithmic reasoning
# Violating the "separation of concerns" principle

After 👉

def prime_factors(n):
    i = 2
    factors = []
    while i * i <= n:
        if n % i:
            i += 1
        else:
            n //= i
            factors.append(i)
    if n > 1:
        factors.append(n)
    return factors

# Step 1: Identify code that uses direct input() statements
# Step 2: Create a new function with a meaningful name
def prompt_positive_integer(prompt="Enter a positive integer: "):
    # Step 3: Move input logic into the function with parameter options
    try:
        value = int(input(prompt))
        # Step 4: Add validation and error handling
        if value <= 0:
            raise ValueError("Number must be positive")
        return value
    except ValueError as e:
        if str(e) == "Number must be positive":
            raise
        raise ValueError("Invalid input. Please enter a number.")

def calculate_and_display_factors(number=None):
    try:
        if number is None:
            number = prompt_positive_integer()
        factors = prime_factors(number)
        print(f"Prime factors of {number}:")
        for factor in factors:
            print(factor)
        return factors
    except ValueError as e:
        print(f"Error: {e}")
        return None

# Step 5: Create unit tests for the new function
import unittest
from unittest.mock import patch

class TestPrimeFactors(unittest.TestCase):
    def test_prime_factors_of_12(self):
        self.assertEqual(prime_factors(12), [2, 2, 3])
        
    def test_prime_factors_of_13(self):
        self.assertEqual(prime_factors(13), [13])
        
    def test_prime_factors_of_20(self):
        self.assertEqual(prime_factors(20), [2, 2, 5])
        
    def test_prime_factors_of_1(self):
        self.assertEqual(prime_factors(1), [])

class TestInputFunction(unittest.TestCase):
    @patch('builtins.input', return_value='15')
    def test_get_positive_integer_valid(self, mock_input):
        self.assertEqual(get_positive_integer(), 15)
        
    @patch('builtins.input', return_value='0')
    def test_get_positive_integer_zero(self, mock_input):
        with self.assertRaises(ValueError):
            get_positive_integer()
            
    @patch('builtins.input', return_value='-5')
    def test_get_positive_integer_negative(self, mock_input):
        with self.assertRaises(ValueError):
            get_positive_integer()
            
    @patch('builtins.input', return_value='abc')
    def test_get_positive_integer_not_number(self, mock_input):
        with self.assertRaises(ValueError):
            get_positive_integer()
            
    @patch('builtins.input', return_value='42')
    def test_calculate_with_input(self, mock_input):
        with patch('builtins.print') as mock_print:
            result = calculate_and_display_factors()
            self.assertEqual(result, [2, 3, 7])
            
    def test_calculate_with_argument(self):
        with patch('builtins.print') as mock_print:
            result = calculate_and_display_factors(30)
            self.assertEqual(result, [2, 3, 5])

Type 📝

  • Semi-Automatic

Safety 🛡️

This refactoring is safe but requires careful testing.


Moving from direct input to function calls maintains the same behavior while improving structure.


Adding validation makes the code safer by preventing invalid inputs.


Each step can be tested independently, reducing the risk of introducing bugs and ensuring you have regression on previously tested inputs.

Why is the Code Better? ✨

You can test it without manual input by passing arguments directly to ensure regression of previous cases.


You can reuse the reified functions across your codebase.


You get clear error messages with proper exception handling.


You separate UI logic (getting input) from business logic (running the algorithm).


You make the code more maintainable by following the single responsibility principle.

How Does it Improve the Bijection? 🗺️

This refactoring creates a stronger bijection between the real world and your code by creating distinct functions that map to real-world actions (getting input vs. processing data)


You also add validation that enforces real-world constraints (for example, positive integers only)


In the bijection, it is essential to separate concerns that match actual domain boundaries.


The closer your code matches real-world concepts and constraints, the fewer bugs and surprises you'll encounter.


Dealing with input validation and modeling algorithms following real-world business rules are very different issues, and you should not mix them.

Refactor with AI 🤖

AI can help identify input calls throughout larger codebases and suggest appropriate function signatures and validation rules.


Suggested Prompt: 1. Identify code that uses direct input() statements 2. Create a new function with a meaningful name 3. Move input logic into the function with parameter options 4. Add external validation and error handling 5. Create unit tests for the new function

Without Proper Instructions

With Specific Instructions

ChatGPT

ChatGPT

Claude

Claude

Perplexity

Perplexity

Copilot

Copilot

Gemini

Gemini

DeepSeek

DeepSeek

Meta AI

Meta AI

Qwen

Qwen

Tags 🏷️

  • Coupling

Level 🔋

  • Beginner

Credits 🙏

Image by Spektrum78 on Pixabay


This article is part of the Refactoring Series.


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks