Reescrevendo o assembler da IBM + COBOL em C ++


13

Eu trabalho como agente / gerente de aluguel de uma empresa de aluguel de carros que está executando um sistema de aluguel escrito em 1972. Decidi que talvez fosse hora de uma atualização. Para um pouco de experiência, aqui está um pequeno exemplo da loucura com a qual devemos lidar diariamente com este programa:

Um agente de aluguel deve se lembrar de que a impressão em uma tela usa "MXC" no campo ACT (tudo é baseado em códigos de acesso), que significa perplexamente "MaXimum display on a Contract", enquanto em outra exige PR (for PRint) em o campo AÇÃO, mas várias telas usam um Y no campo PT (para PrinT); outra tela usa Y no campo PRT (para PRinT); outra tela exige que o usuário pressione enter (mas não o enter ao lado do letras, como esse é um novo caractere de linha, deve ser a tecla enter no teclado numérico) e, em seguida, F8, uma tela diferente, mas relacionada, exige apenas F8, algumas telas têm um campo chamado PRT, que deve ser para PRinT, mas o campo realmente não faz nada e a impressão é feita automaticamente após várias solicitações, e ainda mais telas têm um campo chamado PRINT S / N,cujo padrão é insanamente Y para operações em que outro local já esteja entregando papelada e N para operações nas quais outro revendedor precisará de papelada.

Decidi que poderia fazer um trabalho melhor do que isso, então decidi entrar em contato com a pessoa da empresa que tomaria a decisão de atualizar isso. Acabo entrando em contato com o vice-presidente de TI, responsável por este programa. Pego um pouco de informação com ele e soube que minha empresa de aluguel de carros tem seu programa de aluguel escrito na montadora de mainframe IBM com um pouco de COBOL misturado. Ele diz que não há vagas abertas no momento, mas que eu deveria e-mail meu currículo de qualquer maneira (no caso de algo se abre).

Isso me leva a minhas perguntas.

O primeiro é técnico. Com a idéia de melhorar a capacidade de manutenção no futuro, meu pensamento é reescrevê-lo em uma linguagem de nível superior à linguagem assembly. Minha área de experiência é em C ++, então essa é a escolha óbvia para mim. A empresa precisa urgentemente de uma maneira mais fácil de atualizar o programa, pois li recentemente um artigo em que o homem com quem falei é citado por dizer que a equipe trabalhou muito e eles têm orgulho de anunciar que o programa agora tem suporte para 5 códigos de localização de dois dígitos (em vez de 4) e números de carro com 8 dígitos (em vez de 7). Minha filosofia de atualização, mesmo em situações terríveis, está alinhada com a de Joel: http://www.joelonsoftware.com/articles/fog0000000069.html , resumindo, as reescrições devem ser incrementais, em vez de jogar fora tudo o que havia antes e começando de novo.

Existe uma maneira fácil de integrar o assembly IBM ao C ++ e, em caso afirmativo, como devo fazê-lo? Estou vagamente ciente da palavra-chave asm, mas não sei se é melhor usá-la ou fazer outra coisa. Esse plano não é aconselhável? Eu faço a maior parte do meu trabalho no Linux usando g ++ e GNU make, portanto respostas específicas são bem-vindas, mas definitivamente não são necessárias (já que não tenho idéia de que tipo de sistema de compilação elas não têm, mas suspeito que quase nenhuma).

A segunda pergunta é mais política. Como devo convencer essa empresa de que eles precisam fazer a troca? As economias de custo teóricas são enormes (com base em minhas estimativas, a empresa está gastando mais ou menos um milhão de dólares por ano, apenas com o aumento dos custos de treinamento para aprender a interagir com o programa), mas minhas alterações propostas provavelmente colocariam todas as programadores atuais sem trabalho, caso sejam promulgados, para que haja uma grande resistência estrutural à mudança.

editar: devo explicar por que modificar o que a empresa já tem parece a melhor solução para mim. Ainda estou aberto a outras sugestões, porque este é um monstro de um programa. Eu nunca tive um trabalho de programação antes, então, por favor, corrija-me em qualquer análise incorreta que eu possa fornecer.

Primeiro, existe a solução pronta para uso.

Nas minhas conversas com alguns gerentes de nível médio sobre esse tipo de coisa, uma das principais preocupações com a mudança para um novo sistema é o grande número de funcionários leais que estão na empresa há décadas e já se acostumam com o sistema. . Se eu tiver a capacidade de modificar o que temos, eu poderia manter a interface atual em uma espécie de 'modo de compatibilidade'. Os usuários já precisam fazer login para usar o sistema atual, para que eu possa adicionar a capacidade de ativar uma configuração quando os usuários fizerem login pela primeira vez (depois de fazer essa alteração), onde terão a opção de usar o interface 'clássica' ou a interface 'nova'. Não há como encontrar uma solução pronta para uso que permita isso,

Minha empresa também possui o software que usamos; nós não o licenciamos. Isso significa que a gerência com a qual estou conversando atualmente é a mesma pessoa que poderia me autorizar a fazer uma alteração. Com uma solução de terceiros, eu precisaria obter a aprovação da minha empresa, além de garantir quaisquer direitos necessários à empresa que desenvolveu o produto que usamos, o que adiciona um obstáculo adicional. Isso também exigiria convencer a empresa a desistir de "seu" produto e adquirir outro produto, o que parece ser um obstáculo maior do que tentar atualizar o que temos, mas eu poderia estar errado sobre esse assunto.

Finalmente, olhando para o futuro, não quero apenas melhorar a interface do usuário e corrigir alguns bugs. Depois de atualizar esses problemas 'urgentes', eu esperava atualizar o modo fundamental como a empresa funciona em relação à tecnologia. Depois de passar de um a dois anos nesse tipo de problema, meu plano era voltar à administração e propor mudanças mais drásticas. Há muitas maneiras pelas quais a empresa administra que poderiam ser fundamentalmente aprimoradas pela tecnologia que simplesmente não estão usando no momento. Por exemplo, cada região opera da mesma maneira. O principal aeroporto local é o centro da distribuição de carros. Eles são enviados principalmente conforme a necessidade. No entanto, o aeroporto é usado como base para todas as operações. Eles enviarão duas pessoas em um carro para minha localidade para buscar um carro que não precisamos, depois retorne ao aeroporto com o carro em que eles entraram, mais o que eles estão levando de volta (estamos a 52 quilômetros do aeroporto). Em seguida, eles chegarão ao local a 8 km de distância em dois carros para deixar um deles e depois voltarão no outro carro para o aeroporto. Eles fazem isso mesmo que o carro que devolvemos seja o mesmo tipo de carro que eles precisam perto de nós. Estou na empresa há cerca de dois anos e parece que eles se desviam disso nas emergências mais extremas da escassez de carros (cerca de três vezes). Substituiria as 4 pessoas que trabalham em todas as regiões por um sistema de agendamento automatizado que determina quais carros vão para onde e tentam encontrar o caminho que exige o menor tempo possível + milhas + motoristas para entregar todos os carros onde precisam, como exemplo das correções de nível superior que espero adicionar um dia.

No entanto, antes de me sentir à vontade em propor tudo isso, acho que seria útil conhecer a empresa e a base de código executando tarefas menores, como atualizar a interface. Soluções como terceirização ou de outra forma removeriam essa possibilidade.


3
Olhe para o emulador Hercules - há muita mágica no SO principal de um mainframe IBM que precisa ser emulado.
Yann Ramin

Eu honestamente olhava para "refazer desde o início" (piada ruim em C64). Sério, verifique as especificações e veja o que é necessário para escrever uma nova GUI. Contanto que a interface do banco de dados seja a mesma, e isso levará muito tempo e pesquisa para determinar, então você é ouro - e está na fila para ganhar uma carga de dinheiro em $ # && :)

8
Se o sistema atual realmente funcionar, você precisa ter uma boa razão para que isso seja recompensado pelo cliente. Além disso, é provável que você subestime severamente a quantidade de trabalho necessária para reproduzir o sistema atual - apenas pense que precisa entender completamente o sistema atual primeiro - tornando sua reescrita ainda mais cara que a sua estimativa original. Isso não é para desencorajá-lo, mas apenas para garantir que você realmente saiba qual tarefa hercúlea você pode estar se inscrevendo ANTES de não voltar atrás. Além disso, aumente o seu trabalho - em outras palavras, permaneça no mainframe por enquanto.

Receio que a emulação do sistema seja muito difícil, o que me levou a procurar uma maneira de fazer pequenas mudanças modulares, em vez de tentar começar do zero.
9608 David Stone

@ David Stone Eu já estive em um projeto que fizemos a coisa "selecione a nova ou a interface antiga". Foi rapidamente para o inferno do desenvolvimento - o código estava fortemente associado à interface e rapidamente o if (m_newInterface)código espaguete começou a aparecer em toda a base de código. A dissociação e a refatoração levaram tempo suficiente para que, quando isso fosse feito, a maioria dos usuários já tivesse migrado para a nova interface (pense em vários anos).
Vitor Py

Respostas:


4

Confinando-me à frente técnica ...

Sugiro que você comece determinando o ambiente em que o aplicativo é executado. "Mainframe" pode significar algumas coisas diferentes, pois a idade do aplicativo pode ser CICS ou IMS . Também é possível que os autores originais tenham escrito sua própria tarefa iniciada.

Dependendo de onde o aplicativo é executado, provavelmente fará uso de APIs para esse ambiente, o CICS agora usa uma interface marcadamente diferente de seus primeiros dias, não posso falar pelo IMS. Se o aplicativo for sua própria tarefa iniciada, ele poderá muito bem ter sua própria API interna - passo alguns anos suportando um animal, escrito na mesma época.

Outra coisa a considerar, dada a idade do aplicativo, é que o Assembler com o qual você deseja integrar o C ++ é anterior à existência do Language Environment (LE). Dessa forma, a menos que o Assembler tenha sido atualizado para estar em conformidade com LE, você terá alguma dificuldade, pois C e C ++ são compatíveis com LE e exigem que seus chamadores e calendários também estejam em conformidade.

Outro ponto a considerar: se esse aplicativo estiver sendo executado em um mainframe moribundo, você pode tentar integrar-se a um aplicativo que é executado em hardware e software não suportado. Não seria diferente tentar integrar-se a um aplicativo escrito em 1989 que roda no DOS 4.01 em seu hardware original.


O aplicativo pode até usar TPF , o que tornaria uma conversão extremamente difícil.
Gilbert Le Blanc

13

Você está pulando a arma. Pela sua descrição, parece que você não entendeu completamente o ambiente técnico atual (qual sistema operacional exatamente, onde e como os dados são armazenados, existem interfaces para outros sistemas etc.). Mas ainda mais importante: um plano sério deve considerar alternativas a uma reescrita interna parcial ou completa do software, ou seja, software de prateleira, terceirização da reescrita, software como serviço etc.

Qualquer que seja a forma como a empresa finalmente segue, primeiro precisa de uma análise sólida do que o software faz hoje e quais são os requisitos para a nova solução.

Então, por que você não oferece essa análise?


7
+1 Esta é a única resposta que propõe uma análise completa do aplicativo existente e das exigências da empresa antes de propor uma solução técnica. Me lembra a velha piada: você começa a codificar - vou descobrir o que eles querem.

Esse é um bom ponto, e eu definitivamente preciso de mais informações antes de me comprometer a fazer esse trabalho. Parte do problema é que perguntei por aí, e foi só quando cheguei a um vice-presidente da empresa que encontrei alguém que realmente sabia de todos os detalhes. Liguei para vários escritórios, incluindo suporte técnico, e nenhum deles poderia me fornecer detalhes como a linguagem de programação em que o programa foi escrito. No entanto, tenho alguns motivos para lançar uma solução pronta para uso, que editarei em minha pergunta original.
David Stone

7

Estou surpreso que você tenha recebido uma resposta tão educada!

Vamos analisar isso do ponto de vista deles.

Eles gerenciam com sucesso um sistema de trinta anos com provavelmente centenas de milhares de linhas de código, interfaces para talvez vinte outros sistemas que incorporam processos de negócios que evoluíram ao longo de quarenta anos.

Eles são provavelmente / esperançosamente especialistas em seu campo e, como tal, considerariam o C ++ um passo abaixo da "Linguagem Assembler de Alto Nível" da IBM, para lhe dar o título completo. A linguagem montadora da IBM é extremamente poderosa e a linguagem "MACRO" é a melhor implementação de uma linguagem de pré-processamento / modelo de todos os tempos.

Houve uma proposta para substituir o sistema a cada cinco anos desde que foi implementado pela primeira vez. Alguns teriam sido rejeitados após um estudo de viabilidade, outros teriam chegado ao estágio de design antes de perderem o orçamento, outros podem até ter entrado no código e talvez no estágio de teste antes de atingirem problemas de desempenho e serem enlatados.

C ++ simplesmente não ajudaria. Não há biblioteca de gráficos no ambiente em que o aplicativo é executado. (Existe uma biblioteca gráfica 3270, mas é improvável que ela seja suportada no C ++, pois ninguém a usa e existe uma biblioteca cliente "X" completa, mas você precisa estar em um ambiente diferente para usá-la.)

Eles têm uma interface de usuário que está longe de ser perfeita, mas que é bem conhecida e rápida. Um operador experiente preencherá um formulário cerca de três vezes mais rápido usando uma tela verde em vez de uma GUI equivalente. Além disso, eles podem prestar atenção ao cliente enquanto tocam no tipo.

Os operadores de tela precisarão treinar a interface que usarem, treinar todos os funcionários para usar uma GUI nova e desconhecida seria um item de orçamento enorme, em comparação com apenas treinar os novos funcionários no velho mundo.


4

Eu tive um problema semelhante há alguns anos no BellSouth. Eu simplesmente escrevi o código COBOL para verificar os programas em linguagem assembler byte a byte, separar os opcodes e operandos, converter instruções de ramificação em ir para tos e converter instruções de comparação em IFs. Este programa converteu instruções DC no código equivalente da Divisão de Dados COBOL e converte movimentos, zaps etc., conforme apropriado.

Feito isso, escrevi um segundo programa para converter IFs e GOTOs em IF-THENs, com movimentos e tais nas linhas entre esses clusters (isso não era realmente tão difícil de fazer - o segredo é inserir os IFs e ELSEs ao reler a primeira fase do "pidgin" cobol de trás para frente).

O IF-THEN-ELSER procurou situações nas quais encontrou uma referência GOTO próxima a um rótulo, que poderia ser excluído com segurança (diminuindo o número de referências em 1). Você executa esse programa IF-THEN-ELSER iterativamente (ou seja, executa-o repetidamente, parando apenas quando seu último passe não consegue encontrar uma maneira de fazer mais alterações). A maior parte do trabalho crítico é realizada por esse "se-então-outro", cujo produto COBOL deve ser cuidadosamente revisado e analisado por um programador experiente e competente em linguagem assembler (ou seja, eu). Na minha experiência, meu "if-then-elser" foi capaz de eliminar mais de 90% dos GO TOs resultantes das instruções originais da ramificação.

Suponho que seja possível avançar a partir deste ponto com uma conversão direta para C (ou C ++) - idiomas com os quais também estou familiarizado. No entanto, na BellSouth, nosso idioma de escolha era COBOL / II. Sempre existe um pouco de complexidade decorrente de situações em que um rótulo restante possui várias referências GO TO (muitas vezes, essas situações são tratadas por COBOL PERFORMs, mas às vezes podem ser simplesmente representadas pelas construções OR e AND).

Eu acho que é bom produzir código em COBOL / II elegantemente estruturado em bloco, com EVALUATEs (Case constrói) e 88 níveis de nomes de condição. A adição desses retoques finais levou a outro par de programas, que se mostraram úteis em programas COBOL não originários de programas convertidos do assembler.

Não sei se isso ajuda.

Como outros comentaram acima, você deve conduzir esse processo com cuidado. Ajuda a ter 10 a 12 anos de experiência em codificação de assembler, informando seus processos de tomada de decisão. Nós que temos essa experiência somos difíceis de encontrar mais.

Como nota final: escrevemos apenas em assembler na época porque os compiladores para as linguagens de alto nível produziam código de objeto pesado e ineficiente. Os "otimizadores" COBOL atuais de otimização não têm os mesmos maus hábitos. ----------


2

O conselho de Joel é geralmente bom, mas neste caso uma reescrita completa está atrasada. Idealmente, reescreva com um machado de batalha, pois o que você está lidando aqui é um monstro.

Não sei exatamente o que acrescentar, pois você já fez um bom argumento para reescrever a si mesmo. Minha única recomendação seria pular completamente o C ++ e ir direto para uma interface da web para o sistema de aluguel, pois provavelmente será ainda mais fácil treinar os funcionários e poupará muito trabalho na interface do usuário (além de facilitar é muito mais fácil obter sangue novo no projeto ou até terceirizar a coisa toda).

Quanto aos programadores COBOL existentes - talvez eles possam ajudar na documentação do sistema existente e no processo de migração.


2
+1: este não é um trabalho para C ++
6502 6/11

2
@ 6502: Por que não? A experiência do OP está em C ++. Descobrir como lidar com um sistema antigo já é ruim o suficiente. Aprender outro idioma não vai ajudar. Um programador competente que utilize as ferramentas em que o programador é competente poderá fazê-lo funcionar muito melhor do que o sistema antigo, independentemente do idioma.
In silico

1
@ Silico: Na minha opinião, para um projeto razoavelmente grande desse tamanho, você economizaria tempo aprendendo uma linguagem mais apropriada, como por exemplo, Python, do que usando C ++. Supondo que o Python não estivesse lá, para um projeto grande o suficiente, a IMO compensa escrever seu próprio Python em C ++ e codificá-lo usando isso em vez de fazer tudo em C ++. C ++ é uma linguagem muito agradável e seu ponto forte é que, por design, não deseja deixar nenhum espaço abaixo de C ++ e acima do assembler. Totalmente inútil aqui. Você sugeriria escrever tudo usando FPGAs se essa fosse a área de especialização do pôster?
6502

3
@ 6502: Eu discordo. Com uma técnica moderna e apropriada, bibliotecas bem projetadas e competência no idioma, todos esses problemas são irrelevantes. (Isso é verdade para qualquer linguagem, é claro.) E as pessoas desenvolveram sistemas e software de larga escala em C ++ que funcionam muito bem e não parecem ter problemas com o uso do C ++. Só porque você não usaria C ++ para este trabalho não significa que outras pessoas não seriam capazes de usá-lo e usá-lo bem. Isso é para o OP decidir. Tenho certeza de que você é bastante produtivo em outros idiomas e, por todos os meios, continue assim.
In silico

1
In silico: Claramente, qualquer coisa pode, em teoria, ser implementada com qualquer coisa (incluindo brainf k). Isso não significa que todas as ferramentas são equivalentes. Penso que, para uma aplicação comercial, ter um código simples de escrever e ler (mesmo por pessoas não técnicas) é muito mais importante que a velocidade de computação. Além disso, algo como comportamento indefinido é um preço alto demais para pagar pela proximidade desnecessária do metal. Se para você C ++ é uma boa escolha para a lógica de negócios de um pedido de entrada de dados, em seguida, gostaria de saber se para você Há algo ** para o qual C ++ é uma má escolha ...
6502

2

Quanto ao aspecto técnico, muito dependerá da qualidade - a modularidade - do código do montador. Alguns programadores entenderam a importância de criar módulos com funções limitadas e específicas e uma interface parametrizada clara e simples, usando convenções de ligação de sub-rotina IBM padrão. A grande maioria, no entanto, tendia a criar monstruosidades monolíticas.

Com o primeiro, a reutilização é possível e não deve ser tão difícil calcular a "cola" entre C ++ e assembler, assumindo que os módulos do assembler usem sequências de chamada padrão (ou pelo menos consistente). Com toda a probabilidade, no entanto, o código do assembler será uma bagunça. Embora fosse possível escrever um assembler estruturado, poucas pessoas o fizeram e será quase impossível discernir qualquer "design" do qual começar a reescrever.

Por outro lado, o montador 360/370 é de alto nível comparado aos microprocessadores atuais e muito mais simples, e C às vezes é considerado "montador de alto nível". Eu trabalhei para uma empresa DBMS cujo produto era inteiramente 370 montador, e nosso principal arquiteto acreditava que seria possível traduzir automaticamente o código do montador em C (para portabilidade futura). Eu sou cético, mas o ponto é que a distância não é tão grande quanto você imagina.

Não sei se isso é útil. Como eu disse, acho que depende muito da qualidade e tamanho do código original.


1

Não sei dizer se isso é uma piada ou não - sua empresa está executando seriamente um código originalmente escrito há 39 anos? Se estiver rodando em um System / 360, você precisará compilar o g ++ a partir da fonte ...

Honestamente, eu nem pensaria em usar nenhum código antigo; você precisa adotar uma nova abordagem, sentar e pensar no que precisa ser inserido no design e fazê-lo do zero. Sim, envolverá um bom planejamento, mas acho que nem Joel diria que esse é um caso de mudanças incrementais.

Quanto a convencer a empresa de que você precisa mudar, é necessário apontar razões específicas que melhorarão a eficiência, levarão a custos mais baixos e / ou mostrarão que o que você está usando agora está prejudicando os resultados financeiros no momento.

Outro comentário - imagino que outras empresas de aluguel de carros se juntaram a nós no século XXI; Eu faria um pouco de reconhecimento para ver como eles estão fazendo isso (baseado na Web, eu imagino), e possivelmente modelá-lo após um desses sistemas (ou seja, não reinvente a roda).

Boa sorte!


5
Não é incomum que os sistemas mainframe usem bases de código iniciadas nos anos 60 ou 70. A IBM mantém seus mainframes do zSeries e o sistema operacional compatível com o 360 apenas por causa disso. Não há muito no modelo de negócios de uma empresa de aluguel de carros (ou de um banco ou de uma companhia de seguros) que tenha sido alterada desde então. Você pode adicionar uma interface da Web, com certeza, mas o que mudou na maneira como você armazena os carros no banco de dados?
Bo Persson

O zOS possui um compilador C ++ nativo completo com pré-processadores para CICS e DB2. Portanto, não há necessidade de g ++.
James Anderson

5
Em segundo lugar, é bastante comum que grandes empresas executem código de mainframe de 30 anos. Geralmente é confiável, tem desempenho e faz o trabalho. Também tende a ser muito mal documentado.
James Anderson

3
@ Chris, por que o código de 39 anos não deve ser executado? Cresce obsoleto aos 30 anos? 20? O código de produção testado em batalha pode ser antigo, mas ainda faz o trabalho perfeitamente. Qualquer reescrita precisa atingir o mesmo nível que o programa antigo antes de ter algum benefício para quem paga suas taxas.

1
@ Chris, muito pouco é mais rápido que um aplicativo bem escrito de "tela verde", e eu sei disso por experiência pessoal, sendo o cara de Java em uma loja da Cobol. Sinceramente, acredito que todos subestimam a quantidade de trabalho necessária para ter uma aplicação semelhante. Além disso, o código não enferruja. Apenas ser velho não é motivo suficiente para consertá-lo.

0

Em teoria, não deve ser difícil convencer a alta gerência a substituir o sistema antigo, se você puder mostrá-lo que isso economizará muito dinheiro. E vai. Ficará cada vez mais difícil encontrar programadores para manter esse sistema, e esses programadores ficarão cada vez mais caros. Não é uma questão de "se", é uma questão de "quando". Realmente tem que ser atualizado.

No entanto, a questão é se você pode convencê-los não apenas de que precisa ser atualizado (e provavelmente sabem disso de qualquer maneira), mas de que deve ser um projeto interno. Especialmente se a equipe for apenas você. Muitas vezes, uma substituição tão importante é terceirizada. Pode até haver algum software disponível para consideração. Essas opções precisam ser investigadas e seus gerentes insistirão em que isso aconteça se forem sensatos.

Quanto à tarefa, se você o fizer, acredito que uma reescrita de bulldoze pode ser apropriada nesse caso. O argumento de Joel não se aplica em todos os casos. No entanto, eu realmente não poderia saber sem vê-lo.


0
  1. Uma mudança incremental está fora de questão.

  2. É provável que exista uma solução disponível no mercado. Existem muitas empresas de aluguel de carros.

  3. Se, como último recurso, você precisar reescrevê-lo, primeiro gaste algum tempo pensando em como o novo sistema funcionará.
    Depois de identificar a funcionalidade e as interfaces, você poderá escolher as ferramentas de desenvolvimento que melhor se ajustam às necessidades (e suas habilidades).

Uma estratégia para minimizar o estresse para os usuários finais é começar a emular a interface feia antiga que os usuários conhecem, com algumas gentilezas, como listas suspensas para mnemônicas feias, depois adicionar gradualmente a funcionalidade da interface do usuário e desmaiá-la para uma interface moderna.

Você provavelmente não deseja manter o mesmo formato de arquivo de dados; portanto, não haverá retorno quando eles mudarem.


1
Se você não fizer isso de forma incremental, o projeto provavelmente falhará.
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.