En los últimos años, hemos estado cambiando lentamente a un código progresivamente mejor escrito, unos pocos pasos a la vez. Finalmente estamos comenzando a hacer el cambio a algo que al menos se asemeja a SOLID, pero aún no hemos llegado allí. Desde que realizó el cambio, una de las mayores quejas de los desarrolladores es que no pueden soportar la revisión por pares y atravesar docenas y docenas de archivos donde anteriormente cada tarea solo requería que el desarrollador tocara de 5 a 10 archivos.
Antes de comenzar a hacer el cambio, nuestra arquitectura se organizó de manera muy similar a la siguiente (concedido, con uno o dos órdenes de magnitud más archivos):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
En cuanto al archivo, todo fue increíblemente lineal y compacto. Obviamente, hubo una gran cantidad de duplicación de código, acoplamiento apretado y dolores de cabeza, sin embargo, todos podían atravesarlo y resolverlo. Los principiantes completos, personas que nunca habían abierto Visual Studio, podrían resolverlo en solo unas pocas semanas. La falta de complejidad general del archivo hace que sea relativamente sencillo para los desarrolladores novatos y los nuevos empleados comenzar a contribuir sin demasiado tiempo de aceleración. Pero esto es más o menos donde cualquier beneficio del estilo de código desaparece.
Respaldo de todo corazón todos los intentos que hacemos para mejorar nuestra base de código, pero es muy común obtener un retroceso del resto del equipo en cambios masivos de paradigma como este. Algunos de los mayores puntos conflictivos actualmente son:
- Pruebas unitarias
- Cuenta de clase
- Complejidad de la revisión por pares
Las pruebas unitarias han sido increíblemente difíciles de vender para el equipo, ya que todos creen que es una pérdida de tiempo y que pueden manejar y probar su código mucho más rápido en conjunto que cada pieza individualmente. El uso de pruebas unitarias como un aval para SOLID ha sido en gran parte inútil y se ha convertido en una broma en este punto.
El conteo de clases es probablemente el mayor obstáculo para superar. ¡Las tareas que solían tomar de 5 a 10 archivos ahora pueden tomar de 70 a 100! Si bien cada uno de estos archivos tiene un propósito distinto, el gran volumen de archivos puede ser abrumador. La respuesta del equipo ha sido principalmente gemidos y rascarse la cabeza. Anteriormente, una tarea puede haber requerido uno o dos repositorios, un modelo o dos, una capa lógica y un método de controlador.
Ahora, para crear una aplicación simple para guardar archivos, tiene una clase para verificar si el archivo ya existe, una clase para escribir los metadatos, una clase para abstraer DateTime.Now
y poder inyectar tiempos para pruebas unitarias, interfaces para cada archivo que contiene lógica, archivos para contener pruebas unitarias para cada clase, y uno o más archivos para agregar todo a su contenedor DI.
Para aplicaciones de tamaño pequeño a mediano, SOLID es una venta súper fácil. Todos ven el beneficio y la facilidad de mantenimiento. Sin embargo, simplemente no están viendo una buena propuesta de valor para SOLID en aplicaciones a gran escala. Así que estoy tratando de encontrar formas de mejorar la organización y la gestión para superar los dolores de crecimiento.
Pensé que daría un poco más fuerte de un ejemplo del volumen del archivo basado en una tarea recientemente completada. Me dieron la tarea de implementar alguna funcionalidad en uno de nuestros microservicios más nuevos para recibir una solicitud de sincronización de archivos. Cuando se recibe la solicitud, el servicio realiza una serie de búsquedas y comprobaciones, y finalmente guarda el documento en una unidad de red, así como en 2 tablas de base de datos separadas.
Para guardar el documento en la unidad de red, necesitaba algunas clases específicas:
- IBasePathProvider
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect
- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests
- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider
- NewGuidProviderTests
- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests
- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request)
- PatientFileWriter
- PatientFileWriterTests
Entonces, hay un total de 15 clases (excluyendo POCO y andamios) para realizar un ahorro bastante sencillo. Este número se disparó significativamente cuando necesité crear POCO para representar entidades en unos pocos sistemas, construí algunos repositorios para comunicarme con sistemas de terceros que son incompatibles con nuestros otros ORM y construí métodos lógicos para manejar las complejidades de ciertas operaciones.
Respuestas:
Creo que has entendido mal la idea de una sola responsabilidad. La única responsabilidad de una clase podría ser "guardar un archivo". Para hacer eso, puede dividir esa responsabilidad en un método que verifique si existe un archivo, un método que escribe metadatos, etc. Cada uno de esos métodos tiene una responsabilidad única, que es parte de la responsabilidad general de la clase.
Una clase para abstraer
DateTime.Now
suena bien. Pero solo necesita uno de esos y podría combinarse con otras características del entorno en una sola clase con la responsabilidad de abstraer las características del entorno. Nuevamente, una sola responsabilidad con múltiples sub-responsabilidades.No necesita "interfaces para cada archivo que contenga lógica", necesita interfaces para clases que tienen efectos secundarios, por ejemplo, aquellas clases que leen / escriben en archivos o bases de datos; e incluso entonces, solo son necesarios para las partes públicas de esa funcionalidad. Entonces, por ejemplo
AccountRepo
, es posible que no necesite ninguna interfaz, es posible que solo necesite una interfaz para el acceso real a la base de datos que se inyecta en ese repositorio.Esto sugiere que también ha entendido mal las pruebas unitarias. La "unidad" de una prueba unitaria no es una unidad de código. ¿Qué es incluso una unidad de código? ¿Una clase? ¿Un método? ¿Una variable? ¿Una sola instrucción de máquina? No, la "unidad" se refiere a una unidad de aislamiento, es decir, un código que puede ejecutarse de forma aislada de otras partes del código. Una prueba simple de si una prueba automatizada es una prueba unitaria o no es si puede ejecutarla en paralelo con todas sus otras pruebas unitarias sin afectar su resultado. Hay un par de reglas más generales para las pruebas unitarias, pero esa es su medida clave.
Entonces, si partes de su código se pueden probar como un todo sin afectar otras partes, entonces haga eso.
Siempre sea pragmático y recuerde que todo es un compromiso. Cuanto más se adhiera a DRY, más estrechamente debe estar su código. Cuanto más introduzcas abstracciones, más fácil será probar el código, pero más difícil será de entender. Evite la ideología y encuentre un buen equilibrio entre el ideal y mantenerlo simple. Ahí radica el punto óptimo de máxima eficiencia tanto para el desarrollo como para el mantenimiento.
fuente
Esto es lo opuesto al principio de responsabilidad única (SRP). Para llegar a ese punto, debe haber dividido su funcionalidad de una manera muy fina, pero de eso no se trata el SRP: hacer eso ignora la idea clave de la cohesión .
Según el SRP, el software debe dividirse en módulos a lo largo de líneas definidas por sus posibles razones para cambiar, de modo que se pueda aplicar un solo cambio de diseño en un solo módulo sin requerir modificaciones en otro lugar. Un solo "módulo" en este sentido puede corresponder a más de una clase, pero si un cambio requiere que toque decenas de archivos, entonces es realmente múltiples cambios o está haciendo mal SRP.
Bob Martin, quien originalmente formuló el SRP, escribió una publicación de blog hace unos años para tratar de aclarar la situación. Discute con cierta extensión cuál es una "razón para cambiar" a los efectos del SRP. Vale la pena leerlo en su totalidad, pero entre las cosas que merecen especial atención se encuentra esta redacción alternativa del SRP:
(énfasis mío). El SRP no se trata de dividir las cosas en las piezas más pequeñas posibles. Ese no es un buen diseño, y su equipo tiene razón para resistir. Hace que su base de código sea más difícil de actualizar y mantener. Parece que puede estar tratando de vender a su equipo en base a consideraciones de pruebas unitarias, pero eso sería poner el carro antes que el caballo.
Del mismo modo, el principio de segregación de interfaz no debe tomarse como un absoluto. No es más una razón para dividir su código tan finamente que el SRP, y generalmente se alinea bastante bien con el SRP. Que una interfaz contenga algunos métodos que algunos clientes no usan no es una razón para dividirla. De nuevo estás buscando cohesión.
Además, le insto a que no tome el principio abierto-cerrado o el principio de sustitución de Liskov como una razón para favorecer las jerarquías de herencia profundas. No hay un acoplamiento más apretado que una subclase con sus superclases, y el acoplamiento apretado es un problema de diseño. En cambio, favorezca la composición sobre la herencia donde sea que tenga sentido hacerlo. Esto reducirá su acoplamiento y, por lo tanto, la cantidad de archivos que un cambio en particular puede necesitar tocar, y se alinea muy bien con la inversión de dependencia.
fuente
Esto es una mentira. Las tareas nunca tomaron solo 5-10 archivos.
No está resolviendo ninguna tarea con menos de 10 archivos. ¿Por qué? Porque estás usando C #. C # es un lenguaje de alto nivel. Estás utilizando más de 10 archivos solo para crear hello world.
Oh, seguro que no los notas porque no los escribiste. Entonces no los miras. Confías en ellos
El problema no es la cantidad de archivos. Es que ahora tienes tantas cosas que no confías.
Entonces, descubra cómo hacer que esas pruebas funcionen hasta el punto de que una vez que pasen, confíe en estos archivos de la misma manera que confía en los archivos en .NET. Hacer eso es el punto de las pruebas unitarias. A nadie le importa la cantidad de archivos. Se preocupan por la cantidad de cosas en las que no pueden confiar.
El cambio es difícil en aplicaciones a gran escala, sin importar lo que haga. La mejor sabiduría para aplicar aquí no proviene del tío Bob. Proviene de Michael Feathers en su libro Working Effectively with Legacy Code.
No comience un festival de reescritura. El antiguo código representa el conocimiento ganado con esfuerzo. Lanzarlo porque tiene problemas y no se expresa en un nuevo y mejorado paradigma X es solo pedir un nuevo conjunto de problemas y ningún conocimiento ganado con esfuerzo.
En su lugar, encuentre formas de hacer que su código antiguo no comprobable sea verificable (el código heredado en Feathers habla). En esta metáfora el código es como una camisa. Las piezas grandes se unen en costuras naturales que se pueden deshacer para separar el código de la forma en que las quitaría. Haga esto para permitirle adjuntar "fundas" de prueba que le permitan aislar el resto del código. Ahora, cuando crea las mangas de prueba, tiene confianza en las mangas porque lo hizo con una camisa de trabajo. (Ahora, esta metáfora está empezando a doler).
Esta idea surge del supuesto de que, como en la mayoría de las tiendas, los únicos requisitos actualizados están en el código de trabajo. Esto le permite bloquear eso en pruebas que le permiten realizar cambios en el código de trabajo comprobado sin que pierda cada parte de su estado de trabajo comprobado. Ahora, con esta primera ola de pruebas en su lugar, puede comenzar a hacer cambios que hacen que el código "heredado" (no comprobable) sea verificable. Puede ser audaz porque las pruebas de costuras lo respaldan al decir que esto es lo que siempre hizo y las nuevas pruebas muestran que su código realmente hace lo que cree que hace.
¿Qué tiene que ver todo esto con:
Abstracción.
Puedes hacerme odiar cualquier código base con malas abstracciones. Una mala abstracción es algo que me hace mirar dentro. No me sorprendas cuando miro dentro. Sé más o menos lo que esperaba.
Dame un buen nombre, pruebas legibles (ejemplos) que muestren cómo usar la interfaz y organízala para que pueda encontrar cosas y no me importará si usamos 10, 100 o 1000 archivos.
Me ayudas a encontrar cosas con buenos nombres descriptivos. Poner cosas con buenos nombres en cosas con buenos nombres.
Si hace todo esto correctamente, abstraerá los archivos donde tiene que terminar una tarea, dependiendo de otros 3 a 5 archivos. Los archivos 70-100 todavía están allí. Pero se esconden detrás del 3 al 5. Eso solo funciona si confías en el 3 al 5 para hacerlo bien.
Entonces, lo que realmente necesita es el vocabulario para encontrar buenos nombres para todas estas cosas y pruebas en las que las personas confían para que dejen de leer todo. Sin eso, también me estarías volviendo loco.
@Delioth hace un buen punto sobre los dolores de crecimiento. Cuando estás acostumbrado a que los platos se encuentren en el armario sobre el lavavajillas, es necesario acostumbrarse a que estén encima de la barra de desayuno. Hace algunas cosas más difíciles. Hace algunas cosas más fáciles. Pero causa todo tipo de pesadillas si la gente no está de acuerdo a dónde van los platos. En una base de código grande, el problema es que solo puede mover algunos de los platos a la vez. Así que ahora tienes platos en dos lugares. Es confuso. Hace que sea difícil confiar en que los platos están donde se supone que deben estar. Si quieres superar esto, lo único que debes hacer es seguir moviendo los platos.
El problema con eso es que realmente te gustaría saber si vale la pena tomar los platos en la barra de desayuno antes de pasar por todas estas tonterías. Bueno, para eso todo lo que puedo recomendar es ir de campamento.
Al probar un nuevo paradigma por primera vez, el último lugar donde debería aplicarlo es en una base de código grande. Esto va para cada miembro del equipo. Nadie debería creer que SOLID funciona, que OOP funciona o que la programación funcional funciona. Cada miembro del equipo debe tener la oportunidad de jugar con la nueva idea, sea cual sea, en un proyecto de juguete. Les permite ver al menos cómo funciona. Les permite ver lo que no hace bien. Les permite aprender a hacerlo justo antes de hacer un gran desastre.
Darles a las personas un lugar seguro para jugar les ayudará a adoptar nuevas ideas y les dará la confianza de que los platos realmente podrían funcionar en su nuevo hogar.
fuente
AppSettings
solo para obtener una URL o ruta de archivo.Parece que su código no está muy bien desacoplado y / o los tamaños de sus tareas son demasiado grandes.
Los cambios de código deben ser de 5 a 10 archivos, a menos que esté haciendo una coodod o refactorización a gran escala. Si un solo cambio toca muchos archivos, probablemente signifique que sus cambios caen en cascada. Algunas abstracciones mejoradas (más responsabilidad individual, segregación de interfaz, inversión de dependencia) deberían ayudar. También es posible que tal vez se fue demasiado sola responsabilidad y podría utilizar un poco más el pragmatismo - más corto y más delgadas jerarquías de tipos. Eso también debería hacer que el código sea más fácil de entender, ya que no tiene que comprender docenas de archivos para saber qué está haciendo el código.
También podría ser una señal de que tu trabajo es demasiado grande. En lugar de "hey, agregue esta función" (que requiere cambios en la interfaz de usuario y cambios en la API y cambios en el acceso a datos y cambios en la seguridad y cambios en las pruebas y ...) divídalos en partes más útiles. Eso se vuelve más fácil de revisar y más fácil de entender porque requiere que establezca contratos decentes entre los bits.
Y, por supuesto, las pruebas unitarias ayudan a todo esto. Te obligan a hacer interfaces decentes. Te obligan a hacer que tu código sea lo suficientemente flexible como para inyectar los bits necesarios para probar (si es difícil de probar, será difícil de reutilizar). Y alejan a las personas de las cosas de ingeniería excesiva porque cuanto más ingenieras, más necesitas probar.
fuente
Me gustaría exponer algunas de las cosas ya mencionadas aquí, pero más desde una perspectiva de dónde se dibujan los límites de los objetos. Si está siguiendo algo similar al diseño impulsado por dominio, entonces sus objetos probablemente representarán aspectos de su negocio.
Customer
yOrder
, por ejemplo, serían objetos. Ahora, si tuviera que adivinar en función de los nombres de clase que tenía como punto de partida, suAccountLogic
clase tenía un código que se ejecutaría para cualquier cuenta. En OO, sin embargo, cada clase debe tener contexto e identidad. No debe obtener unAccount
objeto y luego pasarlo a unaAccountLogic
clase y hacer que esa clase realice cambios en elAccount
objeto. Eso es lo que se llama un modelo anémico, y no representa muy bien a OO. En cambio, tuAccount
la clase debería tener un comportamiento, comoAccount.Close()
oAccount.UpdateEmail()
, y esos comportamientos afectarían solo esa instancia de la cuenta.Ahora, CÓMO se manejan estos comportamientos puede (y en muchos casos debería) descargarse en dependencias representadas por abstracciones (es decir, interfaces).
Account.UpdateEmail
, por ejemplo, podría querer actualizar una base de datos o un archivo, o enviar un mensaje a un bus de servicio, etc. Y eso podría cambiar en el futuro. Por lo tanto, suAccount
clase puede depender, por ejemplo, de unaIEmailUpdate
, que podría ser una de las muchas interfaces implementadas por unAccountRepository
objeto. No querría pasar unaIAccountRepository
interfaz completa alAccount
objeto porque probablemente haría demasiado, como buscar y encontrar otras (cualquiera) cuentas, a las que puede que no deseeAccount
que tenga acceso el objeto, pero a pesar de queAccountRepository
podría implementar ambasIAccountRepository
eIEmailUpdate
interfaces, elAccount
El objeto solo tendría acceso a las pequeñas porciones que necesita. Esto le ayuda a mantener el Principio de segregación de interfaz .Siendo realistas, como han mencionado otras personas, si se trata de una explosión de clases, lo más probable es que esté utilizando el principio SOLID (y, por extensión, OO) de la manera incorrecta. SOLID debería ayudarlo a simplificar su código, no complicarlo. Pero lleva tiempo entender realmente qué significan cosas como el SRP. Sin embargo, lo más importante es que el funcionamiento de SOLID dependerá mucho de su dominio y contextos limitados (otro término DDD). No hay bala de plata ni talla única.
Una cosa más que me gusta enfatizar a las personas con las que trabajo: una vez más, un objeto OOP debe tener comportamiento y, de hecho, está definido por su comportamiento, no por sus datos. Si su objeto no tiene más que propiedades y campos, todavía tiene comportamiento, aunque probablemente no sea el comportamiento que pretendía. Una propiedad públicamente editable / configurable sin otra lógica establecida implica que el comportamiento de su clase que contiene es que cualquier persona en cualquier lugar por cualquier razón y en cualquier momento puede modificar el valor de esa propiedad sin ninguna lógica comercial o validación necesaria. Por lo general, ese no es el comportamiento que la gente pretende, pero si tienes un modelo anémico, generalmente es el comportamiento que tus clases anuncian a cualquiera que los use.
fuente
Eso es una locura ... pero estas clases suenan como algo que yo mismo escribiría. Así que echemos un vistazo a ellos. Ignoremos las interfaces y pruebas por ahora.
BasePathProvider
- En mi humilde opinión, cualquier proyecto no trivial que trabaje con archivos lo necesita. Así que supongo que ya existe tal cosa y puede usarla como está.UniqueFilenameProvider
- Claro, ya lo tienes, ¿no?NewGuidProvider
- El mismo caso, a menos que solo estés usando GUID.FileExtensionCombiner
- El mismo caso.PatientFileWriter
- Supongo que esta es la clase principal para la tarea actual.Para mí, se ve bien: debe escribir una nueva clase que necesite cuatro clases auxiliares. Las cuatro clases de ayuda parecen bastante reutilizables, por lo que apuesto a que ya están en algún lugar de su código base. De lo contrario, es mala suerte (¿eres realmente la persona de tu equipo para escribir archivos y usar GUID?) O algún otro problema.
Con respecto a las clases de prueba, seguro, cuando crea una nueva clase o la actualiza, debe probarse. Así que escribir cinco clases también significa escribir cinco clases de prueba. Pero esto no hace que el diseño sea más complicado:
Con respecto a las interfaces, solo son necesarias cuando su marco DI o su marco de prueba no pueden manejar clases. Puede verlos como un peaje para herramientas imperfectas. O puede verlos como una abstracción útil que le permite olvidar que hay cosas más complicadas: leer la fuente de una interfaz lleva mucho menos tiempo que leer la fuente de su implementación.
fuente
+++
Con respecto a romper las reglas: hay cuatro clases que necesito en lugares, donde solo podría inyectarlas después de una refactorización importante que hace que el código sea más feo (al menos para mis ojos), así que decidí convertirlos en singletons (un mejor programador podría encontrar una mejor manera, pero estoy contento con eso; el número de estos singletons no ha cambiado desde hace años).Func<Guid>
para esto e inyectar un método anónimo como()=>Guid.NewGuid()
en el constructor? Y no hay necesidad de probar esta función .Net framework, esto es algo que Microsoft ha hecho por usted. En total, esto te ahorrará 4 clases.Dependiendo de las abstracciones, crear clases de responsabilidad única y escribir pruebas unitarias no son ciencias exactas. Es perfectamente normal balancearse demasiado en una dirección cuando se aprende, ir al extremo y luego encontrar una norma que tenga sentido. Parece que su péndulo se ha movido demasiado, e incluso podría estar atascado.
Aquí es donde sospecho que esto está saliendo de los rieles:
Uno de los beneficios que proviene de la mayoría de los principios SÓLIDOS (ciertamente no es el único beneficio) es que facilita la escritura de pruebas unitarias para nuestro código. Si una clase depende de abstracciones podemos burlarnos de las abstracciones. Las abstracciones segregadas son más fáciles de burlar. Si una clase hace una cosa, es probable que tenga una menor complejidad, lo que significa que es más fácil conocer y probar todas sus rutas posibles.
Si su equipo no está escribiendo pruebas unitarias, están sucediendo dos cosas relacionadas:
Primero, están haciendo mucho trabajo adicional para crear todas estas interfaces y clases sin darse cuenta de todos los beneficios. Se necesita un poco de tiempo y práctica para ver cómo escribir pruebas unitarias nos facilita la vida. Hay razones por las cuales las personas que aprenden a escribir pruebas unitarias se adhieren a él, pero hay que persistir el tiempo suficiente para descubrirlas por sí mismo. Si su equipo no está intentando eso, sentirán que el resto del trabajo extra que están haciendo es inútil.
Por ejemplo, ¿qué sucede cuando necesitan refactorizar? Si tienen cien clases pequeñas pero no hay pruebas que les indiquen si sus cambios funcionarán o no, esas clases e interfaces adicionales parecerán una carga, no una mejora.
En segundo lugar, escribir pruebas unitarias puede ayudarlo a comprender cuánta abstracción realmente necesita su código. Como dije, no es una ciencia. Comenzamos mal, viramos por todo el lugar y mejoramos. Las pruebas unitarias tienen una forma peculiar de complementar SOLID. ¿Cómo sabes cuándo necesitas agregar una abstracción o separar algo? En otras palabras, ¿cómo sabes cuándo estás "suficientemente SÓLIDO"? A menudo la respuesta es cuando no puedes probar algo.
Tal vez su código sea comprobable sin crear tantas pequeñas abstracciones y clases. Pero si no estás escribiendo las pruebas, ¿cómo puedes saberlo? ¿Cuán lejos llegamos? Podemos obsesionarnos con dividir las cosas cada vez más pequeñas. Es una madriguera de conejo. La capacidad de escribir pruebas para nuestro código nos ayuda a ver cuándo hemos cumplido nuestro propósito para que podamos dejar de obsesionarnos, seguir adelante y divertirnos escribiendo más código.
Las pruebas unitarias no son una bala de plata que resuelve todo, pero son una bala realmente increíble que mejora la vida de los desarrolladores. No somos perfectos, y tampoco lo son nuestras pruebas. Pero las pruebas nos dan confianza. Esperamos que nuestro código sea correcto y nos sorprende cuando está mal, no al revés. No somos perfectos y nuestras pruebas tampoco. Pero cuando se prueba nuestro código, tenemos confianza. Es menos probable que nos muerdamos las uñas cuando se implementa nuestro código y nos preguntamos qué es lo que se romperá esta vez y si será culpa nuestra.
Además de eso, una vez que lo entendemos, escribir pruebas unitarias hace que el desarrollo del código sea más rápido, no más lento. Pasamos menos tiempo revisando código viejo o depurando para encontrar problemas que son como agujas en un pajar.
Los errores disminuyen, hacemos más y reemplazamos la ansiedad con confianza. No es una moda o aceite de serpiente. Es real. Muchos desarrolladores darán fe de esto. Si su equipo no ha experimentado esto, deben superar esa curva de aprendizaje y superar el obstáculo. Dale una oportunidad, dándote cuenta de que no obtendrán resultados al instante. Pero cuando sucede, se alegrarán de haberlo hecho y nunca mirarán hacia atrás. (O se convertirán en parias aislados y escribirán publicaciones de blog enojadas sobre cómo las pruebas unitarias y la mayoría de los otros conocimientos de programación acumulados son una pérdida de tiempo).
La revisión por pares es mucho más fácil cuando pasan todas las pruebas unitarias y una gran parte de esa revisión es solo asegurarse de que las pruebas sean significativas.
fuente