파이썬의 리팩토링 설계 패턴

파이썬의 리팩토링 설계 패턴

Python 의이 테이블에는 코드 냄새 목록과이를 해결하는 설계 패턴이 포함되어 있습니다.

class CodeSmells:
    Duplicated_Code = [
        form_template_method,
        introduce_polymorphic_creation_with_factory_method,
        chain_constructors,
        replace_one__many_distinctions_with_composite,
        extract_composite,
        unify_interfaces_with_adapter,
        introduce_null_object,
    ]
    Long_Method = [
        compose_method,
        move_accumulation_to_collecting_parameter,
        replace_conditional_dispatcher_with_command,
        move_accumulation_to_visitor,
        replace_conditional_logic_with_strategy,
    ]
    Conditional_Complexity = [  # Complicated conditonal logic
        replace_conditional_logic_with_strategy,
        move_emblishment_to_decorator,
        replace_state_altering_conditionals_with_state,
        introduce_null_object,
    ]
    Primitive_Obssession = [
        replace_type_code_with_class,
        replace_state_altering_conditionals_with_state,
        replace_conditional_logic_with_strategy,
        replace_implict_tree_with_composite,
        replace_implicit_language_with_interpreter,
        move_emblishment_to_decorator,
        encapsulate_composite_with_builder,
    ]
    # Lack of "information hiding" [Parnas]
    Indecent_Exposure = [encapsulate_classes_with_factory]
    # The logic/responsibility is sprawled in multiple places
    # (classes, methods)
    Solution_Sprawl = [move_creation_knowledge_to_factory]
    # [Fowler and Beck] Interfaces of classes different,
    # but classes are similar
    Alternative_Classes_with_Different_Interfaces = unify_interfaces_with_adapter
    # [Fowler and Beck] A class the doesn't do enough to pay itself
    Lazy_Class = [inline_singleton]
    Large_Class = [
        replace_conditional_dispatcher_with_command,
        replace_state_altering_conditionals_with_state,
        replace_implict_tree_with_composite,
    ]
    Switch_Statements = [  # Complicated switches
        replace_conditional_dispatcher_with_command,
        move_accumulation_to_visitor,
    ]
    # Code that do the same with different types or quantity of data
    # (similar to duplication)
    Combination_Explostion = [replace_implicit_language_with_interpreter]
    # The same problem being solved in many ways in the system
    # (similar to duplication)
    Oddball_Solutions = [unify_interfaces_with_adapter]

여행

거의 1 년의 노력 끝에, 나는 마침내 책에서 모든 리팩토링 사례를 작성하려는 자체 부과 목표를 완료했습니다. 패턴에 대한 리팩토링 Python의 Joshua Kerievsky. 이 책은 프로덕션 코드에서 디자인 패턴을 적용하는 방법에 대한 이해를 넓혔습니다.

각 예제에는 원래 코드 및 컨텍스트에 대한 간단한 설명과 리팩토링 된 코드와 리팩토링을 통해 얻은 이점이 포함됩니다. 예를 들어, “Compose Method”에 대한 리팩토링은 읽기 어려운 코드를 간단하고 간소화 된 구현으로 변환합니다.

이 예제를 작성하고 원래 코드를 살펴 보겠습니다.

# Original code
# It is not easy to understand the code
def add(element):
    readonly = False
    size = 0
    elements = []
    if not readonly:
        new_size = size + 1
        if new_size > len(elements):
            new_elements = []
            for i in range(size):
                new_elements[i] = elements[i]
            elements = new_elements
        size += 1
        elements[size] = element

코드가 이해하기 쉽지 않다는 것을 알 수 있습니다. 중첩 된 조건과 루프가 많이 있습니다. 이제 Refactored 코드로 가자.

# Code Refactored
# The new code has meaningfull names for blocks of code and is not nested.
# The Compose Method is a refactoring to simplificate the code
def at_capacity(new_size, elements):
    new_size > len(elements)


def grow(size):
    new_elements = []
    for i in range(size):
        new_elements[i] = elements[i]
    elements = new_elements


def add_elements(elements, element, size):
    size += 1
    elements[size] = element


def add_refac(element):
    readonly = False
    if readonly:
        return
    if at_capacity:
        grow()
    add_elements(element)

리팩토링의 아이디어는 의미있는 방법으로 합병증을 줄이고 중첩 된 가지를 제거하는 것입니다. 코드 블록을 메소드에 추출해야했습니다.

책을 통해 작업하고 예제를 작성하는 동안 UML 다이어그램을 해석하고 메커니즘을 자세히 이해해야했습니다. 이것은 강렬한 초점과 정신적 노력이 필요했습니다. 여러 번, Java에서 Python으로 코드를 변환하는 것이 간단하지 않기 때문에 예제를 처음부터 다시 작성해야했습니다. 기본 파이썬은 순환 수입, 생성자 오버로드 또는 인터페이스를 잘 지원하지 않으므로 일부 적응이 필요했습니다. 코드의 향후 상담을 돕기 위해이 영역에 대한 의견을 추가했습니다.

이 과정을 통해 디자인 패턴에 대한 이전의 이해는 대부분 이론적이며 사소한 시나리오로 제한되어 있음을 깨달았습니다. 예를 들어, “다형성”이 개발 문제를 해결한다는 것을 이해했지만,이 책은 설정 단계를 추상화하고 나머지 테스트 구현을 재사용하여 테스트 자동화에 적용을 보여주었습니다.

다음은 두 가지 버전의 코드입니다. 원본과 새 코드의 차이점은 테스트 설정입니다.

# Original code
# Similar methods differs from the object instantiation.
# All the rest is the same
class TestCase:
    pass


class DOMBuilder:
    def __init__(self, orders) -> None:
        pass

    def calc(self):
        return 42


class XMLBuilder:
    def __init__(self, orders) -> None:
        pass

    def calc(self):
        return 42


class DOMTest(TestCase):
    def run_dom_test(self):
        expected = 42
        builder = DOMBuilder("orders")  # different object created
        assert builder.calc() == expected


class XMLTest(TestCase):
    def run_xml_test(self):
        expected = 42
        builder = XMLBuilder("orders")  # different object created
        assert builder.calc() == expected


# Code refactored
# The instantiation of the DOMBuilder or XMLBuilder is the only difference
# in both tests.
# It was created an OutputBuilder like an interface for both classes
# (it is not necessary given that Python uses duck type).
# In TestCase a new method called "create_builder" was introduced to be
# implemented by the children classes.
# This is the step executed in runtime for each type of test. This is the
# polymorphism. When both tests (DOMTest and XMLTest) are executed,
# the instance returned from the "create_builder" depends on the
# implementation. Is can be DOMBuilder or XMLBuilder.
class OutputBuilder:
    def calc(self):
        raise NotImplementedError()


class DOMBuilderRefac(OutputBuilder):
    def calc(self):
        return 42


class XMLBuilderRefac(OutputBuilder):
    def calc(self):
        return 42


class TestCaseRefac:
    def create_builder(self):
        raise NotImplementedError()

    def run_test(self):
        expected = 42
        builder = self.create_builder()  # different object created
        assert builder.calc() == expected


class DOMTestRefac(TestCaseRefac):
    def create_builder(self) -> OutputBuilder:
        return DOMBuilderRefac()


class XMLTestRefac(TestCaseRefac):
    def create_builder(self):
        return XMLBuilderRefac()


def run():
    dom_tc = DOMTestRefac()
    dom_tc.run_test()

    xml_tc = XMLTestRefac()
    xml_tc.run_test()

“방문자”패턴은 내가 이해하기 가장 어려웠습니다. 나는 패턴에 대해 읽었다 디자인 패턴 리팩토링을 시도하기 전에 예약하십시오. 원본 (반복되지 않은) 코드가 새 버전으로 변환되는 것을 본 후에야 패턴이 처음처럼 보이는 것처럼 패턴이 복잡하지 않다는 것을 깨달았습니다. 본질적으로 패턴은 방법에서 클래스를 분리합니다. 다시, 두 코드 모두 비교를위한 것입니다. 패턴의 구현은 “책에 의해”입니다.

# Original code
# The TextExtractor has lots of conditons to handle Nodes, like StringNode
# The idea ofthe rectoring is distribute the logic into Visitor classes


# Interface
class Node:
    pass


class LinkTag(Node):
    pass


class Tag(Node):
    pass


class StringNode(Node):
    pass


class TextExtractor:
    def extract_text(self, nodes: list[Node]):
        result = []
        for node in nodes:
            if isinstance(node, StringNode):
                result.append("string")
            elif isinstance(node, LinkTag):
                result.append("linktag")
            elif isinstance(node, Tag):
                result.append("tag")
            else:
                result.append("other")
        return result


# Code refactored
# Interface (the visitor)
class NodeVisitorRefac:
    def visit_link_tag(self, node):
        return "linktag"

    def visit_tag(self, node):
        return "tag"

    def visit_string_node(self, node: object):
        return "string"


class NodeRefac:
    def accept(self, node: NodeVisitorRefac):
        pass


class LinkTagRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_link_tag(self)


class TagRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_tag(self)


class StringNodeRefac(NodeRefac):
    def accept(self, node: NodeVisitorRefac):
        return node.visit_string_node(self)


# The concret visitor
class TextExtractorVisitorRefac(NodeVisitorRefac):
    def extract_text(self, nodes: list[NodeRefac]):
        result = []
        for node in nodes:
            result.append(node.accept(self))
        return result


def run_refac():
    # The original object calling its method
    result1 = TextExtractor().extract_text([StringNode()])
    # The new object accepting visitors
    result2 = TextExtractorVisitorRefac().extract_text([StringNodeRefac()])
    return result1, result2

결론

나는 모든 사람 에게이 책을 강력히 추천합니다. 처음 읽을 때, 나는 정적 코드 예제에 따라 개념을 파악하기가 어렵다는 것을 알았습니다. 그러나 코드를 적극적으로 작성하면 아이디어가 점차 활기차게됩니다. 오류가 발생하고이를 해결하려면 기본 개념을 이해해야합니다. 이 과정은 이론을 실천으로 바꾸고 지식을 강화합니다.

출처 참조

Post Comment

당신은 놓쳤을 수도 있습니다