Nuestra base de código es antigua y los nuevos programadores, como yo, aprenden rápidamente a hacerlo de la misma manera en aras de la uniformidad. Pensando que tenemos que comenzar en alguna parte, me encargué de refactorizar una clase de titular de datos como tal:
- Se eliminaron los métodos de establecimiento y se hicieron todos los campos
final
(tomo "final
es bueno" axiomáticamente). Los setters se usaron solo en el constructor, como resultado, por lo que esto no tuvo efectos secundarios. - Introdujo una clase de constructor
La clase Builder era necesaria porque el constructor (que es lo que provocó la refactorización en primer lugar) abarca aproximadamente 3 líneas de código. Tiene muchos parámetros.
Por suerte, un compañero de equipo mío estaba trabajando en otro módulo y necesitó los setters, porque los valores que requería estaban disponibles en diferentes puntos del flujo. Por lo tanto, el código se veía así:
public void foo(Bar bar){
//do stuff
bar.setA(stuff);
//do more stuff
bar.setB(moreStuff);
}
Argumenté que debería usar el generador en su lugar, porque deshacerse de los establecedores permite que los campos permanezcan inmutables (ya me han escuchado hablar sobre la inmutabilidad antes), y también porque los constructores permiten que la creación de objetos sea transaccional. Dibujé el siguiente pseudocódigo:
public void foo(Bar bar){
try{
bar.setA(a);
//enter exception-throwing stuff
bar.setB(b);
}catch(){}
}
Si se dispara esa excepción, bar
habrá datos corruptos, que se habrían evitado con un generador:
public Bar foo(){
Builder builder=new Builder();
try{
builder.setA(a);
//dangerous stuff;
builder.setB(b);
//more dangerous stuff
builder.setC(c);
return builder.build();
}catch(){}
return null;
}
Mis compañeros de equipo respondieron que la excepción en cuestión nunca se disparará, lo cual es lo suficientemente justo para esa área de código en particular, pero creo que falta el bosque para el árbol.
El compromiso era volver a la solución anterior, es decir, usar un constructor sin parámetros y configurar todo con los establecedores según sea necesario. La razón era que esta solución sigue el principio KISS, que el mío viola.
Soy nuevo en esta empresa (menos de 6 meses) y estoy completamente consciente de que perdí esta. Las preguntas que tengo son:
- ¿Hay otro argumento para usar Builders en lugar de la "vieja forma"?
- ¿Vale la pena el cambio que propongo?
pero en serio,
- ¿Tiene algún consejo para presentar mejor tales argumentos cuando aboga por probar algo nuevo?
setA
?Respuestas:
Aquí es donde salió mal: realizó un cambio arquitectónico importante en la base de código de modo que se volvió muy diferente de lo que se esperaba. Sorprendiste a tus colegas. La sorpresa solo es buena si se trata de una fiesta, el principio de menos sorpresa es dorado (incluso si se trata de fiestas, ¡entonces sabría usar calzoncillos limpios!)
Ahora, si pensabas que este cambio valía la pena, habría sido mucho mejor esbozar el pseudocódigo que muestra el cambio y presentarlo a tus colegas (o al menos a quien tenga el rol más cercano al arquitecto) y ver qué pensaban antes de hacer algo. Tal como está, te volviste pícaro, pensaste que toda la base de código era tuya para jugar como creías conveniente. ¡Un jugador del equipo, no un jugador de equipo!
Puede que sea un poco duro contigo, pero he estado en el lugar opuesto: mira un código y piensa "¡¿WTF sucedió aquí ?!", solo para encontrar que el chico nuevo (casi siempre es el chico nuevo) decidió cambiar un montón de cosas basadas en lo que él piensa que debería ser la "forma correcta". Tornillos con el historial de SCM también, que es otro problema importante.
fuente
Como se describe, no veo qué hace que el código requiera establecedores . Probablemente no sé lo suficiente sobre el caso de uso, pero ¿por qué no esperar hasta que se conozcan todos los parámetros antes de crear una instancia del objeto?
Como alternativa, si la situación requiere establecedores: ofrezca una forma de congelar su código para que los colocadores arrojen un error de aserción (también conocido como error del programador) si intenta modificar los campos después de que el objeto esté listo.
Creo que eliminar los setters y usar final es la forma "adecuada" de hacerlo, así como forzar la inmutabilidad, pero ¿es realmente con lo que deberías pasar tu tiempo?
Consejos generales
Viejo = malo, nuevo = bueno, a mi manera, a tu manera ... este tipo de discusión no es constructiva.
Actualmente, la forma en que se usa su objeto sigue una regla implícita. Esto es parte de la especificación no escrita de su código y su equipo podría pensar que no hay mucho riesgo para otras partes del código de usarlo incorrectamente. Lo que quiere hacer aquí es hacer que la regla sea más explícita con un generador y comprobaciones en tiempo de compilación.
De alguna manera, la refactorización del código está vinculada a la teoría de la Ventana Rota : le parece correcto solucionar cualquier problema tal como lo ve (o tomar nota para alguna reunión posterior de "mejora de la calidad") para que el código siempre parezca agradable de trabajar, Es seguro y limpio. Sin embargo, usted y su equipo no tienen la misma idea sobre lo que es "suficientemente bueno". Lo que ve como una oportunidad para contribuir y mejorar el código, de buena fe, probablemente se ve de manera diferente al equipo existente.
Por cierto, no debe suponerse que su equipo sufre necesariamente de inercia, malos hábitos, falta de educación, ... lo peor que puede hacer es proyectar una imagen de un sabelotodo que esté allí para mostrarles Cómo codificar. Por ejemplo, la inmutabilidad no es algo nuevo , sus pares probablemente lo hayan leído, pero el hecho de que este tema se esté volviendo popular en los blogs no es suficiente para convencerlos de que dediquen tiempo a cambiar su código.
Después de todo, hay infinitas formas de mejorar una base de código existente, pero cuesta tiempo y dinero. Podría mirar tu código, llamarlo "viejo" y encontrar cosas sobre las que jugar. Por ejemplo: sin especificación formal, no hay suficientes afirmaciones, tal vez no hay suficientes comentarios o pruebas, no hay suficiente cobertura de código, algoritmo no óptimo, demasiado uso de memoria, no usa el "mejor" patrón de diseño posible en cada caso, etc. ad nauseam . Pero para la mayoría de los puntos que plantearía, pensarías: está bien, mi código funciona, no hay necesidad de pasar más tiempo en él.
Tal vez su equipo piense que lo que sugiere es más allá del punto de rendimientos decrecientes. Incluso si hubiera encontrado una instancia real de un error debido al tipo de ejemplo que muestra (con la excepción), probablemente solo lo arreglarían una vez y no querrían refactorizar el código.
Entonces, la pregunta es sobre cómo gastar su tiempo y energía, dado que son limitados . Se realizan cambios continuos en el software, pero generalmente su idea comienza con un puntaje negativo hasta que pueda proporcionar buenos argumentos. Incluso si tiene razón sobre el beneficio, no es un argumento suficiente en sí mismo, porque terminará en la canasta "Es bueno tener, un día ... ".
Cuando presenta sus argumentos, debe hacer algunos controles de "cordura": ¿está tratando de vender la idea o usted mismo? ¿Qué pasa si alguien más le presentó esto al equipo? ¿Encontraría algunos defectos en su razonamiento? ¿Está tratando de aplicar alguna práctica recomendada general o tuvo en cuenta los detalles de su código?
Luego, debe asegurarse de no aparecer como si está tratando de corregir los "errores" pasados de su equipo. Ayuda a demostrar que no es su idea, pero puede recibir comentarios razonablemente. Cuanto más lo haga, más oportunidades tendrá de seguir sus sugerencias.
fuente
Usted no
Si su constructor tiene 3 líneas de parámetros, es demasiado grande y complejo. Ningún patrón de diseño lo arreglará.
Si tiene una clase cuyos datos están disponibles en diferentes momentos, deben ser dos objetos diferentes, o su consumidor debe agregar los componentes y luego construir el objeto. No necesitan un constructor para hacer eso.
fuente
String
sy otras primitivas. No hay lógica en ningún lado, ni siquiera verifica si haynull
s, etc. Por lo tanto, es solo tamaño, no complejidad en sí misma. Pensé que hacer algo asíbuilder.addA(a).addB(b); /*...*/ return builder.addC(c).build();
sería mucho más fácil a la vista.Pareces más concentrado en tener razón que en trabajar bien con los demás. Peor aún, reconoce que lo que está haciendo es una lucha por el poder y, sin embargo, no puede pensar cómo es probable que las personas sientan las cosas cuya experiencia y autoridad desafía.
Invierta eso.
Concéntrese en trabajar con otros. Enmarca tus pensamientos como sugerencias e ideas. Haga que ELLOS hablen de SUS ideas antes de hacer preguntas para que piensen en las suyas. Evite las confrontaciones directas y esté dispuesto a aceptar que continuar haciendo las cosas a la manera del grupo (por ahora) es más productivo que pelear una batalla cuesta arriba para cambiar el grupo. (Aunque traiga las cosas de vuelta.) Haga que trabajar con usted sea una alegría.
Haga esto de manera consistente y debería crear menos conflictos, y ver que otras ideas sean adoptadas por otros. Todos sufrimos la ilusión de que el argumento lógico perfecto sorprenderá a otros y ganará el día. Y si no funciona de esa manera, creemos que debería .
Pero eso está en conflicto directo con la realidad. La mayoría de los argumentos se pierden antes de que se haga el primer punto lógico. Incluso entre personas supuestamente lógicas, la psicología triunfa sobre la razón. Convencer a la gente se trata de superar las barreras psicológicas para ser escuchado adecuadamente, y no de tener una lógica perfecta.
fuente
Frame your thoughts as suggestions and ideas. Get THEM to talk through THEIR ideas before asking questions to make them think about yours
+1 para eso, sin embargo"Cuando tienes un nuevo martillo, todo se verá como un clavo". - Esto es lo que pensé cuando leí tu pregunta.
Si yo fuera su colega, le preguntaría "¿por qué no inicializar todo el objeto en el constructor?" Eso es lo que es su funcionalidad después de todo. ¿Por qué usar el patrón de construcción (o cualquier otro diseño)?
Es difícil saber de ese pequeño fragmento de código. Tal vez lo sea: usted y sus colegas deben evaluarlo.
Sí, aprenda más sobre el tema antes de discutirlo. Ármate con buenos argumentos y razones antes de entrar en la discusión.
fuente
He estado en la situación en la que fui reclutado por mis habilidades. Cuando pude ver el código, bueno ... fue más que horrible. Cuando intentas limpiarlo, alguien que ha estado allí por más tiempo siempre suprime los cambios, porque no ven los beneficios. Suena como algo que estás describiendo. Terminó con mi renuncia a ese puesto y ahora puedo evolucionar mucho mejor en una empresa que tiene una mejor práctica.
No creo que debas actuar junto con los demás en el equipo porque han estado allí más tiempo. Si ve que los miembros de su equipo usan malas prácticas en los proyectos en los que trabaja, es una responsabilidad que tenga que señalarlo, como lo ha hecho. Si no, entonces no eres un recurso muy valioso. Si señala las cosas una y otra vez, pero nadie está escuchando, solicite a la gerencia un reemplazo o cambie de trabajo. Es muy importante que no te permitas adaptarte a las malas prácticas.
A la pregunta real
¿Por qué usar objetos inmutables? Porque sabes con lo que estás lidiando.
Supongamos que tiene una conexión de base de datos que se pasa que le permite cambiar el host a través de un configurador, entonces no sabe qué conexión es. Siendo inmutable, entonces lo sabes.
Sin embargo, supongamos que tiene una estructura de datos que puede recuperarse de la persistencia y luego volver a persistir con datos nuevos, entonces tiene sentido que esta estructura no sea inmutable. Es la misma entidad, pero los valores pueden cambiar. En su lugar, utilice objetos de valor inmutable como argumentos.
Sin embargo, en lo que está centrando la pregunta es en un patrón de generador para deshacerse de las dependencias en el constructor. Digo un gran NO gordo a esto. La instancia es obviamente demasiado compleja ya. ¡No intentes ocultarlo, arréglalo!
Tl; dr
Eliminar los setters puede ser correcto, dependiendo de la clase. El patrón de construcción no está resolviendo el problema, solo lo está ocultando. (La suciedad debajo de la alfombra todavía existe incluso si no puedes verla)
fuente
Si bien todas las demás respuestas son muy útiles (más útiles que las mías), existe una propiedad de los constructores que aún no ha mencionado: al convertir la creación de un objeto en un proceso, puede imponer restricciones lógicas complejas.
Puede ordenar, por ejemplo, que ciertos campos se establezcan juntos o en un orden específico. Incluso puede devolver una implementación diferente del objeto de destino dependiendo de esas decisiones.
Este es un ejemplo indirecto que no tiene mucho sentido, pero demuestra las tres propiedades: el personaje siempre se establece primero, los límites de rango siempre se establecen y validan juntos, y usted elige su implementación en el momento de la construcción.
Es fácil ver cómo esto puede conducir a un código de usuario más legible y confiable, pero como también puede ver, hay un inconveniente: un montón de código de constructor (e incluso omití los constructores para acortar los fragmentos). Entonces intenta calcular la compensación haciendo preguntas como:
Sus colegas simplemente decidieron que la compensación no sería beneficiosa. Debes discutir las razones de manera abierta y honesta, preferiblemente sin recurrir a principios abstractos, sino concentrándote en las implicaciones prácticas de esta solución o aquella.
fuente
Parece que esta clase es en realidad más de una clase, juntas en la misma definición de archivo / clase. Si es un DTO, entonces debería ser posible instanciarlo de una vez y que sea inmutable. Si no utiliza todos sus campos / propiedades en ninguna transacción, es casi seguro que está rompiendo el principio de Responsabilidad Única. Podría ser que dividirlo en clases DTO más pequeñas, más coherentes e inmutables podría ayudar. Considere leer Clean Code si aún no lo ha hecho para poder articular mejor los problemas y los beneficios de hacer un cambio.
No creo que pueda diseñar código a este nivel por comité, a menos que esté literalmente programando en la mafia (no parece que esté en ese tipo de equipo), así que creo que está bien hacer un cambio basado en su mejor juicio, y luego tómalo en la barbilla si resulta que lo juzgaste mal.
Sin embargo, siempre que pueda articular los posibles problemas con el código tal como está, aún puede debatir que se requiere una solución , incluso si la suya no es la aceptada. Las personas son libres de ofrecer alternativas.
" Las opiniones fuertes, las opiniones débiles " es un buen principio: sigue adelante con confianza en función de lo que sabe, pero si encuentra alguna información nueva que lo contrarreste, sea modesto, renuncie y entre en una discusión sobre cuál debería ser la solución correcta ser. La única respuesta incorrecta es dejar que empeore.
fuente