RodrigoToroIcarte / IIC2113-2023-1

6 stars 0 forks source link

Duda clean code: largo de una función/método #113

Open SamWeinstein opened 1 year ago

SamWeinstein commented 1 year ago

Hola!

Me estoy enfocando 100%, desde la entrega pasada, en limpiar mi código y noté que la principal razón que me quita puntaje en clean code es la extensión de mis métodos. Es por esto que acortar los métodos fue mi principal preocupación en la entrega pasada (E4), sin embargo, me llevé la sorpresa de que sigo teniendo 0 puntos en el capitulo de 3 de clean code por la extensión de mis métodos. En los comentarios de la corrección, se me dieron algunos métodos de referencia que estaban muy largos, sin embargo, todos esos métodos mencionados están en el rango de 20-25 lineas.

Revisé en el capitulo 3 de clean code si existía algún parámetro como regla pero no encontré nada claro. ¿Existe algún número máximo de lineas que puede tener un método para tenerlo como parámetro? De ese modo me aseguro de que ningún método de mi tarea sobrepase esa cantidad de lineas.

Aprovecho de hacer la misma pregunta pero para el caso de las clases. ¿Hay algún número de lineas desde el cual una clase se considera muy larga?

muchas gracias!

kipasten commented 1 year ago

Hola! soy alumno, pero creo recordar de clases que los metodos deben ser de 5 lineas aprox, ya más que eso es largo... y el largo de clases no se menciona, sin embargo, si se mantiene el principio de una responsabilidad por clase debería estar bien y quedar clases cortas, igual si hay más de 200 lineas en una clase puede considerarse larga y tal vez esté haciendo más de una cosa. Recortar los métodos, mas que hacerlos eficientes, es separarlos en funciones que hagan una sola cosa, como ejemplo, recorrer una lista es una y lo que pasa con cada uno de sus elementos es otra función... Si te quedan muchas funciones dentro de una clase y hacen cosas diferentes, puede pasar que de ahí salga una clase nueva con una responsabilidad especifica que agrupe ciertas funciones de la clase. Igual esperaría la respuesta del profe, pero espero te sirva por mientras Suerte con la limpieza de código!

SamWeinstein commented 1 year ago

Muchísimas gracias por tu respuesta @kipasten !! espero la confirmación del profesor respecto a si es efectivamente 5 lineas como máximo por método (me parece demasiado poco 🥲) para asegurarme que absolutamente ningún método supere ese límite.

Encontré en el texto de clean code que dice que no debería pasar las 20 líneas por ningún motivo. Estaba adaptando el código para eso hasta ahora.

RodrigoToroIcarte commented 1 year ago

Confirmo la respuesta de Ignacio. La regla es que tus funciones deben ser los más cortas posibles. Si sigues las reglas del capítulo tus funciones naturalmente terminarán teniendo 5 o menos líneas.

Por ejemplo, considera este refactoring que usa de ejemplo el libro. Se pasa de este código:

Screenshot from 2023-06-30 16-14-48

A este: Screenshot from 2023-06-30 16-15-07

En todos los ejemplos de "código limpio" las funciones tienen alrededor de 5 líneas.

¿Cómo llegamos a tener funciones así de chicas? Simple, hay que llamar desde funciones a otras funciones. Por ejemplo, el libro nos dice que todo bloque dentro de un while o if debería ser una sola línea (un llamado a otra función). Con eso, una función con un while termina teniendo dos o tres líneas (el while, el llamado a la función, y posiblemente un return al final):

Screenshot from 2023-06-30 16-19-28

¿Qué ganamos con esto? Ganamos que quien lea el código se pueda saltar bloques enteros de código que no le interesan.

El semestre pasado un estudiante mandó a recorregir su examen. Él argumentaba que la siguiente función cumplía con los principios del capítulo (esta es la misma pregunta de clean code del simulacro del examen que dejé en canvas):

public void MapRoute()
{
    int node = _startingNode;
    foreach (char road in _route)
    {
        if (_roads.RoadExist(node, road))
        {
            _cost += _roads.GetCost(node, road);
            node = _roads.GetEndingNode(node, road);
            _nodes.Add(node);
        }
        else
        {
            SetRouteAsInvalid();
            break;
        }
    }
}

Mi respuesta: El método MapRoute rompe varias reglas del capítulo 3 (independiente del número de líneas): (1) Es difícil de entender, (2) tiene un mal nombre, (3) hace más de una cosa (construye la ruta, suma costos, revisa si la ruta es válida, maneja el caso en que la ruta es inválida, etc), (4) tiene 2 niveles de indentación cuando fácilmente podría haber sido 1, (5) mezcla instrucciones de bajo nivel (foreach, +=, etc) con llamados de alto nivel (SetRouteAsInvalid), y (6) es raro porque este método hace incluso más cosas que los métodos originales que di en el código base.

Compara entender lo que hace el método MapRoute con entender lo que hace el siguiente código:

private TripInformation GetTripInformationAssumingThatTheTripIsValid()
{
    Trip trip = ComputeTrip();
    double cost = trip.GetCost();
    int[] nodesInTrip = trip.GetNodesInTrip();

    return new ValidTripInformation(cost, nodesInTrip);
}

Entender este método es relativamente simple (y hace lo mismo que el MapRoute()). Primero que nada el nombre nos dice que obtiene información del Trip asumiendo que el trip es válido. Luego computa el Trip. Calcula su costo. Obtiene sus nodos. Y retorna la información en un objeto ValidTripInformation.

Entender este código es bastante más fácil que entender el MapRoute porque no requiere que entienda el foreach. No tengo que entender porqué se avanza por un string caracter a caracter. No tengo que entender porqué se suman costos ni porqué se cambia el nodo. No tengo que entender qué es un nodo ni por qué es un int. No tengo que entender qué es un road ni porqué a veces no existen. Etc.

El método GetTripInformationAssumingThatTheTripIsValid() esconde todos esos detalles al lector. Básicamente nos dice, lo primero que hacemos es computar un Trip. ¿Es relevante para ti saber cómo computamos el Trip? Bueno, puedes ver ese código en ComputeTrip(). Si no te importa, entonces lo siguiente que hacemos es computar el costo del Trip. Finalmente computamos los nodos del trip y retornamos toda esa información.

En contraste, en el método MapRoute() está todo tan mezclado que el lector está obligado a entenderlo el método completo, con todos sus detalles, para saber si lo que está buscando se encuentra ahí o no.

SamWeinstein commented 1 year ago

Muchísimas gracias profesor @RodrigoToroIcarte . Me queda clarísimo ahora. Solo me queda una simple pregunta: para poder hacer muchísimos métodos cortos pero que las clases no queden tan largas, ¿es "clean" hacer para cada clase que sea muy extensa una nueva clase auxiliar que contenga métodos auxiliares que capaz no corresponde dejarlos en la misma clase? sería en un nuevo archivo obviamente, capaz en un directorio incluso que contenga la clase junto con la clase auxiliar con esos métodos auxiliares.

RodrigoToroIcarte commented 1 year ago

Sí y no. El cap 10 habla un poco sobre eso. Si creas funciones cortas tus clases terminan siendo gigantes. Pero ahí hay una oportunidad. Lo más probable es que tu clase termine con subconjuntos de funciones relacionadas entre ellas. Esos conjuntos de funciones son un HINT. Es el código diciéndote que existe una "miniclase" que quiere escapar de tu clase gigante. Lo difícil es entender qué tienen en común ese conjunto de funciones para poder dar un buen nombre a esa nueva clase:

Screenshot from 2023-06-30 16-53-35