Refactorización de métodos largos: dejar tal cual vs separar en métodos vs usar funciones locales

9

Supongamos que tengo un método largo como este:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

Este método no tiene partes repetitivas que se deben mover a un método separado o función local.

Hay muchas personas (incluido yo) que piensan que los métodos largos son olores de código. Además, no me gusta la idea de usar #regionaquí y hay una respuesta muy popular que explica por qué esto es malo .


Pero si separo este código en métodos

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

Veo los siguientes problemas:

  • Contaminar el alcance de la definición de clase con definiciones que se usan internamente por un solo método y que significa que debo documentar en algún lugar Task1y que Task2están destinadas solo a elementos internos de SomeLongMethod(o cada persona que lea mi código tendrá que inferir esta idea).

  • Contaminación IDE autocompletar (por ejemplo, Intellisense) de métodos que se utilizarían solo una vez dentro del SomeLongMethodmétodo único .


Entonces, si separo este código de método en funciones locales

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

entonces esto no tiene los inconvenientes de los métodos separados, pero esto no se ve mejor (al menos para mí) que el método original.


¿Qué versión de SomeLongMethodes más fácil de mantener y legible para usted y por qué?

Vadim Ovchinnikov
fuente
@gnat mi pregunta es específica para c # , no para java . Mi principal preocupación es sobre el método clásico frente a la función local, no solo separarlos en métodos o no.
Vadim Ovchinnikov
@gnat También AFAIK Java (a diferencia de C #) no admite funciones anidadas.
Vadim Ovchinnikov
1
la palabra clave privada seguramente protege su 'alcance de definición de clase'?
Ewan
1
espera ... ¿qué tan grande es tu clase?
Ewan

Respuestas:

13

Las tareas autónomas deben trasladarse a métodos autónomos. No hay inconveniente lo suficientemente grande como para anular este objetivo.

Usted dice que contaminan el espacio de nombres de la clase, pero esa no es la compensación que debe tener en cuenta al factorizar su código. Es mucho más probable que un futuro lector de la clase (por ejemplo, usted) pregunte "¿Qué hace este método específico y cómo debo cambiarlo para que haga lo que dicen los requisitos modificados?" que "¿Cuál es el propósito y el sentido de ser de todos los métodos en la clase? " Por lo tanto, hacer que cada uno de los métodos sea más fácil de entender (al tener menos elementos) es mucho más importante que hacer que toda la clase sea más fácil de entender (haciéndolo tener menos métodos).

Por supuesto, si las tareas no son realmente autónomas pero requieren que algunas variables de estado se muevan, entonces un objeto de método, un objeto auxiliar o una construcción similar puede ser la opción correcta. Y si las tareas son totalmente autónomas y no están vinculadas con el dominio de la aplicación (por ejemplo, solo el formato de cadena, entonces posiblemente deberían ir a una clase completamente diferente (utilidad). Pero eso no cambia el hecho de que " mantener métodos bajos por clase "es, en el mejor de los casos, un objetivo muy secundario.

Kilian Foth
fuente
Entonces, estás a favor de métodos, no de funciones locales, ¿sí?
Vadim Ovchinnikov
2
@VadimOvchinnikov Con un IDE moderno de plegado de código, hay poca diferencia. La mayor ventaja de una función local es que puede acceder a variables que de otro modo tendría que pasar como parámetros, pero si hay muchas de esas, entonces las tareas no eran realmente independientes para empezar.
Kilian Foth
6

No me gusta ninguno de los enfoques. No proporcionó ningún contexto más amplio, pero la mayoría de las veces se ve así:

Tiene una clase que hace algún trabajo al combinar el poder de múltiples servicios en un solo resultado.

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

Cada uno de sus servicios implementa solo una parte de la lógica. Algo especial, que solo este servicio puede hacer. Si tiene otros métodos privados además de los que admiten la API principal para que pueda ordenarlos fácilmente y no debería haber demasiados de todos modos.

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

La conclusión es: si comienza a crear regiones en su código para agrupar métodos, es hora de comenzar a pensar en encapsular su lógica con un nuevo servicio.

t3chb0t
fuente
2

El problema con la creación de funciones dentro del método es que no reduce la longitud del método.

Si desea el nivel adicional de protección 'privado al método', puede crear una nueva clase

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

Pero las clases en las clases son muy feas: /

Ewan
fuente
2

Minimizar el alcance de las construcciones del lenguaje como variables y métodos es una buena práctica. Por ejemplo, evite las variables globales. Hace que el código sea más fácil de entender y ayuda a evitar el mal uso porque se puede acceder a la construcción en menos lugares.

Esto habla a favor del uso de las funciones locales.

fsl
fuente
1
hmm seguramente reutilizando código == compartiendo un método entre muchas personas que llaman. es decir, maximizar su alcance
Ewan
1

Estoy de acuerdo en que los métodos largos a menudo son un olor a código, pero no creo que esa sea la imagen completa, más bien es por eso que el método es largo que indica un problema. Mi regla general es que si el código está haciendo múltiples tareas distintas, cada una de esas tareas debe ser su propio método. Para mí sí importa si esas secciones de código son repetitivas o no; a menos que esté absolutamente seguro de que nadie querría llamarlos individualmente por separado en el futuro (¡y eso es algo muy difícil de asegurar!) póngalos en otro método (y hágalo privado, lo que debería ser un indicador muy fuerte no espera que se use fuera de la clase).

Debo confesar que aún no he usado funciones locales, por lo que mi comprensión está lejos de ser completa aquí, pero el enlace que proporcionó sugiere que a menudo se usarían de manera similar a una expresión lambda (aunque aquí hay una serie de casos de uso). donde pueden ser más apropiados que lambdas, o donde no puede usar un lambda, vea Funciones locales en comparación con expresiones lambda para detalles). Aquí, entonces, creo que podría deberse a la granularidad de las "otras" tareas; si son bastante triviales, ¿tal vez una función local estaría bien? Por otro lado, he visto ejemplos de expresiones lambda gigantescas (probablemente donde un desarrollador descubrió recientemente LINQ) que han hecho que el código sea mucho menos comprensible y fácil de mantener que si se hubiera llamado simplemente desde otro método.

La página de funciones locales indica que:

'Las funciones locales aclaran la intención de su código. Cualquiera que lea su código puede ver que el método no es invocable, excepto por el método que lo contiene. Para los proyectos de equipo, también hacen que sea imposible para otro desarrollador llamar por error al método directamente desde otro lugar de la clase o estudio ''.

No estoy seguro de que realmente esté con MS en este caso como motivo de uso. El alcance de su método (junto con su nombre y comentarios) debe dejar en claro cuál era su intención en ese momento . Con una función local, podría ver eso, otros desarrolladores pueden verlo como más sacrosanto, pero potencialmente hasta el punto en que si se produce un cambio en el futuro que requiera que se reutilice esa funcionalidad, escriben su propio procedimiento y dejan la función local en su lugar ; creando así un problema de duplicación de código. La última oración de la cita anterior podría ser relevante si no confía en los miembros del equipo para que comprendan un método privado antes de llamarlo, y por lo tanto lo llaman inapropiadamente (por lo que su decisión podría verse afectada por eso, aunque la educación sería una mejor opción aquí) .

En resumen, en general, preferiría separarme en métodos, pero MS escribió Funciones locales por una razón, por lo que ciertamente no diría que no las use,

d219
fuente