Devemos evitar o uso de padrões de design em projetos em constante mudança?

32

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 ou product.Description.Currentmostrar 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, exibindo nullJSON. 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?

Arseni Mourzenko
fonte
43
Eu acho que esse é um dilema falso. Pela admissão do seu amigo, o código agora é um prato de espaguete não sustentável. Isso não é culpa dos padrões de software; é a incapacidade de seu amigo de usar esses padrões adequadamente, de maneira a aumentar a capacidade de manutenção e não diminuí-la. Quando eu puder apresentar exemplos específicos, postarei uma resposta adequada.
Robert Harvey
5
Além disso, FWIW, qualquer cliente que não tenha alguma tolerância para os custos de derivação provavelmente não deve estar fazendo o Agile, a menos que haja permissões incluídas nos custos para mudar os requisitos.
Robert Harvey
5
Eu suspeito que, sem os padrões de design, o código teria atingido um estado insustentável muito mais cedo
Steven A. Lowe
28
Esta pergunta não faz nenhum sentido. A única maneira de "evitar padrões de design" é não escrever nenhum software. Coisas como "XYZ Pattern" são apenas nomes dados a estratégias comuns de codificação para permitir que os programadores transmitam informações e conselhos sobre nossa estrutura de código e opções entre si de forma mais conveniente. Qualquer opção de design em seu código pode receber um nome e ser chamado de "padrão de design", embora não seja necessariamente conhecido (a menos que, suponho, você tenha orgulho do seu design exclusivo e esteja suficientemente motivado para dar um nome e um blog sobre ou algo assim).
Jason C
9
Ou seja, você pode evitar chamar seu pé de "pé", mas você ainda o tem, é mais difícil falar sobre isso com alguém se você não o chamar de "pé". Você pode pensar que está "evitando padrões de design", mas, se criar um design decente que funcione, dê um passo atrás e dê uma olhada, provavelmente descobrirá que seu design acabou se encaixando em um dos padrões nomeados comuns, quer você chame assim ou não.
Jason C

Respostas:

86

Vejo algumas suposições erradas nesta pergunta:

  • código com padrões de design, embora aplicado corretamente, precisa de mais tempo para ser implementado do que código sem esses padrões.

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.

  • o cliente é o culpado porque não possui formação técnica e não se preocupa com a coesão ou a coerência do produto

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.).

  • a solução para os requisitos de mudança rápida é alterar rapidamente o código

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.

Doc Brown
fonte
30
+1 - você não pode resolver problemas de pessoas com soluções técnicas.
Telastyn
1
+1, mas "ou pelo menos melhor evolutível (isso significa: mais fácil de ser adaptado às mudanças nos requisitos)" - eu qualificaria isso com mudanças razoáveis ​​nos requisitos, certo?
Fuhrmanator
1
@ Fuhrmanator: Eu acho que isso é difícil de discutir em termos gerais. IMHO, é óbvio que nenhum padrão de design o ajudará quando seu primeiro requisito for "precisamos de um processador de texto" e isso muda para "precisamos de um simulador de vôo". Para mudanças menos drásticas nos requisitos, nem sempre é fácil decidir o que o ajudará a manter seu software evolutivo. A melhor coisa é IMHO para não aplicar muitos padrões, mas alguns princípios - principalmente os princípios SOLID e YAGNI. E eu concordo 100% com Telastyn - quando os requisitos mudam com muita frequência, provavelmente não é um problema técnico.
Doc Brown
4
+1 - "Honestamente, se '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. "Se você recebe constantemente novas orientações, é melhor sentar-se com todas as partes interessadas e reafirmar quais são as expectativas. Resolva suas diferenças e, esperançosamente, não perca tempo e dinheiro de todos, dando ao projeto um objetivo mais claro.
krillgar
1
@Cornelius Doc Brown disse que é difícil sem concretude. Os requisitos que vão de um processador de texto a um simulador de vôo não seriam razoáveis; nenhum padrão de design ajudaria. Há muita área cinza entre o exemplo dele e, digamos, adicionar um novo formato de arquivo à função Salvar do processador de texto (o que é bastante razoável). Sem detalhes, é difícil discutir. Além disso, não é que alguém não queira mudar. É que essas mudanças são difíceis, se você já fez uma escolha de design com base em um requisito. O processador de texto vs. o flight sim é um ótimo exemplo de escolha antecipada.
Fuhrmanator
43

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.

Aviv Cohn
fonte
13
+1 "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 em termos técnicos do que qualquer outra solução ou design que você possa imaginar." Exatamente. As pessoas ficam tão envolvidas em padrões de design nomeados que esquecem que nada mais são do que nomes dados a várias estratégias para tornar mais fácil para nós programadores nos comunicarmos sobre nosso código e nossas escolhas de design. Esta atitude é muito frequentemente associado com a tentativa de forçar "padrões" inapropriados em problemas que não beneficiam necessariamente - grande confusão.
Jason C
14

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?

BobDalgleish
fonte
9

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.

BillThor
fonte
2
Também poderia indicar, visto que "é necessário" violar a lei de Deméter, que o modelo usado na superfície era inadequado. Por que o "modelo de visualização" (usado de maneira genérica) não possui apenas a descrição para exibir? (Ou seja, por que há mais do que apenas a descrição atual no nível da interface do usuário?) A camada de negócios pode preparar um objeto adequadamente preenchido para a camada da interface do usuário que já possui conteúdo diferente, dependendo se é o gerente ou não.
Cornelius
7

A questão parece estar errada em muitos pontos. Mas os flagrantes são:

  • Para o Padrão de Objeto Nulo que você mencionou, após a alteração dos requisitos, você altera um pouco do código. Tudo bem, mas isso não significa que você 'assassinou' o Padrão de Objeto Nulo (entre, tenha cuidado com a sua redação, isso soa muito extremo, algumas pessoas paranóicas demais não verão isso engraçado).

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)

  • O design do qual você falou é ruim porque, quando as mudanças nos requisitos são pequenas, você faz grandes mudanças no design estratégico em todos os lugares. A menos que o problema de negócios de alto nível mude, você não pode justificar a realização de grandes mudanças no design.

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.

InformedA
fonte
7

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.

Alex
fonte
+1 Estes são pensamentos muito bons sobre o nível muito alto, acho que você deve procurar em um sistema, mas, como você disse, requer muita experiência em design de software.
Samuel
4

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.Priceinstância (chamarei de Priceobjeto 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. Prices têm um ToStringmétodo que gera o preço ou a sequência vazia, se não for conhecida (ou NullPrice#ToStringretorna uma sequência vazia). Este é um comportamento razoável. Então os requisitos mudam.

Temos que gerar a nullpara 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 ifdeclaraçã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:

if(product.Price.ToString("c").Length == 0) { // one way of many
    writer.write("Price unspecified [Change]");
} else {
    writer.write(product.Price.ToString("c"));
}

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#ToStringmé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:

class Price {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return ToString(c);
    }
    ...
}

class NullPrice {
    ...
    // A new ToString method
    public string ToString(string c, string default) {
        return default;
    }
    ...
}

E agora nosso código de visualização se torna:

writer.write(product.Price.ToString("c", "Price unspecified [Change]"));

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 IsSetmétodo Priceque retorne um booleano:

class Price {
    ...
    public bool IsSet() {
        return return true;
    }
    ...
}

class NullPrice {
    ...
    public bool IsSet() {
        return false;
    }
    ...
}

Ver lógica:

if(product.Price.IsSet()) {
    writer.write(product.Price.ToString("c"));
} else {
    writer.write("Price unspecified [Change]");
}

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#IsSetoutro 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:

class PriceStringHelper {
    public PriceStringHelper() {}

    public string PriceToString(Price price, string default) {
        if(price.IsSet()) { // or use string length to not change the Price class at all
           return price.ToString("c");
        } else {
            return default;
        }
    }
}

Ver lógica:

writer.write(new PriceStringHelper().PriceToString(product.Price, "Price unspecified [Change]"));

Existem muitas outras maneiras de fazer as alterações (poderíamos generalizar PriceStringHelperem 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.

cbojar
fonte
3

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:

  • pontos de variação - no sistema ou nos requisitos atuais existentes, como várias interfaces que devem ser suportadas e
  • pontos de evolução - pontos de variação especulativos que não estão presentes nos requisitos existentes.

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 PV

Outra analogia à sua pergunta seria perguntar se o uso CONSTANTSno código-fonte é uma boa idéia. Referindo-se a este exemplo , digamos que o cliente eliminou a necessidade de senhas. Assim, a MAX_PASSWORD_SIZEpropagação constante em todo o seu código se tornaria inútil e até um obstáculo à manutenção e legibilidade. Você culparia o uso CONSTANTScomo o motivo?

Fuhrmanator
fonte
2

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.

Benjamin Hodgson
fonte