Nos últimos anos, passamos lentamente a mudar para um código escrito progressivamente melhor, alguns pequenos passos de cada vez. Finalmente estamos começando a mudar para algo que pelo menos se assemelha ao SOLID, mas ainda não chegamos lá. Desde a mudança, uma das maiores reclamações dos desenvolvedores é que eles não suportam a revisão por pares e a passagem de dezenas e dezenas de arquivos, onde anteriormente todas as tarefas exigiam apenas que o desenvolvedor tocasse de 5 a 10 arquivos.
Antes de começar a fazer a troca, nossa arquitetura era organizada da seguinte maneira (concedida, com uma ou duas ordens de magnitude a mais de arquivos):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
Em termos de arquivo, tudo era incrivelmente linear e compacto. Obviamente, havia muita duplicação de código, acoplamento rígido e dores de cabeça, no entanto, todos podiam atravessar e descobrir. Iniciantes completos, pessoas que nunca abriram o Visual Studio, conseguiram descobrir isso em apenas algumas semanas. A falta de complexidade geral de arquivos torna relativamente simples para desenvolvedores iniciantes e novos contratados começarem a contribuir sem muito tempo de aceleração. Mas é aqui que praticamente qualquer benefício do estilo de código sai pela janela.
Eu sinceramente apoio todas as tentativas que fazemos para melhorar nossa base de código, mas é muito comum receber alguma reação do resto da equipe em mudanças de paradigma massivas como essa. Atualmente, alguns dos maiores pontos de discórdia são:
- Testes unitários
- Contagem de turmas
- Complexidade da revisão por pares
Os testes de unidade têm sido incrivelmente difíceis de vender para a equipe, pois todos acreditam que são uma perda de tempo e que podem testar seu código com muito mais rapidez como um todo do que cada peça individualmente. O uso de testes de unidade como um endosso ao SOLID tem sido inútil e tornou-se uma piada neste momento.
A contagem de turmas é provavelmente o maior obstáculo a superar. As tarefas que costumavam levar de 5 a 10 arquivos agora podem demorar de 70 a 100! Embora cada um desses arquivos tenha uma finalidade distinta, o grande volume de arquivos pode ser esmagador. A resposta da equipe tem sido principalmente gemidos e coçar a cabeça. Anteriormente, uma tarefa pode ter exigido um ou dois repositórios, um modelo ou dois, uma camada lógica e um método de controlador.
Agora, para criar um aplicativo simples para salvar arquivos, você tem uma classe para verificar se o arquivo já existe, uma classe para gravar os metadados, uma classe para abstrair, DateTime.Now
para que você possa injetar horários para testes de unidade, interfaces para cada arquivo que contém lógica, arquivos para conter testes de unidade para cada classe existente e um ou mais arquivos para adicionar tudo ao seu contêiner de DI.
Para aplicações de pequeno e médio porte, o SOLID é uma venda super fácil. Todo mundo vê o benefício e a facilidade de manutenção. No entanto, eles não veem uma boa proposta de valor para o SOLID em aplicativos de grande escala. Por isso, estou tentando encontrar maneiras de melhorar a organização e o gerenciamento para superar as dores do crescimento.
Eu achei que daria um exemplo um pouco mais forte do volume do arquivo com base em uma tarefa concluída recentemente. Foi-me dada a tarefa de implementar algumas funcionalidades em um de nossos microsserviços mais recentes para receber uma solicitação de sincronização de arquivos. Quando a solicitação é recebida, o serviço executa uma série de pesquisas e verificações e, finalmente, salva o documento em uma unidade de rede, além de 2 tabelas de banco de dados separadas.
Para salvar o documento na unidade de rede, eu precisava de algumas classes 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
Portanto, são 15 classes (excluindo POCOs e andaimes) para realizar um salvamento bastante direto. Esse número aumentou significativamente quando eu precisei criar POCOs para representar entidades em alguns sistemas, criei alguns repositórios para se comunicar com sistemas de terceiros incompatíveis com nossos outros ORMs e criei métodos lógicos para lidar com os meandros de certas operações.
Respostas:
Eu acho que você não entendeu a idéia de uma única responsabilidade. A única responsabilidade de uma classe pode ser "salvar um arquivo". Para fazer isso, ele pode dividir essa responsabilidade em um método que verifica se existe um arquivo, um método que grava metadados etc. Cada um desses métodos tem uma única responsabilidade, que faz parte da responsabilidade geral da classe.
Uma aula para abstrair
DateTime.Now
parece boa. Mas você só precisa de um deles e pode ser agrupado com outros recursos do ambiente em uma única classe, com a responsabilidade de abstrair os recursos ambientais. Novamente, uma única responsabilidade com múltiplas sub-responsabilidades.Você não precisa de "interfaces para todos os arquivos que contenham lógica", mas de interfaces para classes com efeitos colaterais, por exemplo, aquelas classes que lêem / gravam em arquivos ou bancos de dados; e mesmo assim, eles são necessários apenas para as partes públicas dessa funcionalidade. Assim, por exemplo
AccountRepo
, talvez você não precise de nenhuma interface, apenas uma interface para o acesso real ao banco de dados que é injetado nesse repositório.Isso sugere que você também não entendeu os testes de unidade. A "unidade" de um teste de unidade não é uma unidade de código. O que é uma unidade de código? Uma aula? Um método? Uma variável? Uma única instrução de máquina? Não, a "unidade" refere-se a uma unidade de isolamento, ou seja, código que pode ser executado isoladamente de outras partes do código. Um teste simples para determinar se um teste automatizado é um teste de unidade ou não é se você pode executá-lo em paralelo com todos os outros testes de unidade sem afetar o resultado. Existem mais algumas regras práticas nos testes de unidade, mas essa é a sua medida principal.
Portanto, se partes do seu código puderem ser testadas como um todo sem afetar outras, faça isso.
Seja sempre pragmático e lembre-se de que tudo é um compromisso. Quanto mais você aderir ao DRY, mais forte será o seu código. Quanto mais você introduz abstrações, mais fácil o código é testar, mas mais difícil é entender. Evite a ideologia e encontre um bom equilíbrio entre o ideal e mantenha-o simples. Aí reside o ponto ideal da máxima eficiência, tanto para o desenvolvimento quanto para a manutenção.
fonte
Isso é o oposto do princípio de responsabilidade única (SRP). Para chegar a esse ponto, você deve ter dividido sua funcionalidade de maneira muito refinada, mas não é disso que trata o SRP - isso ignora a ideia principal de coesão .
De acordo com o SRP, o software deve ser dividido em módulos ao longo das linhas definidas por suas possíveis razões para alterar, de modo que uma única alteração no projeto possa ser aplicada em apenas um módulo sem exigir modificações em outros lugares. Um único "módulo" nesse sentido pode corresponder a mais de uma classe, mas se uma alteração exigir que você toque em dezenas de arquivos, serão realmente várias alterações ou você estará fazendo o SRP errado.
Bob Martin, que originalmente formulou o SRP, escreveu um post no blog alguns anos atrás para tentar esclarecer a situação. Ele discute detalhadamente o que é um "motivo para mudar" para os propósitos do SRP. Vale a pena ler na íntegra, mas entre as coisas que merecem atenção especial está esta redação alternativa do SRP:
(ênfase minha). O SRP não é sobre dividir as coisas nas menores partes possíveis. Isso não é um bom design, e sua equipe está certa em resistir. Isso torna sua base de código mais difícil de atualizar e manter. Parece que você pode estar tentando vender sua equipe com base em considerações de teste de unidade, mas isso colocaria o carrinho à frente do cavalo.
Da mesma forma, o princípio de segregação da interface não deve ser tomado como absoluto. Não é mais um motivo para dividir o seu código tão finamente do que o SRP, e geralmente ele se alinha muito bem com o SRP. O fato de uma interface conter alguns métodos que alguns clientes não usam não é um motivo para quebrá-la. Você está novamente procurando por coesão.
Além disso, peço que você não tome o princípio de aberto-fechado ou o princípio de substituição de Liskov como uma razão para favorecer hierarquias profundas de herança. Não há acoplamento mais apertado do que uma subclasse com suas superclasses, e o acoplamento apertado é um problema de design. Em vez disso, favorece a composição sobre a herança, sempre que fizer sentido. Isso reduzirá seu acoplamento e, portanto, o número de arquivos que uma alteração específica pode precisar tocar, e se alinha perfeitamente à inversão de dependência.
fonte
Isso é uma mentira. As tarefas nunca levaram apenas 5 a 10 arquivos.
Você não está resolvendo nenhuma tarefa com menos de 10 arquivos. Por quê? Porque você está usando c #. C # é uma linguagem de alto nível. Você está usando mais de 10 arquivos apenas para criar um olá mundo.
Ah, claro que você não os percebe porque não os escreveu. Então você não olha neles. Você confia neles.
O problema não é o número de arquivos. Agora você tem tanta coisa que não confia.
Então, descubra como fazer esses testes funcionarem a tal ponto que, uma vez aprovados, você confia nesses arquivos da mesma maneira que confia nos arquivos do .NET. Fazer isso é o ponto do teste de unidade. Ninguém se importa com o número de arquivos. Eles se preocupam com o número de coisas em que não podem confiar.
A mudança é difícil em aplicações de larga escala, não importa o que você faça. A melhor sabedoria a aplicar aqui não vem do tio Bob. Vem de Michael Feathers em seu livro Working Effective with Legacy Code.
Não inicie um fest de reescrita. O código antigo representa um conhecimento adquirido com dificuldade. Jogá-lo fora porque tem problemas e não é expresso no novo e aprimorado paradigma X está apenas pedindo um novo conjunto de problemas e nenhum conhecimento adquirido com força.
Em vez disso, encontre maneiras de tornar seu código antigo e não testável (código legado no Feathers speak). Nesta metáfora, o código é como uma camisa. Peças grandes são unidas em costuras naturais que podem ser desfeitas para separar o código da maneira que você remove as costuras. Faça isso para anexar "mangas" de teste que permitem isolar o restante do código. Agora, quando você cria as mangas de teste, confia nas mangas porque fez isso com uma camisa de trabalho. (ow, essa metáfora está começando a doer).
Essa idéia parte da suposição de que, como na maioria das lojas, os únicos requisitos atualizados estão no código de trabalho. Isso permite que você troque isso em testes que permitem fazer alterações no código de trabalho comprovado sem que ele perca todos os seus status de trabalho comprovado. Agora, com essa primeira onda de testes, você pode começar a fazer alterações que tornam o código "legado" (não testável) testável. Você pode ser ousado, porque os testes de costuras estão apoiando você dizendo que isso é o que sempre fazia e os novos testes mostram que seu código realmente faz o que você pensa.
O que isso tem a ver com:
Abstração.
Você pode me fazer odiar qualquer base de código com más abstrações. Uma má abstração é algo que me faz olhar para dentro. Não me surpreenda quando olho para dentro. Seja praticamente o que eu esperava.
Dê-me um bom nome, testes legíveis (exemplos) que mostram como usar a interface e organizá-la para que eu possa encontrar coisas e não me importo se usamos 10, 100 ou 1000 arquivos.
Você me ajuda a encontrar coisas com bons nomes descritivos. Coloque coisas com bons nomes em coisas com bons nomes.
Se você fizer tudo isso corretamente, você abstrairá os arquivos para onde a tarefa está concluída, dependendo de 3 a 5 outros arquivos. Os arquivos 70-100 ainda estão lá. Mas eles estão se escondendo atrás dos 3 a 5. Isso só funciona se você confiar nos 3 a 5 para fazer isso direito.
Então, o que você realmente precisa é o vocabulário para criar bons nomes para todas essas coisas e testes nas quais as pessoas confiam, para que parem de percorrer tudo. Sem isso, você também estaria me deixando louco.
@Delioth faz um bom argumento sobre dores de crescimento. Quando você está acostumado com a louça no armário acima da lava-louça, é preciso algum tempo para se acostumar a estar acima da barra de café da manhã. Torna algumas coisas mais difíceis. Facilita algumas coisas. Mas isso causa todos os tipos de pesadelos se as pessoas não concordam onde os pratos vão. Em uma grande base de código, o problema é que você só pode mover alguns pratos de cada vez. Então agora você tem pratos em dois lugares. É confuso. Torna difícil confiar que os pratos estejam onde deveriam estar. Se você quiser superar isso, a única coisa a fazer é continuar mexendo na louça.
O problema é que você realmente gostaria de saber se vale a pena lavar os pratos na barra de café da manhã antes de passar por toda essa bobagem. Bem, para isso tudo o que posso recomendar é acampar.
Ao experimentar um novo paradigma pela primeira vez, o último local que você deve aplicá-lo é em uma grande base de código. Isso vale para todos os membros da equipe. Ninguém deve acreditar que o SOLID funciona, que OOP funciona ou que a programação funcional funciona. Todo membro da equipe deve ter a chance de brincar com a nova idéia, seja ela qual for, em um projeto de brinquedo. Isso permite que eles vejam pelo menos como funciona. Isso permite que eles vejam o que não faz bem. Isso permite que eles aprendam a fazer isso antes de fazer uma grande bagunça.
Dar às pessoas um lugar seguro para brincar as ajudará a adotar novas idéias e a confiar que os pratos realmente podem funcionar em seu novo lar.
fonte
AppSettings
apenas para obter um caminho de URL ou arquivo.Parece que seu código não está muito bem dissociado e / ou o tamanho das suas tarefas é muito grande.
As alterações de código devem ter entre 5 e 10 arquivos, a menos que você esteja executando um refecho de código ou em larga escala. Se uma única alteração tocar muitos arquivos, provavelmente significa que suas alterações estão em cascata. Algumas abstrações aprimoradas (responsabilidade mais única, segregação de interface, inversão de dependência) devem ajudar. Também é possível que você tenha assumido uma responsabilidade única e possa usar um pouco mais de pragmatismo - hierarquias mais curtas e mais finas. Isso também deve facilitar a compreensão do código, já que você não precisa entender dezenas de arquivos para saber o que o código está fazendo.
Também pode ser um sinal de que seu trabalho é muito grande. Em vez de "ei, adicione esse recurso" (que requer alterações na interface do usuário e alterações de API e alterações de acesso a dados e alterações de segurança e alterações de teste e ...), divida-as em partes mais úteis. Isso fica mais fácil de revisar e mais fácil de entender, porque exige que você estabeleça contratos decentes entre os bits.
E, é claro, os testes de unidade ajudam tudo isso. Eles forçam você a fazer interfaces decentes. Eles o forçam a tornar seu código flexível o suficiente para injetar os bits necessários para testar (se for difícil testar, será difícil reutilizar). E eles afastam as pessoas de coisas com excesso de engenharia, porque quanto mais você projeta, mais precisa testar.
fonte
Eu gostaria de expor algumas das coisas já mencionadas aqui, mas mais de uma perspectiva de onde os limites dos objetos são traçados. Se você está seguindo algo parecido com o Design Orientado a Domínio, é provável que seus objetos representem aspectos de seus negócios.
Customer
eOrder
, por exemplo, seriam objetos. Agora, se eu fizesse um palpite com base nos nomes das classes que você tinha como ponto de partida, suaAccountLogic
classe tinha um código que seria executado em qualquer conta. No OO, no entanto, cada classe deve ter contexto e identidade. Você não deve obter umAccount
objeto e depois passá-lo para umaAccountLogic
classe e fazer com que essa classe faça alterações noAccount
objeto. Isso é chamado de modelo anêmico e não representa muito bem o OO. Em vez disso, seuAccount
A classe deve ter um comportamento, comoAccount.Close()
ouAccount.UpdateEmail()
, e esses comportamentos afetariam apenas a instância da conta.Agora, COMO esses comportamentos são tratados pode (e em muitos casos deve) ser descarregado para dependências representadas por abstrações (ou seja, interfaces).
Account.UpdateEmail
, por exemplo, pode querer atualizar um banco de dados ou um arquivo ou enviar uma mensagem para um barramento de serviço etc. E isso pode mudar no futuro. Portanto, suaAccount
classe pode ter uma dependência, por exemplo, de umIEmailUpdate
, que poderia ser uma das muitas interfaces implementadas por umAccountRepository
objeto. Você não gostaria de passar umaIAccountRepository
interface inteira para oAccount
objeto, porque provavelmente faria muito, como pesquisar e encontrar outras (quaisquer) contas, às quais você talvez não queira que oAccount
objeto tenha acesso, mas mesmo queAccountRepository
possa implementar as duasIAccountRepository
eIEmailUpdate
interfaces, oAccount
O objeto teria acesso apenas às pequenas porções necessárias. Isso ajuda a manter o Princípio de Segregação de Interface .Realisticamente, como outras pessoas mencionaram, se você está lidando com uma explosão de classes, é provável que esteja usando o princípio SOLID (e, por extensão, OO) da maneira errada. O SOLID deve ajudar você a simplificar seu código, não complicá-lo. Mas leva tempo para realmente entender o que significam coisas como o SRP. O mais importante, no entanto, é que o funcionamento do SOLID dependerá muito do seu domínio e contextos limitados (outro termo DDD). Não há bala de prata ou tamanho único.
Mais uma coisa que gosto de enfatizar para as pessoas com quem trabalho: novamente, um objeto OOP deve ter comportamento e é, de fato, definido por seu comportamento, não por seus dados. Se o seu objeto não tiver nada além de propriedades e campos, ele ainda terá comportamento, embora provavelmente não seja o comportamento que você pretendia. Uma propriedade gravável publicamente / configurável sem outra lógica de conjunto implica que o comportamento para sua classe que contém é que qualquer pessoa em qualquer lugar, por qualquer motivo e a qualquer momento, possa modificar o valor dessa propriedade sem nenhuma lógica ou validação comercial necessária. Geralmente, esse não é o comportamento que as pessoas pretendem, mas se você tem um modelo anêmico, geralmente é o comportamento que suas classes estão anunciando para quem as usa.
fonte
Isso é loucura ... mas essas aulas parecem algo que eu mesmo escreveria. Então, vamos dar uma olhada neles. Vamos ignorar as interfaces e testes por enquanto.
BasePathProvider
- IMHO qualquer projeto não trivial trabalhando com arquivos precisa dele. Então, eu diria que já existe e você pode usá-lo como está.UniqueFilenameProvider
- Claro, você já tem, não é?NewGuidProvider
- O mesmo caso, a menos que você esteja apenas olhando para usar o GUID.FileExtensionCombiner
- O mesmo caso.PatientFileWriter
- Acho que essa é a classe principal da tarefa atual.Para mim, parece bom: você precisa escrever uma nova classe que precise de quatro classes auxiliares. Todas as quatro classes auxiliares parecem bastante reutilizáveis, então eu aposto que elas já estão em algum lugar na sua base de código. Caso contrário, é azar (você é realmente a pessoa da sua equipe para gravar arquivos e usar GUIDs ???) ou algum outro problema.
Com relação às classes de teste, com certeza, quando você cria uma nova classe ou atualiza-a, ela deve ser testada. Portanto, escrever cinco classes significa escrever cinco classes de teste também. Mas isso não torna o design mais complicado:
Com relação às interfaces, elas são necessárias apenas quando sua estrutura de DI ou sua estrutura de teste não pode lidar com classes. Você pode vê-los como um pedágio para ferramentas imperfeitas. Ou você pode vê-las como uma abstração útil, permitindo esquecer que existem coisas mais complicadas - ler a fonte de uma interface leva muito menos tempo do que ler a fonte de sua implementação.
fonte
+++
Com relação à quebra das regras: são necessárias quatro classes em alguns lugares, onde eu só poderia injetá-las após uma grande refatoração, tornando o código mais feio (pelo menos para os meus olhos), então decidi fazê-las em singletons (um programador melhor) pode encontrar uma maneira melhor, mas estou feliz com isso; o número desses singletons não muda desde as idades).Func<Guid>
-lo e injetar um método anônimo como()=>Guid.NewGuid()
no construtor? E não há necessidade de testar essa função de estrutura .Net, isso é algo que a Microsoft fez por você. No total, você economizará 4 aulas.Dependendo das abstrações, criar classes de responsabilidade única e escrever testes de unidade não são ciências exatas. É perfeitamente normal ir muito longe em uma direção ao aprender, ir ao extremo e depois encontrar uma norma que faça sentido. Parece que o seu pêndulo ficou muito longe e pode até ficar preso.
Aqui é onde eu suspeito que isso está saindo dos trilhos:
Um dos benefícios que advém da maioria dos princípios do SOLID (certamente não é o único benefício) é que facilita a gravação de testes de unidade para o nosso código. Se uma classe depende de abstrações, podemos zombar das abstrações. Abstrações que são segregadas são mais fáceis de zombar. Se uma classe faz uma coisa, é provável que tenha menor complexidade, o que significa que é mais fácil conhecer e testar todos os caminhos possíveis.
Se sua equipe não está escrevendo testes de unidade, duas coisas relacionadas estão acontecendo:
Primeiro, eles estão fazendo muito trabalho extra para criar todas essas interfaces e classes sem perceber todos os benefícios. Leva um pouco de tempo e prática para ver como escrever testes de unidade facilita nossa vida. Há razões pelas quais as pessoas que aprendem a escrever testes de unidade cumprem isso, mas você precisa persistir o tempo suficiente para descobri-las por si mesmo. Se sua equipe não está tentando fazer isso, eles sentirão que o resto do trabalho extra que eles estão fazendo é inútil.
Por exemplo, o que acontece quando eles precisam refatorar? Se eles tiverem centenas de turmas pequenas, mas não tiverem testes para dizer se suas alterações funcionarão ou não, essas classes e interfaces extras parecerão um fardo, não uma melhoria.
Segundo, escrever testes de unidade pode ajudá-lo a entender quanta abstração seu código realmente precisa. Como eu disse, não é uma ciência. Começamos mal, virando por todo o lado e melhorando. Os testes de unidade têm uma maneira peculiar de complementar o SOLID. Como você sabe quando precisa adicionar uma abstração ou separar algo? Em outras palavras, como você sabe quando está "suficientemente sólido"? Muitas vezes, a resposta é quando você não pode testar alguma coisa.
Talvez seu código possa ser testado sem criar tantas abstrações e classes minúsculas. Mas se você não está escrevendo os testes, como pode saber? Até onde vamos? Podemos ficar obcecados em dividir as coisas cada vez menos. É uma toca de coelho. A capacidade de escrever testes para o nosso código nos ajuda a ver quando cumprimos nosso objetivo, para que possamos parar de ficar obcecados, seguir em frente e nos divertir escrevendo mais código.
Os testes de unidade não são uma bala de prata que resolve tudo, mas são uma bala realmente impressionante que melhora a vida dos desenvolvedores. Não somos perfeitos, nem nossos testes. Mas os testes nos dão confiança. Esperamos que nosso código esteja correto e ficamos surpresos quando está errado, e não o contrário. Não somos perfeitos e nem nossos testes. Mas quando nosso código é testado, temos confiança. Estamos menos propensos a roer as unhas quando nosso código é implantado e nos perguntamos o que vai quebrar dessa vez e se será nossa culpa.
Além disso, assim que pegamos o jeito, escrever testes de unidade torna o desenvolvimento do código mais rápido, não mais lento. Passamos menos tempo revisitando o código antigo ou depurando para encontrar problemas que são como agulhas no palheiro.
Os erros diminuem, fazemos mais e substituímos a ansiedade pela confiança. Não é uma moda passageira ou óleo de cobra. É real. Muitos desenvolvedores atestam isso. Se sua equipe ainda não experimentou isso, eles precisam avançar nessa curva de aprendizado e superar o problema. Dê uma chance, percebendo que eles não obterão resultados instantaneamente. Mas quando isso acontecer, eles ficarão felizes em ter feito e nunca olharão para trás. (Ou eles se tornarão párias isolados e escreverão posts irritados no blog sobre como os testes de unidade e a maioria dos outros conhecimentos acumulados de programação são uma perda de tempo.)
A revisão por pares é muito mais fácil quando todos os testes de unidade são aprovados e grande parte dessa revisão é apenas garantir que os testes sejam significativos.
fonte