Um amigo meu está trabalhando para uma pequena empresa em um projeto que todo desenvolvedor odiaria: ele é pressionado a liberar o mais rápido possível, ele é o único que parece se importar com dívidas técnicas, o cliente não tem formação técnica etc.
Ele me contou uma história que me fez pensar sobre a adequação dos padrões de design em projetos como este. Aqui está a história.
Tivemos que exibir produtos em locais diferentes no site. Por exemplo, os gerentes de conteúdo podem visualizar os produtos, mas também os usuários finais ou os parceiros por meio da API.
Às vezes, faltavam informações nos produtos: por exemplo, muitos deles não tinham preço quando o produto foi criado, mas o preço ainda não foi especificado. Alguns não tinham uma descrição (a descrição é um objeto complexo com históricos de modificação, conteúdo localizado etc.). Alguns estavam carecendo de informações sobre remessas.
Inspirado pelas minhas recentes leituras sobre padrões de design, achei que essa era uma excelente oportunidade para usar o padrão mágico de Objeto Nulo . Então eu fiz, e tudo estava liso e limpo. Bastava ligar
product.Price.ToString("c")
para exibir o preço ouproduct.Description.Current
mostrar a descrição; não é necessário material condicional. Até que, um dia, a parte interessada pediu para exibi-lo de maneira diferente na API, exibindonull
JSON. E também de maneira diferente para os gerenciadores de conteúdo, mostrando "Preço não especificado [Alterar]". E tive que matar meu amado padrão de Objeto Nulo, porque não havia mais necessidade dele.Da mesma forma, tive que remover algumas fábricas abstratas e alguns construtores, acabei substituindo meu belo padrão de fachada por chamadas diretas e feias, porque as interfaces subjacentes mudavam duas vezes por dia durante três meses, e até o Singleton me deixava quando os requisitos informavam que o objeto em questão tinha que ser diferente, dependendo do contexto.
Mais de três semanas de trabalho consistiram em adicionar padrões de design e depois separá-los um mês depois, e meu código finalmente se tornou espaguete suficiente para ser impossível de manter por qualquer pessoa, inclusive eu. Não seria melhor nunca usar esses padrões em primeiro lugar?
Na verdade, eu tive que trabalhar nesses tipos de projetos em que os requisitos estão mudando constantemente e são ditados por pessoas que realmente não têm em mente a coesão ou a coerência do produto. Nesse contexto, não importa o quão ágil você seja, você encontrará uma solução elegante para um problema e, quando finalmente o implementa, aprende que os requisitos mudaram tão drasticamente, que sua solução elegante não se encaixa não mais.
Qual seria a solução nesse caso?
Não está usando nenhum padrão de design, para de pensar e escreve código diretamente?
Seria interessante fazer uma experiência em que uma equipe está escrevendo código diretamente, enquanto outra está pensando duas vezes antes de digitar, correndo o risco de jogar fora o design original alguns dias depois: quem sabe, talvez as duas equipes tenham o mesma dívida técnica. Na ausência de tais dados, eu apenas afirmaria que não parece correto digitar código sem pensar antes ao trabalhar em um projeto de 20 meses por homem.
Mantenha o padrão de design que não faz mais sentido e tente adicionar mais padrões para a situação recém-criada?
Isso também não parece certo. Padrões são usados para simplificar o entendimento do código; coloque muitos padrões e o código se tornará uma bagunça.
Comece a pensar em um novo design que inclua os novos requisitos e refatorie lentamente o antigo para o novo?
Como um teórico e aquele que favorece o Agile, eu gosto muito dele. Na prática, quando você sabe que precisará voltar ao quadro branco toda semana e refazer grande parte do design anterior e que o cliente simplesmente não tem fundos suficientes para pagar por isso, nem tempo suficiente para esperar , isso provavelmente não vai funcionar.
Então, alguma sugestão?
fonte
Respostas:
Vejo algumas suposições erradas nesta pergunta:
Os padrões de design não têm fim em si, eles devem atendê-lo, não vice-versa. Se um padrão de design não facilitar a implementação do código ou, pelo menos, evoluir melhor (isso significa: mais fácil de ser adaptado às mudanças de requisitos), o padrão perderá seu objetivo. Não aplique padrões quando eles não facilitarem a "vida" da equipe. Se o novo padrão de objeto Nulo estava servindo seu amigo durante o tempo em que ele o usou, tudo estava bem. Se fosse para ser eliminado mais tarde, isso também poderia estar ok. Se o padrão de objeto Nulo atrasou a implementação (correta), seu uso estava errado. Observe que desta parte da história não se pode concluir nenhuma causa do "código do espaguete" até agora.
Isso não é trabalho nem culpa dele! Seu trabalho é se preocupar com coesão e coerência. Quando os requisitos mudam duas vezes por dia, sua solução não deve ser sacrificar a qualidade do código. Diga ao cliente quanto tempo leva e, se achar que precisa de mais tempo para obter o design "correto", adicione uma margem de segurança suficientemente grande para qualquer estimativa. Especialmente quando você tem um cliente tentando pressioná-lo, use o "Princípio Scotty" . E, ao discutir com um cliente não técnico sobre o esforço, evite termos como "refatoração", "testes de unidade", "padrões de design" ou "documentação de código" - coisas que ele não entende e provavelmente considera "desnecessário" absurdo "porque ele não vê valor nele. ou pelo menos compreensível para o cliente (recursos, sub-recursos, alterações de comportamento, documentos do usuário, correções de erros, otimização de desempenho etc.).
Honestamente, se "as interfaces subjacentes mudarem duas vezes por dia durante três meses", a solução não deve ser reagir alterando o código duas vezes por dia. A solução real é perguntar por que os requisitos mudam com tanta frequência e se é possível fazer uma alteração nessa parte do processo. Talvez um pouco mais de análise inicial ajude. Talvez a interface seja muito ampla porque o limite entre os componentes foi escolhido incorretamente. Às vezes, ajuda a pedir mais informações sobre qual parte dos requisitos é estável e quais ainda estão em discussão (e, na verdade, adia a implementação para as coisas em discussão). E, às vezes, algumas pessoas só precisam ser "chutadas na bunda" por não mudar de idéia duas vezes por dia.
fonte
Minha humilde opinião é que você não deve evitar ou não evitar o uso de padrões de design.
Os padrões de design são simplesmente soluções bem conhecidas e confiáveis para problemas gerais, que receberam nomes. Eles não são diferentes de maneira técnica do que qualquer outra solução ou design que você possa imaginar.
Acho que a raiz do problema pode ser que seu amigo pense em termos de "aplicar ou não aplicar um padrão de design", em vez de pensar em "qual é a melhor solução em que posso pensar, incluindo, mas não se limitando, aos padrões Eu sei".
Talvez essa abordagem o leve a usar padrões de formas parcialmente artificiais ou forçadas, em locais onde não pertencem. E é isso que resulta em uma bagunça.
fonte
No seu exemplo de uso do padrão Null Object, acredito que acabou falhando porque atendeu às necessidades do programador e não às necessidades do cliente. O cliente precisava exibir o preço em um formulário apropriado ao contexto. O programador precisava simplificar parte do código de exibição.
Portanto, quando um padrão de design não atende aos requisitos, dizemos que todos os padrões de design são uma perda de tempo ou dizemos que precisamos de um padrão de design diferente?
fonte
Parece que o erro foi mais para remover os objetos padrão do que para usá-los. No design inicial, o Objeto Nulo parece ter fornecido uma solução para um problema. Esta pode não ter sido a melhor solução.
Ser a única pessoa que trabalha em um projeto oferece a oportunidade de experimentar todo o processo de desenvolvimento. A grande desvantagem é não ter alguém para ser seu mentor. Dedicar tempo para aprender e aplicar as melhores ou melhores práticas provavelmente renderá rapidamente. O truque é identificar qual prática aprender quando.
As referências de encadeamento no formato product.Price.toString ('c') violam a Lei de Demeter . Eu já vi todos os tipos de problemas com essa prática, muitos dos quais relacionados a nulos. Um método como product.displayPrice ('c') pode manipular preços nulos internamente. Da mesma forma product.Description.Current pode ser tratado por product.displayDescription (), product.displayCurrentDescription (). ou product.diplay ('Atual').
O tratamento do novo requisito para gerentes e provedores de conteúdo precisa ser tratado, respondendo ao contexto. Há uma variedade de abordagens que podem ser usadas. Os métodos de fábrica podem usar diferentes classes de produtos, dependendo da classe de usuário para a qual eles serão exibidos. Outra abordagem seria que os métodos de exibição da classe do produto construíssem dados diferentes para diferentes usuários.
A boa notícia é que seu amigo percebe que as coisas estão ficando fora de controle. Felizmente, ele tem o código no controle de revisão. Isso permitirá que ele recue de decisões erradas, que ele invariavelmente tomará. Parte do aprendizado é tentar abordagens diferentes, algumas das quais falharão. Se ele conseguir lidar com os próximos meses, poderá encontrar abordagens que simplifiquem sua vida e limpem o espaguete. Ele poderia tentar consertar uma coisa a cada semana.
fonte
A questão parece estar errada em muitos pontos. Mas os flagrantes são:
Muitas pessoas disseram corretamente que os padrões de design têm muito a ver com rotular e nomear uma prática comum. Então pense em uma camisa, uma camisa tem um colarinho, por algum motivo você remove o colarinho ou parte dele. A nomeação e rotulagem mudam, mas ainda é uma camisa em essência. Este é exatamente o caso aqui, pequenas alterações nos detalhes, o que não significa que você tenha "assassinado" esse padrão. (lembre-se da expressão extrema)
De acordo com minha experiência, quando pequenos requisitos surgirem, você precisará alterar apenas uma pequena parte da base de código. Alguns podem ser um pouco hacky, mas nada muito sério para afetar substancialmente a capacidade de manutenção ou legibilidade e, muitas vezes, algumas linhas de comentário para explicar a parte hacky serão suficientes. Essa é uma prática muito comum também.
fonte
Vamos fazer uma pausa por um momento e examinar a questão fundamental aqui - a arquitetura de um sistema em que o modelo de arquitetura é muito acoplado a recursos de baixo nível no sistema, fazendo com que a arquitetura quebre com frequência no processo de desenvolvimento.
Acho que devemos lembrar que o uso da arquitetura e dos padrões de design relacionados a ela deve ser estabelecido em um nível apropriado e que a análise do nível correto não é trivial. Por um lado, você pode facilmente manter a arquitetura do seu sistema em um nível muito alto, com apenas restrições muito básicas, como "MVC" ou similar, o que pode levar a oportunidades perdidas, como diretrizes claras e alavancagem de código, e onde o código de espaguete pode facilmente florescer em todo esse espaço livre.
Por outro lado, você também pode arquitetar demais o seu sistema, como ao definir as restrições em um nível detalhado, onde assume que pode confiar nas restrições que, na realidade, são mais voláteis do que o esperado, quebrando constantemente suas restrições e forçando você a constantemente remodelar e reconstruir, até você começar a se desesperar.
Alterações nos requisitos de um sistema sempre estarão presentes, em menor ou maior grau. E os benefícios potenciais do uso de padrões de arquitetura e design sempre estarão presentes, portanto não há realmente uma questão de usar ou não padrões de design, mas em que nível você deve usá-los.
Isso requer que você não apenas compreenda os requisitos atuais do sistema proposto, mas também identifique quais aspectos dele podem ser vistos como propriedades principais estáveis do sistema e quais propriedades podem mudar durante o curso do desenvolvimento.
Se você achar que precisa lutar constantemente com código espaguete desorganizado, provavelmente não está fazendo arquitetura suficiente ou em um nível alto. Se você achar que sua arquitetura frequentemente falha, provavelmente está fazendo uma arquitetura muito detalhada.
O uso de padrões de arquitetura e design não é algo em que você possa "revestir" um sistema, como se você pintasse uma mesa. São técnicas que devem ser aplicadas com ponderação, em um nível em que as restrições em que você precisa confiar têm uma grande possibilidade de serem estáveis e em que essas técnicas realmente valem a pena ao modelar a arquitetura e implementar as restrições / arquitetura / padrões reais como código.
Relevante para a questão de uma arquitetura muito detalhada, você também pode dedicar muito esforço à arquitetura em que ela não está dando muito valor. Veja a arquitetura orientada a riscos para referência, eu gosto deste livro - Arquitetura de software suficiente , talvez você também goste .
Editar
Esclareci minha resposta desde que percebi que muitas vezes me expressava como "arquitetura demais", onde realmente queria dizer "arquitetura detalhada demais", que não é exatamente a mesma. Uma arquitetura muito detalhada provavelmente pode ser vista como uma arquitetura "demais", mas mesmo que você mantenha a arquitetura em um bom nível e crie o sistema mais bonito que a humanidade já viu, isso pode ser um esforço demais na arquitetura, se as prioridades forem sobre recursos e tempo de colocação no mercado.
fonte
Seu amigo parece estar enfrentando vários ventos contrários com base em sua anedota. Isso é lamentável e pode ser um ambiente muito difícil de se trabalhar. Apesar da dificuldade, ele estava no caminho correto de usar padrões para facilitar sua vida, e é uma pena que ele tenha deixado esse caminho. O código do espaguete é o resultado final.
Como existem duas áreas problemáticas diferentes, técnica e interpessoal, abordarei cada uma separadamente.
Interpessoal
A luta que seu amigo está enfrentando é com os requisitos que mudam rapidamente e como isso está afetando sua capacidade de escrever código sustentável. Diria primeiro que os requisitos que mudam duas vezes por dia, todos os dias por um período tão longo são um problema maior e têm uma expectativa implícita irrealista. Os requisitos estão mudando mais rapidamente do que o código pode mudar. Não podemos esperar que o código, ou o programador, acompanhe. Esse ritmo acelerado de mudança é sintomático de uma concepção incompleta do produto desejado em um nível superior. Isto é um problema. Se eles não souberem o que realmente querem, perderão muito tempo e dinheiro para nunca conseguir.
Pode ser bom definir limites para alterações. Agrupe as alterações em conjuntos a cada duas semanas e congele-as pelas duas semanas enquanto são implementadas. Crie uma nova lista para o próximo período de duas semanas. Sinto que algumas dessas mudanças se sobrepõem ou se contradizem (por exemplo, oscilando entre duas opções). Quando as mudanças são rápidas e furiosas, todas elas têm prioridade máxima. Se você os deixar acumular em uma lista, poderá trabalhar com eles para organizar e priorizar o que é mais importante para maximizar esforços e produtividade. Eles podem perceber que algumas de suas mudanças são tolas ou menos importantes, dando a seu amigo algum espaço para respirar.
Esses problemas não devem impedir você de escrever um bom código. Código incorreto leva a problemas piores. Pode levar tempo para refatorar de uma solução para outra, mas o próprio fato de ser possível mostra os benefícios de boas práticas de codificação por meio de padrões e princípios.
Em um ambiente de mudanças frequentes, a dívida técnica será vencida em algum momento. É muito melhor fazer pagamentos a ele em vez de jogar a toalha e esperar até que fique grande demais para ser superada. Se um padrão não for mais útil, refatore-o, mas não volte para as formas de codificação de cowboy.
Técnico
Seu amigo parece ter uma boa noção dos padrões básicos de design. Objeto nulo é uma boa abordagem para o problema que ele estava enfrentando. Na verdade, ainda é uma boa abordagem. Onde ele parece ter desafios é entender os princípios por trás dos padrões, o porquê do que eles são. Caso contrário, não acredito que ele teria abandonado sua abordagem.
(A seguir, um conjunto de soluções técnicas que não foram solicitadas na pergunta original, mas que mostram como podemos aderir aos padrões para fins ilustrativos.)
O princípio por trás do objeto nulo é a ideia de encapsular o que varia . Escondemos as mudanças para não precisar lidar com elas em qualquer outro lugar. Aqui, o objeto nulo estava encapsulando a variação na
product.Price
instância (chamarei dePrice
objeto e o preço do objeto nulo seráNullPrice
).Price
é um objeto de domínio, um conceito de negócios. Às vezes, em nossa lógica de negócios, ainda não sabemos o preço. Isto acontece. Caso de uso perfeito para o objeto nulo.Price
s têm umToString
método que gera o preço ou a sequência vazia, se não for conhecida (ouNullPrice#ToString
retorna uma sequência vazia). Este é um comportamento razoável. Então os requisitos mudam.Temos que gerar a
null
para a visualização da API ou uma string diferente para a visualização dos gerentes. Como isso afeta nossa lógica de negócios? Bem, não. Na declaração acima, usei a palavra 'view' duas vezes. Essa palavra provavelmente não foi dita explicitamente, mas precisamos nos treinar para ouvir as palavras ocultas nos requisitos. Então, por que 'ver' importa tanto? Porque nos diz onde a mudança realmente deve acontecer: em nossa opinião.Além : se estamos ou não usando uma estrutura MVC é irrelevante aqui. Embora o MVC tenha um significado muito específico para 'View', eu o uso no significado mais geral (e talvez mais aplicável) de um código de apresentação.
Então, realmente precisamos corrigir isso na exibição. Como poderíamos fazer isso? A maneira mais fácil de fazer isso seria uma
if
declaração. Eu sei que o objeto nulo tinha como objetivo livrar-se de todos os ifs, mas precisamos ser pragmáticos. Podemos verificar a string para ver se está vazia e alternar:Como isso afeta o encapsulamento? A parte mais importante aqui é que a lógica da vista é encapsulada na vista . Podemos manter nossos objetos de lógica / domínio de negócios completamente isolados de alterações na lógica de exibição dessa maneira. É feio, mas funciona. Esta não é a única opção, no entanto.
Poderíamos dizer que nossa lógica de negócios mudou um pouco, pois queremos gerar sequências padrão se nenhum preço for definido. Podemos fazer um pequeno ajuste no nosso
Price#ToString
método (na verdade, criar um método sobrecarregado). Podemos aceitar um valor de retorno padrão e retornar que, se nenhum preço for definido:E agora nosso código de visualização se torna:
O condicional se foi. Fazer isso demais, no entanto, pode proliferar métodos de casos especiais nos objetos do domínio, portanto, isso só faz sentido se houver apenas algumas instâncias disso.
Em vez disso, poderíamos criar um
IsSet
métodoPrice
que retorne um booleano:Ver lógica:
Vemos o retorno do condicional na visualização, mas o caso é mais forte para a lógica de negócios, informando se o preço está definido. Podemos usar em
Price#IsSet
outro lugar agora que o temos disponível.Finalmente, podemos resumir a ideia de apresentar um preço inteiramente em um auxiliar para a exibição. Isso ocultaria o condicional, preservando o objeto de domínio tanto quanto gostaríamos:
Ver lógica:
Existem muitas outras maneiras de fazer as alterações (poderíamos generalizar
PriceStringHelper
em um objeto que retorne um padrão se uma string estiver vazia), mas estas são algumas rápidas que preservam (na maioria das vezes) os padrões e os princípios, como bem como o aspecto pragmático de fazer essa mudança.fonte
A complexidade de um padrão de design pode mordê-lo se o problema que deveria resolver desaparecer repentinamente. Infelizmente, devido ao entusiasmo e popularidade dos padrões de design, esse risco raramente é explicitado. A anedota de seu amigo ajuda muito a mostrar como os padrões não são recompensados. Jeff Atwood tem algumas palavras de escolha sobre o tópico.
Documente pontos de variação (são riscos) nos requisitos
Muitos dos padrões de design mais complexos (Objeto Nulo, nem tanto) contêm o conceito de Variações Protegidas , que é "Identificar pontos de variação ou instabilidade prevista; atribua responsabilidades para criar uma interface estável em torno deles". Adaptador, Visitante, Fachada, Camadas, Observador, Estratégia, Decorador, etc., todos exploram esse princípio. Eles "compensam" quando o software precisa ser estendido na dimensão da variabilidade esperada, e as suposições "estáveis" permanecem estáveis.
Se seus requisitos são tão instáveis que suas "variações previstas" estão sempre erradas, os padrões que você aplica causam dor ou, na melhor das hipóteses, complexidade desnecessária.
Craig Larman fala de duas oportunidades para aplicar variações protegidas:
Ambos devem ser documentados pelos desenvolvedores, mas você provavelmente deve ter o compromisso do cliente com os pontos de variação.
Para gerenciar riscos, você pode dizer que qualquer padrão de design que aplique PV deve ser rastreado até um ponto de variação nos requisitos assinados pelo cliente. Se um cliente alterar um ponto de variação nos requisitos, seu design poderá ter que mudar radicalmente (porque você provavelmente investiu em design [padrões] para suportar essa variação). Não há necessidade de explicar coesão, acoplamento etc.
Por exemplo, seu cliente deseja que o software funcione com três sistemas de inventário herdados diferentes. Esse é um ponto de variação que você cria. Se o cliente abandonar esse requisito, é claro que você tem um monte de infraestrutura de design inútil. O cliente precisa saber que os pontos de variação custam algo.
CONSTANTS
no código fonte são uma forma simples de PVOutra analogia à sua pergunta seria perguntar se o uso
CONSTANTS
no código-fonte é uma boa idéia. Referindo-se a este exemplo , digamos que o cliente eliminou a necessidade de senhas. Assim, aMAX_PASSWORD_SIZE
propagação constante em todo o seu código se tornaria inútil e até um obstáculo à manutenção e legibilidade. Você culparia o usoCONSTANTS
como o motivo?fonte
Eu acho que pelo menos em parte depende da natureza da sua situação.
Você mencionou requisitos em constante mudança. Se o cliente disser "Eu quero que este aplicativo apícola também trabalhe com vespas", isso parece ser o tipo de situação em que um design cuidadoso ajudaria o progresso, não o impediria (especialmente quando você considera que no futuro ele pode querer mantenha moscas da fruta também.)
Por outro lado, se a natureza da mudança for mais parecida com "Eu quero que este aplicativo de apicultura gerencie a folha de pagamento do meu conglomerado de lavanderia", nenhuma quantidade de código o desenterrará.
Não há nada inerentemente bom nos padrões de design. São ferramentas como qualquer outra - apenas as usamos para facilitar nosso trabalho a médio e longo prazo. Se uma ferramenta diferente (como comunicação ou pesquisa ) é mais útil, então a usamos.
fonte