Refactoring with Interface

I like refactoring

Refactoring code involves extracting an interface from a complex, large class.

class LanguageProcessor:
    def concat(self, prev_text, new_text):
        if self.language == 'en':
            ... # Concatenate logic for English
        elif self.language == 'ja':
            ...

In my understanding, the interface should be:

Clear input/output design, minimal code, and single-responsibility simplicity.

In this case, I would do something like this:

class BaseConcatter:
    # Define interface
    @abstractmethod
    def concat(self, prev_text: str, new_text: str):
        ...

class CustomConcatException(Exception):
    pass

class EnglishConcatter(BaseConcatter):
    ...

class JapaneseConcatter(BaseConcatter):
    ...

class Language(Enum):
    ENGLISH = auto()
    JAPANESE = auto()
    ...

concatters = {
    Language.ENGLISH: EnglishConcatter(),
    Language.JAPANESE: JapaneseConcatter(),
}

Then, the original code becomes:

class LanguageProcessor:
    def __init__(self):
        self.concatters = concatters

    def concat(self, prev_text, new_text):
        if self.language not in self.concatters:
            raise SomeException('Unsupported language to concat.')
        result = self.concatters[self.language].concat(prev_text, new_text)
        ...

Design the interface, implement it, create factories, and trim conditionals. It looked good until it wasn’t.

Stupid Mistake

There is some code like this:

try:
    language_processor.concat(prev_text, new_text)
except ExistingException:
    # Some handling
except Exception:
    # Cancel process

If you add a new exception in a concatter, like CustomConcatException, the calling code breaks because the new error bypasses the specific handler and drops into the generic path, cancelling the process. Not every concat failure should cancel everything—this actually happened.

Why did it happen?

My understanding of the interface was incomplete. It is not just input/output; it includes everything observable from outside the process. Exception handling is part of the contract, and I missed it.

Lesson Learned

An interface should define inputs, outputs, and expected exceptions; stay minimal; and keep single responsibility. When refactoring existing code, the exception contract can be the most important part.