Como manter versões diferentes e personalizadas do mesmo software para vários clientes


46

Temos vários clientes com diferentes necessidades. Embora nosso software seja modularizado até certo ponto, é quase certo que precisamos ajustar a lógica de negócios de cada módulo aqui e ali um pouco para cada cliente. As mudanças são provavelmente muito pequenas para justificar a divisão do módulo em um módulo (físico) distinto para cada cliente. Receio problemas com a construção, um caos de ligação. No entanto, essas alterações são muito grandes e muitas para configurá-las por comutadores em algum arquivo de configuração, pois isso levaria a problemas durante a implantação e provavelmente a muitos problemas de suporte, especialmente com administradores do tipo funileiro.

Eu gostaria que o sistema de compilação criasse várias compilações, uma para cada cliente, onde as alterações estão contidas em uma versão especializada do único módulo físico em questão. Então, eu tenho algumas perguntas:

Você recomendaria deixar o sistema de compilação criar várias compilações? Como devo armazenar as diferentes personalizações no controle de origem, svn em particular?


4
Funciona #ifdefpara você?
21411 ohho

6
As diretivas de pré-processador podem rapidamente se tornar muito complicadas e dificultar a leitura do código e a depuração.
Falcon

1
Você deve incluir detalhes sobre a plataforma real e o tipo de aplicativo (desktop / web) para obter melhores respostas. A personalização em um aplicativo C ++ para desktop é completamente diferente de um aplicativo Web PHP.
GrandmasterB

2
@ Falcon: desde que você escolheu em 2011 uma resposta sobre a qual tenho muitas dúvidas, você pode nos dizer se você tem alguma experiência com o uso do SVN da maneira sugerida? Minhas objeções são infundadas?
Doc Brown

2
@ Doc Brown: a ramificação e fusão foi tediosa e complicada. Naquela época, usamos um sistema de plug-in com plug-ins específicos do cliente ou "patches" que alteravam o comportamento ou as configurações. Você terá alguma sobrecarga, mas poderá ser gerenciada com a Dependendy Injection.
Falcon

Respostas:


7

O que você precisa é de uma organização de código semelhante a ramificação . Em seu cenário específico, isso deve ser chamado de ramificação de tronco específica do cliente, porque você provavelmente usará também a ramificação de recursos enquanto desenvolve novas coisas (ou resolve bugs).

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

A idéia é ter um tronco de código com ramificações de novos recursos mesclados. Quero dizer todos os recursos não específicos do cliente.

Além disso, você também possui suas ramificações específicas do cliente, onde também mescla as ramificações dos mesmos recursos, conforme necessário (escolha a cereja, se desejar).

Essas ramificações de clientes são semelhantes às ramificações de recursos, embora não sejam temporárias ou de curta duração. Eles são mantidos por um longo período de tempo e, principalmente, apenas são mesclados. Deve haver o mínimo possível de desenvolvimento de ramificação de recursos específicos do cliente.

As ramificações específicas do cliente são ramificações paralelas ao tronco e estão ativas desde que o próprio tronco e nem sequer são mescladas como um todo em qualquer lugar.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´

7
+1 para ramificação de recursos. Você também pode usar uma ramificação para cada cliente. Eu gostaria de sugerir apenas uma coisa: usar um VCS distribuídos (hg, git, BZR) em vez de SVN / CVS para alcançar este objetivo;)
Herberth Amaral

43
-1. Não é para isso que servem os "ramos de recursos"; contradiz sua definição como "ramificações temporárias que se fundem quando o desenvolvimento de um recurso termina".
precisa

14
Você ainda precisa manter todos esses deltas no controle de origem e mantê-los alinhados - para sempre. A curto prazo, isso pode funcionar. A longo prazo, pode ser terrível.
quickly_now

4
-1, este não é um problema de nomenclatura - o uso de ramificações diferentes para clientes diferentes é apenas outra forma de duplicação de código. Este é IMHO um anti-padrão, um exemplo de como não fazê-lo.
Doc Brown

7
Robert, acho que entendi muito bem o que você sugeriu, mesmo antes da edição, mas acho que essa é uma abordagem horrível. Suponha que você tenha N clientes, sempre que você adicionar um novo recurso principal ao tronco, parece que o SCM facilitará a propagação do novo recurso para os N ramos. Mas o uso de ramificações dessa maneira facilita demais evitar uma separação clara das modificações específicas do cliente. Como resultado, agora você tem N chances de obter um conflito de mesclagem para cada alteração no tronco. Além disso, agora você precisa executar N testes de integração em vez de um.
Doc Brown

38

Não faça isso com as ramificações do SCM. Faça do código comum um projeto separado que produza uma biblioteca ou artefato de esqueleto do projeto. Cada projeto do cliente é um projeto separado que depende do comum como uma dependência.

Isso é mais fácil se o aplicativo base comum usar uma estrutura de injeção de dependência como o Spring, para que você possa injetar facilmente diferentes variantes de substituição de objetos em cada projeto do cliente, pois são necessários recursos personalizados. Mesmo se você ainda não possui uma estrutura de DI, adicionar uma e fazê-lo dessa maneira pode ser sua escolha menos dolorosa.


Outra abordagem para simplificar as coisas é usar herança (e um único projeto) em vez de usar injeção de dependência, mantendo o código comum e as personalizações no mesmo projeto (usando herança, classes parciais ou eventos). Com esse design, as ramificações do cliente funcionariam bem (conforme descrito na resposta selecionada), e sempre era possível atualizar as ramificações do cliente atualizando o código comum.
Drizin 17/09/16

Se não sinto falta de algo, o que você sugere é que, se você tiver 100 clientes, criará (100 x NumberOfChangedProjects ) e usará o DI para gerenciá-los? Se é que assim, eu seria definitivamente ficar longe deste tipo de solução desde manutenção seria terrível ..
SOTN

13

Armazene o software para todos os clientes em uma única filial. Não há necessidade de divergir as alterações feitas para diferentes clientes. Muito provavelmente, você desejará que seu software seja da melhor qualidade para todos os clientes, e a correção de erros em sua infraestrutura principal deve afetar todos sem sobrecarga desnecessária de mesclagem, o que também pode apresentar mais erros.

Modularize o código comum e implemente o código que difere em arquivos diferentes ou proteja-o com definições diferentes. Faça seu sistema de construção ter destinos específicos para cada cliente, cada destino compilando uma versão com apenas o código relacionado a um cliente. Se o seu código for C, por exemplo, convém proteger os recursos de clientes diferentes com " #ifdef" ou qualquer outro mecanismo que seu idioma tenha para gerenciar a configuração de configuração e preparar um conjunto de definições correspondentes à quantidade de recursos pelos quais um cliente pagou.

Se você não gosta de ifdefs, use "interfaces e implementações", "functors", "arquivos de objetos" ou qualquer outra ferramenta que sua linguagem ofereça para armazenar coisas diferentes em um só lugar.

Se você distribuir fontes para seus clientes, é melhor fazer com que seus scripts de construção tenham "destinos de distribuição de origem" especiais. Depois que você invoca esse destino, ele cria uma versão especial das fontes do seu software, as copia para uma pasta separada para que você possa enviá-las e não as compila.


8

Como muitos declararam: fatore seu código corretamente e personalize com base no código comum - isso melhorará significativamente a manutenção. Esteja você usando ou não uma linguagem / sistema orientado a objetos, isso é possível (embora seja um pouco mais difícil fazer em C do que algo realmente orientado a objetos). Este é precisamente o tipo de problema que a herança e o encapsulamento ajudam a resolver!


5

Muito cuidado

A ramificação de recursos é uma opção, mas acho que é um pouco pesada. Além disso, facilita modificações profundas, o que pode levar a uma bifurcação total da sua aplicação, se não for mantida sob controle. Idealmente, você deseja aumentar o máximo possível as personalizações na tentativa de manter sua base de códigos principal o mais comum e genérica possível.

Aqui está como eu faria isso, embora não saiba se é aplicável à sua base de código sem grandes modificações e redimensionamentos. Eu tinha um projeto semelhante em que a funcionalidade básica era a mesma, mas cada cliente exigia um conjunto muito específico de recursos. Criei um conjunto de módulos e contêineres que monto por meio da configuração (à la IoC).

então, para cada cliente, criei um projeto que basicamente contém as configurações e o script de construção para criar uma instalação totalmente configurada para o site. Ocasionalmente, coloco também alguns componentes personalizados para esse cliente. Mas isso é raro e, sempre que possível, tento torná-lo de uma forma mais genérica e pressioná-lo para que outros projetos possam usá-los.

O resultado final é que eu tenho o nível de personalização necessário, scripts de instalação personalizados para que, quando eu chegue ao site do cliente, não pareço mexer no sistema o tempo todo e, como um bônus MUITO significativo adicional, recebo para poder criar testes de regressão conectados diretamente à compilação. Dessa forma, sempre que recebo um bug específico do cliente, posso escrever um teste que afirme o sistema à medida que ele é implantado e, portanto, possa fazer TDD mesmo nesse nível.

então, resumindo:

  1. Sistema fortemente modularizado com uma estrutura de projeto plana.
  2. Crie um projeto para cada perfil de configuração (cliente, embora mais de um possa compartilhar um perfil)
  3. Monte os conjuntos de funcionalidades necessárias como um produto diferente e trate-o como tal.

Se feito corretamente, a montagem do produto deve conter todos os arquivos de configuração, exceto alguns.

Depois de usar isso por um tempo, acabei criando meta-pacotes que montam os sistemas mais utilizados ou essenciais como uma unidade principal e usam esse meta-pacote para montagens de clientes. Depois de alguns anos, acabei tendo uma grande caixa de ferramentas que pude montar muito rapidamente para criar soluções para os clientes. Atualmente, estou analisando o Spring Roo e ver se não consigo avançar um pouco mais com a idéia, esperando que um dia eu possa criar um primeiro rascunho do sistema diretamente com o cliente em nossa primeira entrevista ... Acho que você poderia chamá-lo de Orientado pelo Usuário Desenvolvimento ;-).

Espero que isso tenha ajudado


3

As mudanças são provavelmente muito pequenas para justificar a divisão do módulo em um módulo (físico) distinto para cada cliente. Receio problemas com a construção, um caos de ligação.

OMI, eles não podem ser muito pequenos. Se possível, eu fatoraria o código específico do cliente usando o padrão de estratégia em praticamente todos os lugares. Isso reduzirá a quantidade de código que deve ser ramificada e reduzirá a mesclagem necessária para manter o código geral sincronizado para todos os clientes. Também simplificará o teste ... você pode testar o código geral usando estratégias padrão e testar as classes específicas do cliente separadamente.

Se seus módulos forem codificados de forma que o uso da política X1 no módulo A exija o uso da política X2 no módulo B, pense em refatorar para que X1 e X2 possam ser combinados em uma única classe de política.


1

Você pode usar seu SCM para manter ramificações. Mantenha a ramificação principal intocada / limpa do código personalizado do cliente. Faça o desenvolvimento principal neste ramo. Para cada versão customizada do aplicativo, mantenha ramificações separadas. Qualquer boa ferramenta SCM se sairá muito bem com a junção de ramificações (o Git vem à mente). Quaisquer atualizações na ramificação mestre devem ser mescladas nas ramificações personalizadas, mas o código específico do cliente pode permanecer em sua própria ramificação.


Embora, se possível, tente projetar o sistema de maneira modular e configurável. A desvantagem dessas ramificações personalizadas pode ser que ela se afasta muito do núcleo / mestre.


1

Se você está escrevendo em C simples, aqui está uma maneira bastante feia de fazê-lo.

  • Código comum (por exemplo, unidade "frangulator.c")

  • código específico do cliente, pedaços pequenos e usados ​​apenas para cada cliente.

  • no código da unidade principal, use #ifdef e #include para fazer algo como

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
#fim se

Use isso como um padrão repetidamente em todas as unidades de código que requerem customização específica do cliente.

Isso é MUITO feio e leva a alguns outros problemas, mas também é simples, e você pode comparar arquivos específicos de clientes um contra o outro com bastante facilidade.

Isso também significa que todas as partes específicas do cliente estão claramente visíveis (cada uma no próprio arquivo) o tempo todo, e há um relacionamento claro entre o arquivo de código principal e a parte específica do cliente.

Se você for realmente inteligente, poderá configurar makefiles para criar a definição correta do cliente, para algo como:

faça clienta

criará para client_a e "make clientb" criará para client_b e assim por diante.

(e "make" sem destino fornecido pode emitir uma descrição de aviso ou uso.)

Eu usei uma idéia semelhante antes, leva um tempo para configurar, mas pode ser muito eficaz. No meu caso, uma árvore de origem criou cerca de 120 produtos diferentes.


0

No git, a maneira que eu faria é ter uma ramificação mestre com todo o código comum e ramificações para cada cliente. Sempre que uma alteração no código principal é feita, basta refazer todas as ramificações específicas do cliente por cima do mestre, para que você tenha um conjunto de correções móveis para os clientes aplicados na linha de base atual.

Sempre que você estiver fazendo uma alteração em um cliente e perceber um bug que deve ser incluído em outras ramificações, você pode selecioná-la no mestre ou nas outras ramificações que precisam da correção (embora diferentes ramificações do cliente estejam compartilhando código) , você provavelmente deve ter os dois ramificando um ramo comum do mestre).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.