¿Qué sientes que es sobre generalización?

Habiendo pasado un tiempo jugando en Haskell y otros lenguajes funcionales, he llegado a apreciar la simplicidad del diseño que viene de describir los problemas en términos generales. Si bien muchos aspectos de la progtwigción de plantillas pueden estar lejos de ser simples, algunos usos son tan comunes que no creo que sean un impedimento para la claridad (especialmente las plantillas de funciones). Encuentro que las plantillas a menudo pueden simplificar el diseño actual mientras agregan automáticamente un poco de resistencia futura. ¿Por qué su funcionalidad debe ser relegada a los escritores de la biblioteca?

Por otro lado, algunas personas parecen evitar las plantillas como la plaga. Pude entender esto hace una década, cuando el concepto mismo de tipos generics era ajeno a gran parte de la comunidad de progtwigción. Pero ahora todos los lenguajes OO populares de tipo estático admiten generics de una forma u otra. La familiaridad añadida parece justificar un ajuste de las actitudes conservadoras.

Una de esas actitudes conservadoras me fue expresada recientemente:

Nunca debe hacer nada más general de lo necesario: regla básica del desarrollo de software.

Honestamente, me sorprendió ver esto declarado de manera tan despectiva como si hubiera sido evidente. Personalmente lo encuentro lejos de ser evidente, con lenguajes como Haskell donde todo es genérico a menos que especifique lo contrario. Dicho esto, creo que entiendo de dónde viene este punto de vista.

En el fondo de mi mente, tengo algo así como esa regla traqueteando. Ahora que está a la vanguardia, me doy cuenta de que siempre lo he interpretado a la luz de la architecture en general. Por ejemplo, si tiene una clase, no desea cargarla con toneladas de funciones que podría usar algún día. No se moleste en hacer interfaces si solo necesita una versión concreta (aunque la simulación puede ser un argumento contrario a esta). Ese tipo de cosas…

Lo que no hago, sin embargo, es aplicar este principio en el nivel micro. Si tengo una pequeña función de utilidad que no tiene ninguna razón para depender de ningún tipo en particular, haré una plantilla.

Entonces, ¿qué piensas, así que? ¿Qué consideras que es sobre generalizar? ¿Esta regla tiene una aplicabilidad diferente dependiendo del contexto? ¿Estás de acuerdo en que esto es una regla?

Sobre generalizar me vuelve loco. No me asustan las plantillas (ni mucho menos) y me gustan las soluciones generales. Pero también me gusta resolver el problema por el cual el cliente está pagando. Si es un proyecto de una semana, ¿por qué ahora estoy financiando un extravagancia de un mes que continuará trabajando no solo a través de posibles cambios futuros obvios como nuevos impuestos, sino también a través del descubrimiento de nuevas lunas o la vida en Marte?

Al volver a colocar esto en las plantillas, el cliente solicita alguna capacidad que implique que escriba una función que tome una cadena y un número. Me da una solución progtwigda que toma dos tipos y hace lo correcto para mi caso específico y algo que podría o no estar bien (debido a la ausencia de requisitos) en el rest de los casos, y no lo agradeceré. Estaré tentado de que, además de pagarle, tengo que pagarle a alguien para que lo pruebe, a alguien que lo documente y a alguien que trabaje dentro de sus limitaciones en el futuro si se presenta un caso más general.

Por supuesto, no toda generalización es sobre generalización. Todo debería ser lo más simple posible, pero no más simple. Tan general como sea necesario, pero no más general. Tan probado como podemos permitirnos, pero no más probado. Etc. También, “predice lo que podría cambiar y encapsularlo”. Todas estas reglas son simples, pero no fáciles. Es por eso que la sabiduría importa en los desarrolladores y en quienes los administran.

Si puede hacerlo al mismo tiempo, y el código es al menos tan claro, la generalización siempre es mejor que la especialización.

Hay un principio que las personas de XP siguen llamado YAGNI: No lo vas a necesitar.

El wiki tiene esto para decir:

Incluso si está totalmente, totalmente seguro de que necesitará una característica más adelante, no la implemente ahora. Por lo general, resultará que: a) no lo necesita después de todo, o b) lo que realmente necesita es muy diferente de lo que previó antes.

Esto no significa que deba evitar incluir flexibilidad en su código. Significa que no debe sobrepasar algo sobre la base de lo que cree que podría necesitar más adelante .

¿Demasiado genérico? Debo admitir que soy un fanático de la Progtwigción Genérica (como un principio) y me gusta mucho la idea que Haskell y Go están usando allí.

Al progtwigr en C ++, sin embargo, se le ofrecen dos formas de alcanzar objectives similares:

  • Progtwigción genérica: por medio de plantillas, a pesar de que existen problemas con el tiempo de comstackción, la dependencia de la implementación, etc.
  • Progtwigción orientada a objetos: su antecesor de una manera, que coloca el problema en el objeto en sí (clase / estructura) en lugar de en la función …

Ahora, cuando usar? Es una pregunta difícil, seguro. La mayoría de las veces no es mucho más que un presentimiento, y ciertamente he visto el abuso de cualquiera de los dos.

Por experiencia diría que cuanto más pequeña es una función / clase, más básico es su objective, más fácil es generalizar. Como ejemplo, llevo una caja de herramientas en la mayoría de mis proyectos favoritos y en el trabajo. La mayoría de las funciones / clases son genéricas … un poco como Boost en cierto modo;)

// No container implements this, it's easy... but better write it only once! template  void erase_if(Container& c, Pred p) { c.erase(std::remove_if(c.begin(), c.end(), p), c.end()); } // Same as STL algo, but with precondition validation in debug mode template  Iterator lower_bound(Container& c, typename Container::value_type const& v) { ASSERT(is_sorted(c)); return std::lower_bound(c.begin(), c.end(), v); } 

Por otro lado, cuanto más te acerques al trabajo específico de la empresa, menos probabilidades tienes de ser genérico.

Es por eso que yo mismo aprecio el principio del menor esfuerzo. Cuando estoy pensando en una clase o método, primero paso un paso atrás y pienso un poco:

  • ¿Tendría sentido que fuera más genérico?
  • ¿Cuál sería el costo?

Dependiendo de las respuestas, adapto el grado de genérico y lucho para evitar el locking prematuro , es decir, evito usar una forma no genérica suficiente cuando no cuesta mucho usar una ligeramente más genérica.

Ejemplo:

 void Foo::print() { std::cout << /* some stuff */ << '\n'; } // VS std::ostream& operator<<(std::ostream& out, Foo const& foo) { return out << /* some stuff */ << '\n'; } 

No solo es más genérico (puedo especificar dónde realizar la salida), sino que también es más idiomático.

Algo está demasiado generalizado cuando se está perdiendo el tiempo generalizándolo. Si va a utilizar las funciones genéricas en el futuro, entonces probablemente no esté perdiendo el tiempo. Es realmente tan simple [en mi mente].

Una cosa a tener en cuenta es que la generalización de su software no es necesariamente una mejora si también lo hace más confuso. A menudo hay un intercambio.

Creo que deberías considerar dos principios básicos de progtwigción: KISS (que sea sencillo y directo) y DRY (no te repitas). La mayoría de las veces comienzo con lo primero: implementar la funcionalidad necesaria de la manera más sencilla y directa. Muy a menudo es suficiente, porque ya puede satisfacer mis necesidades. En este caso sigue siendo simple (y no genérico).

Cuando la segunda vez (o la tercera máxima) necesito algo similar, trato de generalizar el problema (función, clase, diseño, lo que sea) basado en los ejemplos concretos de la vida real -> es poco probable que haga la generalización solo por sí mismo. Siguiente problema similar: si se ajusta a la imagen actual con elegancia, bien, puedo resolverlo fácilmente. De lo contrario, verifico si la solución actual se puede generalizar aún más (sin hacerla demasiado complicada / no tan elegante).

Creo que deberías hacer algo similar, incluso si sabes de antemano que necesitarás una solución general: toma algunos ejemplos concretos y haz la generalización basada en ellos. De lo contrario, es demasiado fácil encontrarse con callejones sin salida donde tiene una solución general “agradable”, pero no es útil para resolver los problemas reales.

Sin embargo, puede haber algunos casos excepcionales a esto.
a) Cuando una solución general es casi exactamente el mismo esfuerzo y complejidad. Ejemplo: escribir una implementación de cola usando generics no es mucho más complicado que hacer lo mismo solo para cadenas.
b) Si es más fácil resolver el problema de manera general, y también la solución es más fácil de entender. No sucede con demasiada frecuencia, no puedo dar un ejemplo simple y real de esto en este momento :-(. Pero incluso en este caso, tener / analizar ejemplos concretos anteriormente es una OMI obligatoria, ya que solo puede confirmar que estás en el camino correcto

Se puede decir que la experiencia puede superar el requisito previo de tener problemas concretos, pero creo que en este caso, la experiencia significa que ya ha visto y pensado acerca de problemas y soluciones similares y similares.

Si tiene algo de tiempo, puede consultar Estructura e interpretación de progtwigs de computadora . Tiene muchas cosas interesantes sobre cómo encontrar el equilibrio correcto entre genérico y complejidad, y cómo mantener la complejidad al mínimo que realmente requiere su problema.

Y, por supuesto, los diversos procesos ágiles también recomiendan algo similar: comience con el refactor simple cuando sea necesario.

Para mí, sobre generalización es, si es necesario romper la abstracción en otros pasos. Ejemplo dentro del proyecto, vivo en:

 Object saveOrUpdate(Object object) 

Este método es demasiado genérico, porque se proporciona al cliente dentro de una architecture de 3 niveles, por lo que debe verificar el objeto guardado en el servidor sin un contexto.

Hay 2 ejemplos de Microsoft en generalización general:
1.) CObject (MFC)
2.) Objeto (.Net)

ambos se utilizan para “realizar” generics en c ++ que la mayoría de las personas no utilizan. De hecho, todos hicieron una comprobación de tipo en el parámetro dado usando estos (objeto / objeto) ~