Refactorizando un método 1500 LOC que solo construye la IU gráfica [cerrado]


8

Actualmente me estoy rascando la cabeza sobre cómo refactorizar un método que básicamente solo construye la interfaz de usuario.

El método tiene más de 1500 líneas de código (LOC) de largo, y contando. Ha crecido, no había un plan para abordar esto. Puede que te resulte familiar.

De todos modos, es básicamente un método grande que se parece a esto:

    .
    .
    .

    # null checks
    null_checks_bx = Box(True)

    null_checks_ck = CheckBox()
    null_checks_ck.set_text('Null checks throwing exceptions of type:')

    if 'doNullChecks' in options:
        null_checks_ck.set_active(options['doNullChecks'])
    else:
        null_checks_ck.set_active(True)

    # dict to sorted list: extract values from dict by list comprehension
    exceptions = sorted([exception.get_full_name() for exception in JavaTypes.exception_types])

    null_checks_se = Selector()
    null_checks_se.add_items(exceptions)
    null_checks_se.set_enabled(null_checks_ck.get_active())

    if 'nullChecksExceptionFullClassName' in options:
        null_checks_se.set_value(options['nullChecksExceptionFullClassName'])
    else:
        # default to first entry
        #(defaulting to doEquals = True and doHashCode = True by using hardcoded Commons Lang implies availability of Commons Lang NullArgumentException)
        null_checks_se.set_value(JavaTypes.exception_types[0].get_full_name())

    # callback
    Callbacks.sync_model_active_status(null_checks_ck, null_checks_se)

    null_checks_ck.add_clicked_callback(lambda: Callbacks.sync_model_active_status(null_checks_ck, null_checks_se))

    null_checks_bx.add(Label(indent), False, True)  # dummy indent label
    null_checks_bx.add(null_checks_ck, False, True)
    null_checks_bx.add(null_checks_se, False, True)

    # relationship entity class constructor calls
    relationship_constructor_calls_bx = Box(True)
    #relationship_constructor_calls_bx.set_spacing(5)
    #relationship_constructor_calls_bx.set_padding(3)

    relationship_constructor_calls_ck = CheckBox()
    relationship_constructor_calls_ck.set_text('Instantiate relationship entities (if all necessary columns present)')

    if 'doRelationshipConCalls' in options:
        relationship_constructor_calls_ck.set_active(options['doRelationshipConCalls'])
    else:
        relationship_constructor_calls_ck.set_active(False)

    relationship_constructor_calls_bx.add(Label(indent), False, True)  # dummy indent label
    relationship_constructor_calls_bx.add(relationship_constructor_calls_ck, False, True)

    # relationship not-null checks: this is an option independent of the null checks!
    relationship_not_null_checks = Box(True)
    #relationship_not_null_checks.set_spacing(5)
    #relationship_not_null_checks.set_padding(3)

    relationship_not_null_checks_ck = CheckBox()
    relationship_not_null_checks_ck.set_text('Not-null checks before relationship instantiation')

    relationship_not_null_checks_ck.set_enabled(relationship_constructor_calls_ck.get_active() and not null_checks_ck.get_active())

    if 'doRelationshipNotNullChecks' in options:
        relationship_not_null_checks_ck.set_active(options['doRelationshipNotNullChecks'])
    else:
        relationship_not_null_checks_ck.set_active(True)

    # callback
    Callbacks.sync_convenience_constructor_options(null_checks_ck, relationship_constructor_calls_ck, relationship_not_null_checks_ck)

    null_checks_ck.add_clicked_callback(lambda: Callbacks.sync_convenience_constructor_options(null_checks_ck, relationship_constructor_calls_ck, relationship_not_null_checks_ck))
    relationship_constructor_calls_ck.add_clicked_callback(lambda: Callbacks.sync_convenience_constructor_options(null_checks_ck, relationship_constructor_calls_ck, relationship_not_null_checks_ck))

    relationship_not_null_checks.add(Label(indent), False, True)  # dummy indent label
    relationship_not_null_checks.add(Label(indent), False, True)  # dummy indent label
    relationship_not_null_checks.add(relationship_not_null_checks_ck, False, True)

    # build final box
    checks_bx = Box(False)
    checks_bx.set_spacing(5)
    #checks_bx.set_padding(3)
    checks_bx.set_homogeneous(True)
    checks_bx.add(omit_auto_increment_columns_bx, False, True)
    checks_bx.add(omit_auto_timestamp_columns_bx, False, True)
    checks_bx.add(null_checks_bx, False, True)
    checks_bx.add(relationship_constructor_calls_bx, False, True)
    checks_bx.add(relationship_not_null_checks, False, True)

    # callback
    Callbacks.sync_model_active_status(convenience_constructors_ck, checks_bx)

    convenience_constructors_ck.add_clicked_callback(lambda: Callbacks.sync_model_active_status(convenience_constructors_ck, checks_bx))

    # need wrapped box for Panel class
    constructors_bx = Box(False)
    constructors_bx.set_spacing(5)
    constructors_bx.set_padding(3)
    #constructors_bx.set_homogeneous(True)
    constructors_bx.add(convenience_constructors_bx, False, True)
    constructors_bx.add(checks_bx, False, True)

    constructors_pn = Panel(TitledGroupPanel)
    constructors_pn.set_title('Constructors')
    constructors_pn.add(constructors_bx)

    # getters and setters
    is_conversion_bx = Box(True)
    #is_conversion_bx.set_spacing(3)
    #is_conversion_bx.set_padding(3)

    is_conversion_ck = CheckBox()
    is_conversion_ck.set_text('Convert respective column names starting with "is_"')
    is_conversion_ck.set_tooltip("Column is_incognito => getter isIncognito() (not getIsIncognito()) and setter setIncognito()")

    if 'doIsConversion' in options:
        is_conversion_ck.set_active(options['doIsConversion'])
    else:
        is_conversion_ck.set_active(True)

    is_conversion_bx.add(is_conversion_ck, False, True)

    # generate code for relationship getters and setters to synchronize with column fields
    sync_relationship_pk_fields_bx = Box(True)
    #sync_relationship_pk_fields_bx.set_spacing(3)
    #sync_relationship_pk_fields_bx.set_padding(3)

    sync_relationship_pk_fields = CheckBox()

    .
    .
    .

Tú entiendes.

Lo que no veo es cuáles podrían ser los criterios para dividir este método para que el código sea más manejable, más comprensible, etc. pp.

¿Cuáles son las mejores prácticas aquí?

Podría crear algunos métodos que crearían tipos recurrentes de paneles y widgets, pero esto se convertiría en un esfuerzo bastante tedioso para lo que no veo la ganancia real.

De nuevo, ¿cuáles son algunos enfoques útiles y prácticos para métodos grandes y torpes que solo crean la interfaz gráfica de usuario?

Gracias

PD: oh y, por cierto, la interfaz de usuario es un cuadro de diálogo único en una aplicación estándar con ventana (sin web), y este cuadro de diálogo tiene 6 pestañas que contienen principalmente casillas de verificación, botones de opción y cuadros de texto. Es un complemento de generación de código del que el usuario puede elegir muchas opciones. Podría publicar una imagen tan pronto como vuelva a ejecutar el complemento ...


Una cosa que cambiaría es separar la configuración estática del estado dinámico.
CodesInChaos

@Kawu también puede aceptar la respuesta si resuelve su problema y responde la pregunta de manera integral.
toniedzwiedz

Si, por su puesto. No soy tan principiante. Es solo que es difícil superar la respuesta aceptada.
Kawu

Respuestas:


27

Su código parece tener una característica que puede usar para su ventaja aquí: todos esos comentarios de agrupación. Puede utilizar esos comentarios como base para dividir el método en fragmentos extrayendo métodos más pequeños . Sacas cada fragmento en su propio método, luego llamas a ese método desde la ubicación original del código.

Cuando comience a extraer estas piezas, naturalmente encontrará duplicación o encontrará relaciones fuertes entre algunos de los métodos. A medida que encuentra la duplicación, la extrae en métodos auxiliares para SECAR su código. A medida que encuentre límites naturales de relación, puede dividirlos en clases separadas de funcionalidad más estrechamente relacionada.

A continuación, por ejemplo, comencé a extraer la lógica para construir la parte de cheques nulos. Lo extraje como método, desglosé algunas piezas que necesitaban un nombre mejor y eliminé el espacio de todas las variables dentro del método. Se puede hacer mucho más, pero eso queda como ejercicio para el lector.

def add_indent_label(self, box):
    box.add(Label(indent), False, True)

def get_null_checks(self, options):
    box = Box(True)

    checkbox = CheckBox()
    checkbox.set_text('Null checks throwing exceptions of type:')

    if 'doNullChecks' in options:
        checkbox.set_active(options['doNullChecks'])
    else:
        checkbox.set_active(True)

    exceptions = self.get_exceptions()

    selector = Selector()
    selector.add_items(exceptions)
    selector.set_enabled(checkbox.get_active())

    if 'nullChecksExceptionFullClassName' in options:
        selector.set_value(options['nullChecksExceptionFullClassName'])
    else:
        selector.set_value(self.get_first_exception())

    # callback
    Callbacks.sync_model_active_status(checkbox, selector)
    checkbox.add_clicked_callback(lambda: Callbacks.sync_model_active_status(checkbox, selector))

    self.add_indent_label(box);
    box.add(checkbox, False, True)
    box.add(selector, False, True)

    return box

def get_exceptions(self):
    return sorted([exception.get_full_name() for exception in JavaTypes.exception_types])

# This could probably have a better name
def get_first_exception(self):
    #(defaulting to doEquals = True and doHashCode = True by using hardcoded Commons Lang implies availability of Commons Lang NullArgumentException)
    return JavaTypes.exception_types[0].get_full_name()

Y en su código original, se convierte en:

null_checks_bx = self.get_null_checks(options)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.