Programación para uso futuro de interfaces

41

Tengo un colega sentado a mi lado que diseñó una interfaz como esta:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

El problema es que, en este momento, no estamos usando este parámetro "final" en ningún lugar de nuestro código, simplemente está ahí porque es posible que tengamos que usarlo en el futuro.

Estamos tratando de convencerlo de que es una mala idea poner parámetros en interfaces que no son de utilidad en este momento, pero sigue insistiendo en que habrá que hacer mucho trabajo si implementamos el uso de "end". Fechará algún tiempo después y tendrá que adaptar todo el código.

Ahora, mi pregunta es, ¿existen fuentes que manejen un tema como este de gurús de codificación "respetados" con los que podemos vincularlo?

    
pregunta sveri 11.07.2014 - 09:58

9 respuestas

61

Invítelo a aprender sobre YAGNI . La parte Racional de la página de Wikipedia puede ser particularmente interesante aquí:

  

Según quienes defienden el enfoque de YAGNI, la tentación de escribir código que no es necesario en este momento, pero que podría estar en el futuro, tiene las siguientes desventajas:

     
  • El tiempo dedicado se toma al agregar, probar o mejorar la funcionalidad necesaria.
  •   
  • Las nuevas funciones se deben depurar, documentar y admitir.
  •   
  • Cualquier nueva característica impone restricciones sobre lo que se puede hacer en el futuro, por lo que una característica innecesaria puede impedir que se agreguen las características necesarias en el futuro.
  •   
  • Hasta que la función sea realmente necesaria, es difícil definir completamente lo que debería hacer y probarlo. Si la nueva característica no está definida y probada correctamente, es posible que no funcione correctamente, incluso si finalmente se necesita.
  •   
  • Conduce al código inflado; el software se vuelve más grande y más complicado.
  •   
  • A menos que haya especificaciones y algún tipo de control de revisión, la función puede no ser conocida por los programadores que podrían usarla.
  •   
  • Agregar la nueva característica puede sugerir otras nuevas características. Si estas nuevas características también se implementan, esto podría resultar en un efecto de bola de nieve hacia el avance de características.
  •   

Otros posibles argumentos:

  • "El 80% del costo de por vida de una pieza de software se destina a mantenimiento" . Escribir el código justo a tiempo reduce el costo del mantenimiento: uno tiene que mantener menos código y puede concentrarse en el código que realmente se necesita.

  • El código fuente se escribe una vez, pero se lee docenas de veces. Un argumento adicional, que no se usa en ninguna parte, llevaría a una pérdida de tiempo al comprender por qué hay un argumento que no es necesario. Dado que se trata de una interfaz con varias implementaciones posibles, las cosas solo son más difíciles.

  • Se espera que el código fuente sea auto-documentado. La firma real es engañosa, ya que un lector pensaría que end afecta el resultado o la ejecución del método.

  • Las personas que escriben implementaciones concretas de esta interfaz pueden no entender que el último argumento no debería usarse, lo que llevaría a diferentes enfoques:

    1. No necesito end , así que simplemente ignoraré su valor,

    2. No necesito end , así que lanzaré una excepción si no es null ,

    3. No necesito end , pero intentaré usarlo de alguna manera,

    4. Escribiré un montón de código que podría usarse más adelante cuando se necesite end .

Pero tenga en cuenta que su colega puede tener razón.

Todos los puntos anteriores se basan en el hecho de que la refactorización es fácil, por lo que agregar un argumento más adelante no requerirá mucho esfuerzo. Pero esta es una interfaz, y como interfaz, puede ser utilizada por varios equipos que contribuyen a otras partes de su producto. Esto significa que cambiar una interfaz podría ser particularmente doloroso, en cuyo caso, YAGNI realmente no se aplica aquí.

La respuesta de hjk ofrece una buena solución: agregar un método a una interfaz ya utilizada no es particularmente difícil , pero en algunos casos, también tiene un costo sustancial:

  • Algunos marcos no admiten sobrecargas. Por ejemplo, y si recuerdo bien (corríjame si me equivoco), el WCF de .NET no admite sobrecargas.

  • Si la interfaz tiene muchas implementaciones concretas, agregar un método a la interfaz requeriría revisar todas las implementaciones y agregar el método allí también.

respondido por el Arseni Mourzenko 11.07.2014 - 11:21
26
  

pero sigue insistiendo en que habrá que hacer mucho trabajo si implementamos el uso de la fecha "final" algún tiempo después y tenemos que adaptar todo el código.

(algún tiempo después)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

Eso es todo lo que necesitas, sobrecarga de métodos. El método adicional en el futuro se puede introducir de manera transparente sin afectar las llamadas al método existente.

    
respondido por el h.j.k. 11.07.2014 - 11:35
18
  

[¿Hay] gurús de codificación "respetados" con los que podemos vincularlo [para persuadirlo]?

Las apelaciones a la autoridad no son particularmente convincentes; mejor presentar un argumento que sea válido sin importar quién lo dijo.

Aquí hay un reductio ad absurdum que debe persuadir o demostrar que su compañero de trabajo está estancado en ser "correcto" independientemente de la sensibilidad:

Lo que realmente necesitas es

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

Nunca se sabe cuándo tendrás que internacionalizar para Klingon, por lo que es mejor que te encargues ahora porque se necesitará mucho trabajo para modernizar y Klingons no es conocido por su paciencia.

    
respondido por el msw 11.07.2014 - 10:52
12

Desde la perspectiva de la ingeniería de software, creo que la solución adecuada para este tipo de problemas está en el patrón del constructor. Este es definitivamente un enlace de los autores de 'guru' para su colega enlace .

En el patrón de creación, el usuario crea un objeto que contiene los parámetros. Este contenedor de parámetros se pasará al método. Esto se encargará de cualquier extensión y sobrecarga de los parámetros que su colega necesitaría en el futuro, al tiempo que hace que todo sea muy estable cuando es necesario realizar cambios.

Tu ejemplo será:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}
    
respondido por el InformedA 11.07.2014 - 13:15
7

No lo necesita ahora, así que no lo agregue. Si lo necesita más tarde, amplíe la interfaz:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}
    
respondido por el ToddR 11.07.2014 - 18:38
4

Ningún principio de diseño es absoluto, por lo que aunque estoy más de acuerdo con las otras respuestas, pensé que sería un defensor del diablo y discutiré algunas condiciones en las que consideraría aceptar la solución de su colega:

  • Si se trata de una API pública, y anticipa que la característica será útil para los desarrolladores externos, incluso si no la usa internamente.
  • Si tiene considerables beneficios inmediatos, no solo futuros. Si la fecha de finalización implícita es Now() , al agregar un parámetro se elimina un efecto secundario, que tiene beneficios para el almacenamiento en caché y las pruebas de unidad. Tal vez permita una implementación más sencilla. O tal vez sea más consistente con otras API en su código.
  • Si tu cultura de desarrollo tiene una historia problemática. Si los procesos o la territorialidad hacen que sea demasiado difícil cambiar algo central como una interfaz, entonces lo que he visto antes es gente que implementa soluciones alternativas en el lado del cliente en lugar de cambiar la interfaz, entonces está intentando mantener una docena de fechas de finalización ad hoc Filtros en lugar de uno. Si ese tipo de cosas sucede mucho en su empresa, tiene sentido poner un poco más de esfuerzo en las pruebas futuras. No me malinterpretes, es mejor cambiar tus procesos de desarrollo, pero esto suele ser más fácil decirlo que hacerlo.

Dicho esto, en mi experiencia, el mayor corolario de YAGNI es YDKWFYN: no sabes qué formulario necesitarás (sí, acabo de hacer ese acrónimo). Incluso si la necesidad de algún parámetro límite puede ser relativamente predecible, podría tomar la forma de un límite de página, de días o de una especificación booleana para usar la fecha final de una tabla de preferencias del usuario, o cualquier otro. cantidad de cosas

Como todavía no tiene el requisito, no tiene forma de saber qué tipo de parámetro debería ser. A menudo, terminas teniendo una interfaz incómoda que no se adapta mejor a tus necesidades o tienes que cambiarla de todas formas.

    
respondido por el Karl Bielefeldt 11.07.2014 - 18:54
2

No hay suficiente información para responder esta pregunta. Depende de lo que getFooList realmente haga y de cómo lo haga.

Este es un ejemplo obvio de un método que debería admitir un parámetro adicional, usado o no.

void CapitalizeSubstring (String capitalize_me, int start_index);

La implementación de un método que funciona en una colección en la que puedes especificar el inicio pero no el final de la colección suele ser una tontería.

Realmente tiene que ver el problema en sí mismo y preguntar si el parámetro no tiene sentido en el contexto de toda la interfaz, y realmente cuánto carga impone el parámetro adicional.

    
respondido por el QuestionC 11.07.2014 - 23:22
1

Me temo que tu colega puede tener un punto muy válido. Aunque su solución en realidad no es la mejor.

Desde su interfaz propuesta está claro que

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

está devolviendo instancias encontradas dentro de un intervalo en el tiempo. Si los clientes actualmente no usan el parámetro final, eso no cambia el hecho de que esperan instancias encontradas dentro de un intervalo de tiempo. Solo significa que actualmente todos los clientes usan intervalos abiertos (desde el inicio hasta la eternidad)

Entonces, una mejor interfaz sería:

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

Si proporciona un intervalo con un método de fábrica estático:

public static Interval startingAt(Date start) { return new Interval(start, null); }

entonces los clientes ni siquiera sentirán la necesidad de especificar una hora de finalización.

Al mismo tiempo, su interfaz transmite más correctamente lo que hace, ya que getFooList(String, Date) no comunica que se trata de un intervalo.

Tenga en cuenta que mi sugerencia se deriva de lo que hace actualmente el método, no de lo que debería o podría hacer en el futuro, y como tal, el principio YAGNI (que es muy válido de hecho) no se aplica aquí.

    
respondido por el bowmore 16.07.2014 - 01:36
0

Agregar un parámetro no utilizado es confuso. La gente podría llamar a ese método suponiendo que esta característica funcionará.

No lo añadiría. Es trivial agregarlo luego usando una refactorización y arreglando mecánicamente los sitios de llamadas. En un lenguaje estático, esto es fácil de hacer. No es necesario agregarlo con entusiasmo.

Si hay es una razón para tener ese parámetro, puede evitar la confusión: agregue una aserción en la implementación de esa interfaz para exigir que este parámetro se pase como valor predeterminado. Si alguien usa ese parámetro accidentalmente, al menos notará inmediatamente durante la prueba que esta función no está implementada. Esto elimina el riesgo de que un error pase a la producción.

    
respondido por el usr 12.07.2014 - 00:07

Lea otras preguntas en las etiquetas