¿Es aceptable copiar y pegar código largo pero directo en lugar de envolverlos en una clase o función?

29

Supongamos que tengo un segmento de código para conectarme a internet y mostrar resultados de conexión como este:

HttpRequest* httpRequest=new HttpRequest();
httpRequest->setUrl("(some domain .com)");
httpRequest->setRequestType(HttpRequest::Type::POST);
httpRequest->setRequestData("(something like name=?&age=30&...)");
httpRequest->setResponseCallback([=](HttpClient* client, HttpResponse* response){
    string responseString=response->getResponseDataString();
        if(response->getErrorCode()!=200){
            if(response->getErrorCode()==404){
                Alert* alert=new Alert();
                alert->setFontSize(30);
                alert->setFontColor(255,255,255);
                alert->setPosition(Screen.MIDDLE);
                alert->show("Connection Error","Not Found");
            }else if((some other different cases)){
                (some other alert)
            }else
                Alert* alert=new Alert();
                alert->setFontSize(30);
                alert->setPosition(Screen.MIDDLE);
                alert->setFontColor(255,255,255);
                alert->show("Connection Error","unknown error");
            }
        }else{
            (other handle methods depend on different URL)
        }
}

el código es largo y se usa comúnmente, pero el código anterior no requiere elementos adicionales como la función personalizada y la clase (HttpRequest y Alert se proporcionan de forma predeterminada por el marco), y aunque el segmento de código es largo, es sencillo y no complejo (es largo solo porque hay paquetes de configuraciones como url, tamaño de fuente ...), y el segmento de código tiene pequeñas variaciones entre clases (por ejemplo: url, datos de solicitud, casos de manejo de código de error, manejo normal casos...)

Mi pregunta es, ¿es aceptable copiar y pegar código largo pero directo en lugar de envolverlos en una función para reducir la dependencia del código?

ggrr
fuente
89
Imagine que tiene un error en ese código, como no liberar objetos que asigna. (¿Su marco libera los Alertobjetos?) Ahora imagine que tiene que encontrar cada instancia copiada de este código para corregir el error. Ahora imagine que no es usted quien tiene que hacerlo, sino un asesino de hachas loco que sabe que usted fue quien creó todas estas copias en primer lugar.
Sebastian Redl
8
Y, por cierto, mezclar redes y visualización de errores en un solo lugar ya es un gran no-no, en mi humilde opinión.
sleske
11
No nunca. Completamente inaceptable. Si estuvieras en mi proyecto, ya no estarías en mi proyecto y estarías en un programa de capacitación o PIP.
nhgrif
10
Y aparece el supervisor que dice: "Este cuadro de alerta en el medio de mi pantalla es un terror absoluto. Estoy mirando jifs de gatos y la ventana emergente bloquea mi vista cada vez que aparece. Por favor, muévala a la esquina superior derecha ". 3 semanas después "¡¿Qué diablos hiciste ?! Ya no puedo cerrar mis jifs de gato porque TU ventana emergente está cubriendo la X en la esquina superior derecha, arréglalo".
MonkeyZeus
11
Creo que todos aquí parecen pensar que es una mala idea. Pero para cambiar la pregunta, ¿por qué NO pondrías este código en una clase o función separada?
Karl Gjertsen

Respuestas:

87

Debe considerar el costo del cambio. ¿Qué pasaría si quisieras cambiar cómo se hacen las conexiones? ¿Qué tan fácil sería? Si tiene una gran cantidad de código duplicado, encontrar todos los lugares que deben cambiarse puede llevar mucho tiempo y ser propenso a errores.

También debe tener en cuenta la claridad. Lo más probable es que tener que mirar 30 líneas de código no sea tan fácil de entender como una sola llamada a una función "connectToInternet". ¿Cuánto tiempo se perderá tratando de entender el código cuando sea necesario agregar una nueva funcionalidad?

Hay ciertos casos raros donde la duplicación no es un problema. Por ejemplo, si está haciendo un experimento y el código será desechado al final del día. Pero, en general, el costo de la duplicación supera el pequeño ahorro de tiempo de no tener que extraer el código en una función separada .

Consulte también /software//a/103235/63172

Vaughn Cato
fuente
19
... y quién sabe si estas 30 líneas son realmente las mismas que las que miró anteriormente o si alguien cambió a un puerto o dirección IP diferente en su copia por cualquier razón. La suposición implícita de que " cualquier grupo de 30 líneas que empiecen con el HttpRequestson todas iguales " es una falacia fácil de hacer.
nulo el
@null Excelente punto. Una vez trabajé en el código donde había conexiones de conexión a la base de datos copiadas y pegadas, pero algunas tenían diferencias sutiles en la configuración. No tenía idea si estos eran cambios importantes, deliberados, o simplemente diferencias aleatorias
user949300
54

No.

De hecho, incluso su código "simple" debe dividirse en partes más pequeñas. Al menos dos.

Uno para hacer la conexión y manejar la respuesta normal 200. Por ejemplo, ¿qué sucede si cambia de POST a PUT en algunos casos? ¿Qué sucede si está haciendo miles de millones de estas conexiones y necesita un subproceso múltiple o agrupación de conexiones? Tener el código en un solo lugar, con un argumento para el método, lo hará mucho más fácil.

Del mismo modo, otro para manejar errores. Por ejemplo, si cambia el color o el tamaño de fuente de la alerta. O tiene problemas con las conexiones intermitentes y desea registrar los errores.

user949300
fuente
También podría citar SRP: tener un bloque de código que tiene un solo propósito hace que sea mucho más fácil de entender y mantener ...
Roland Tepp
Este es un caso donde DRY y SRP realmente se alinean. A veces no lo hacen.
user949300
18

¿Es aceptable copiar y pegar ...

No.

Para mí, el argumento decisivo es este:

... se usa comúnmente ...

Si usa un código en más de un lugar, cuando cambia, debe cambiarlo en más de un lugar o comienza a tener inconsistencias: comienzan a ocurrir "cosas extrañas" (es decir, introduce errores).

es sencillo y no complejo ...

Y así debería ser más fácil refactorizar en una función.

... hay paquetes de configuraciones como url, tamaño de fuente ...

¿Y qué les gusta cambiar a los usuarios ? Fuentes, tamaños de fuente, colores, etc., etc.

Ahora; ¿en cuántos lugares tendrá que cambiar ese mismo código para que vuelvan a tener el mismo color / fuente / tamaño? (Respuesta recomendada: solo una ).

... el segmento de código tiene pequeñas variaciones entre clases (por ejemplo: url, datos de solicitud, casos de manejo de código de error, casos de manejo normal ...)

Variación => parámetro (s) de función.

Phill W.
fuente
"¿Y qué les gusta cambiar a los usuarios? Fuentes, tamaños de fuente, colores, etc.". ¿Realmente quieres cambiar eso en docenas de ubicaciones?
Dan
8

Esto realmente no tiene nada que ver con copiar y pegar. Si toma el código de otra parte, en el segundo que toma el código es su código y su responsabilidad, por lo que si lo copia o escribe completamente usted mismo no hace la diferencia.

En sus alertas, toma algunas decisiones de diseño. Lo más probable es que se tomen decisiones de diseño similares para todas las alertas. Por lo tanto, es probable que deba tener un método en algún lugar "ShowAlertInAStyleSuitableForMyApplication" o tal vez un poco más corto, y eso debería llamarse.

Tendrá muchas solicitudes http con un manejo de errores similar. Probablemente no debería duplicar el manejo de errores una y otra vez, sino extraer un manejo de errores común. Especialmente si su manejo de errores se vuelve un poco más elaborado (qué pasa con los errores de tiempo de espera, 401, etc.).

gnasher729
fuente
6

La duplicación está bien en algunas circunstancias. Pero no en este. Ese método es demasiado complejo. Hay un límite inferior, cuando la duplicación es más fácil que "factorizar" un método.

Por ejemplo:

def add(a, b)
    return a + b
end

es estúpido, solo haz a + b.

Pero cuando te vuelves un poco, un poco más complejo, entonces usualmente estás muy por encima de la línea.

foo.a + foo.b

debe convertirse

foo.total
def foo
    ...
    def total
        return self.a + self.b
    end
end

En su caso, veo cuatro "métodos". Probablemente en diferentes clases. Uno para realizar la solicitud, otro para obtener la respuesta, otro para mostrar errores y algún tipo de devolución de llamada para que se llame después de que la respuesta regrese para procesar la respuesta. Es probable que yo personalmente agregue una especie de "envoltorio" además de eso para facilitar las llamadas.

Al final, para hacer una solicitud web, me gustaría que una llamada se vea así:

Web.Post(URI, Params, ResponseHandler);

Esa línea es la que tendría en todo mi código. Luego, cuando necesitaba hacer cambios en "cómo obtengo las cosas", podía hacerlo rápidamente, con mucho menos esfuerzo.

Esto también mantiene el código SECO y ayuda con SRP .

coteyr
fuente
0

En un proyecto de cualquier tamaño / complejidad, quiero poder encontrar código cuando lo necesito para los siguientes propósitos:

  1. Arreglarlo cuando está roto
  2. Cambiar la funcionalidad
  3. Reutilízalo.

¿No sería maravilloso unirse a un proyecto en progreso o continuar trabajando en un proyecto durante varios años y cuando una nueva solicitud para "conectarse a Internet y mostrar resultados de conexión" fuera fácil de encontrar debido a que buen diseño en lugar de confiar en hacer una búsqueda en todo el código para una httprequest? Probablemente sea más fácil encontrarlo con Google de todos modos.

No se preocupe, soy el nuevo y refactorizaré este bloque de código porque estoy muy molesto por unirme a este equipo despistado con la base de código horrible o ahora estoy bajo mucha presión como el resto de usted y simplemente lo copiará y pegará. Al menos mantendrá al jefe alejado de mí. Luego, cuando el proyecto se identifique realmente como un desastre, seré el primero en recomendar que lo reescribamos copiando y pegando la versión en el último y mejor marco que ninguno de nosotros comprende.

JeffO
fuente
0

Una clase y / o una función es mejor, al menos en mi opinión. Por una vez, hace que el archivo sea más pequeño, lo que es una gran ganancia si maneja aplicaciones web o aplicaciones para dispositivos con poco almacenamiento (IoT, teléfonos más antiguos, etc.)

Y, obviamente, el mejor punto es que si tiene algo que cambiar debido a nuevos protocolos, etc., simplemente cambia el contenido de la función y no innumerables veces pone esta función en algún lugar que incluso podría estar en diferentes archivos, lo que hace que sea aún más difícil encontrar y cambiar

Escribí un intérprete SQL completo para poder cambiar mejor de MySQL a MySQLi en PHP, porque solo tengo que cambiar mi intérprete y todo funciona, aunque ese es un ejemplo un poco extremo.

Mi1
fuente
-1

Para decidir si un código debe duplicarse o moverse a una función que se llama dos veces, intente determinar cuál es más probable:

  1. Será necesario cambiar ambos usos del código de la misma manera.

  2. Será necesario cambiar al menos un uso del código para que difieran.

En el primer caso, probablemente será mejor que una función maneje ambos usos; en este último caso, probablemente sea mejor tener un código separado para los dos usos.

Al decidir si un código que se usará una vez debe escribirse en línea o extraerse en otra función, descubra cómo describiría completamente el comportamiento requerido de la función. Si una descripción completa y precisa del comportamiento requerido de la función fuera tan larga o más larga que el código en sí, mover el código a una función separada puede hacer que las cosas sean más difíciles de entender en lugar de ser más fáciles. Puede que valga la pena hacerlo si existe una alta probabilidad de que una segunda persona que llama necesitará usar la misma función, y cualquier cambio futuro a la función tendrá que afectar a ambas personas, pero en ausencia de tal consideración, la legibilidad favorecería dividir las cosas. al nivel donde el código y una descripción de su comportamiento requerido serían aproximadamente igual de largos.

Super gato
fuente