Universidade Federal de Santa Catarina Departamento de Informática e Estatística Curso de Sistemas de Informação Language Oriented Programming Análise do Conceito e Estudo de Caso GUILHERME PIRES GUSTAVO ROYER CHAURAIS Florianópolis – SC Ano 2007 / 1 GUILHERME PIRES GUSTAVO ROYER CHAURAIS Language Oriented Programming Análise do Conceito e Estudo de Caso Trabalho de conclusão de curso apresentado como parte dos requisitos para obtenção do grau de Bacharel em Sistemas de Informação Orientador: Prof. Olinto José Varela Furtado Banca Examinadora José Eduardo De Lucca Ricardo Azambuja Silveira Resumo Analisando a evolução das linguagens de programação, desde linguagens binárias às mais atuais, um dos pontos observados mais importantes refere-se à sua crescente facilidade de uso. Conseqüentemente, ao aumento do número de pessoas capazes de escrever software. O ato de programar computadores vem se tornando cada vez mais inteligível por uma gama maior de usuários e isso resulta na maior facilidade em relacionarmos o mundo a nossa volta com o que estamos desenvolvendo. Utilizando conceitos existentes e baseados nas fortes tendências da área, estudiosos propuseram a criação de um novo paradigma, a Programação Orientada a Linguagens, o qual tem como principal objetivo fazer com que o computador consiga entender nossa intenção, em vez de termos de mostrar para ele quais as seqüências de instruções a serem seguidas para a execução do programa. Este trabalho visa analisar a real eficácia e a eficiência de tal metodologia em relação às outras, mais antigas. Para tanto, um projeto é escrito utilizando-se das técnicas propostas e pontos positivos e negativos são levantados. Deste modo, esta é uma contribuição inicial para o desenvolvimento de futuros trabalhos na área. Palavras-chave: Language Oriented Programming, Domain-Specific Languages, Cálculo de Folha de Pagamento, Paradigmas de Programação, Desenvolvimento de Software, Programação Orientada a Objetos Abstract By analyzing the evolution of the programming languages, from binary code to the newest ones, it’s not hard to see their improvement regarding the ease of use. Therefore, it leads to an increasing amount of people able to write their own software. The act of programming computers has became more understandable for programmers and non-programmers and that results in an easier way of relating the world we live to what we are developing. The use of existing concepts, based on the newest trends in software development, has made people purpose the creation of a new paradigm, the Language Oriented Programming. Its main goal would be to have the computer understanding our intention, instead of us trying to figure out a way of telling it the instruction sequences to be followed for the program execution. With this document, we intend to analyze the real effectiveness and efficiency of that methodology when compared to older ones. For that, a project is written with the purposed techniques and positive and negative points are studied. Thus, this is an initial contribution for the development of future works in this area. Keywords: Languages, Payroll Language Oriented Calculation, Programming, Programming Development, Object Oriented Programming Domain-Specific Paradigms, Software Sumário 1 Introdução ...................................................................................................... 9 2 Fundamentos Teóricos ................................................................................. 12 2.1 DSL – Domain-Specific LanguageS ................................................... 12 2.2 Intentional Programming .................................................................... 16 2.3 Generative Programming ................................................................... 19 2.4 Software Factories ............................................................................. 23 2.5 Model-Driven Architecture.................................................................. 24 3 Language Oriented Programming ................................................................ 26 4 Desenvolvimento – Estudo de Caso ............................................................ 30 4.1 Escopo do Projeto.............................................................................. 30 4.2 Construção da versão Orientada a Objetos ....................................... 41 4.2.1 Escolhendo os elementos necessários ....................................... 41 4.2.2 Implementando o projeto principal .............................................. 44 4.2.3 Implementando o projeto de interface gráfica ............................. 51 4.3 Desenvolvimento da versão Orientada a Linguagens........................ 55 4.3.1 Escolhendo os elementos necessários ....................................... 55 4.3.2 Aprendendo a utilizar a ferramenta............................................. 56 4.3.3 Mudanças no Projeto Inicial ........................................................ 57 4.3.4 Utilizando o Microsoft DSL Tools ................................................ 60 4.3.5 Criação da Primeira Domain-Specific Language – Payrule ........ 66 4.3.6 Criação das Regras utilizando a linguagem Payrule .................. 82 4.3.7 Criação da Segunda Domain-Specific Language – Payxport ..... 83 4.3.8 Criação dos Exportadores utilizando a linguagem Payxport ....... 90 4.3.9 Integrando os projetos ................................................................ 91 4.4 Análise Comparativa .......................................................................... 92 4.5 Ferramentas e Métodos Utilizados nos Projetos................................ 98 4.5.1 Test-Driven Development ........................................................... 98 4.5.2 Model View Controller – MVC ................................................... 104 4.5.3 .NET Framework ....................................................................... 105 5 Conclusões ................................................................................................. 108 6 Referências ................................................................................................ 110 7 Anexos ....................................................................................................... 112 7.1 Projeto PayrollCalc .......................................................................... 112 7.2 Projeto PayrollCalc.Regras (O.O.) ................................................... 123 7.3 Projeto PayrollCalc.Exportacao (O.O.) ............................................ 131 7.4 Projeto PayrollCalcTests .................................................................. 134 7.5 Projeto PayrollInterface .................................................................... 168 7.6 Projeto Payrule (LOP) ...................................................................... 176 7.7 Projeto Payxport (LOP) .................................................................... 194 Índice de Listagens Listagem 1 – Conceitos do sistema de folhas de pagamento .......................... 32 Listagem 2 – Regras para o cálculo de cada funcionário ................................. 37 Listagem 3 - Exemplificação das regras ........................................................... 39 Listagem 4 - Arquivos de Exportação ............................................................... 41 Listagem 5 - Elementos de Payrule e seus detalhes ........................................ 74 Listagem 6 - Shapes de Payrule ...................................................................... 76 Listagem 7 - Validações de Payrule ................................................................. 78 Listagem 8 - Exemplos de uso de Payrule ....................................................... 79 Listagem 9 - Elementos de Payxport ................................................................ 86 Listagem 10 - Shapes de Payxport .................................................................. 87 Listagem 11 - Validações de Payxport ............................................................. 89 Listagem 12 - Exemplos de uso de Payxport ................................................... 89 Índice de Figuras Figura 1 - Análise de Produtividade ................................................................. 26 Figura 2 - Inicio do desenvolvimento pelos testes ............................................ 45 Figura 3 - Todos os 84 testes passando .......................................................... 49 Figura 4 - Diagrama de classes gerado automaticamente ............................... 51 Figura 5 - Visualização da Interface Gráfica..................................................... 53 Figura 6 - Assistente de importação de projetos do Visual Studio ................... 58 Figura 7 - Testes passando na versão 2.0 do .NET Framework ...................... 59 Figura 8 - Início da criação de uma Domain-Specific Language ...................... 61 Figura 9 - Domain-Specific Language pronta para ser criada .......................... 62 Figura 10 - Driagrama da Domain-Specific Language sendo editado .............. 63 Figura 11 - Decoradores sendo editados ......................................................... 64 Figura 12 - Elementos da Toolbar sendo adicionados ..................................... 65 Figura 13 - Utilização da Domain-Specific Language....................................... 65 Figura 14 - Toolbox gerada para a linguagem Payrule .................................... 77 Figura 15 - Programa rodando com base nos resultados das DSLs ................ 92 Figura 16 - Bandeira vermelha representando que o teste não passou ......... 100 Figura 17 - Bandeira verde indicando que o teste passou ............................. 101 Índice de Tabelas Tabela 1 - Comparação entre os paradigmas .................................................. 96 9 1 INTRODUÇÃO Quando analisamos a história da programação de computadores, podemos observar um ponto bastante interessante: vagarosamente, a cada nova linguagem, a cada novo estilo de programação, a cada novo paradigma, o número de programadores aumenta consideravelmente. É impossível compararmos a facilidade que é hoje programar em uma linguagem de alto nível, como Delphi, Java ou C#, à antiga programação em linguagens de montagem. O percentual de pessoas que conseguem realizar essa tarefa é muito maior. Uma das grandes barreiras ao desenvolvimento de software sempre esteve ligada ao seguinte fato: como fazer o computador entender exatamente o que estamos pensando, em vez de escrevermos programas da maneira que o processador entende (DMITRIEV, 2005). Ou seja, será possível confeccionarmos software diretamente em nossa linguagem natural, em vez de perdermos tempo transcrevendo o problema em algoritmos? Voltando um pouco à teoria dos paradigmas de programação, podemos encaixá-los em dois segmentos: paradigmas imperativos e paradigmas declarativos (APPLEBY, 1991). Os primeiros são baseados em instruções a serem seguidas pelo processador, as quais colocam o processador em determinados estados. Desta maneira, seguindo-se a seqüência dos algoritmos apresentados, chegamos ao resultado final. Em tal categoria, enquadra-se a Orientação a Objetos e linguagens que a implementam, como: C#, Delphi, Java e C/C++. Já a intenção dos paradigmas declarativos seria perfeita para resolvermos o problema levantado anteriormente, referente à dificuldade em transpor o que precisamos para instruções seqüenciais, inteligíveis pelo computador. Neste segmento enquadra-se a programação baseada em lógica e sua principal linguagem, o PROLOG. Esta seria uma maneira bastante diferente de se desenvolver software, na qual não utilizamos estruturas de repetição, nem de condição, nem tampouco variáveis para se armazenar valores. Em vez disso, programaríamos basicamente com fatos e regras lógicas, as quais exprimem o domínio relacional do problema a ser resolvido 10 (APPLEBY, 1991). A execução de um programa, construído por uma linguagem lógica, consistiria em provar um teorema por resolução de primeira ordem, tomando como base cálculos de predicados, incluindo sentenças escritas como cláusulas de Horn (PROLOG, 2001). Outro paradigma declarativo seria o Paradigma Funcional, o qual é baseado em funções matemáticas. Isto significa que o programa se resume a uma única função, responsável por chamar outras funções e finalmente retornar apenas um resultado. Isso envolve muita recursividade e faz com que parâmetros nunca sejam modificados dentro de funções. Ou seja, serão passados somente por valor e não por referência. Sua principal linguagem é o LISP, o qual, é baseado em cálculos lambda (de Alozo Church) e, como o PROLOG, possui diversas implementações (APPLEBY, 1991). Ainda nos paradigmas declarativos, podemos encaixar as linguagens para bancos de dados. Estas, são compreendidas por alguns como linguagens específicas para um domínio: trabalhar com dados. Basicamente, são constituídas por duas sub-linguagens: DDL (Data Definition Language), responsável pela definição dos dados de um banco de dados; e DML (Data Manipulation Language), a qual deve fornecer, no mínimo, estruturas para busca, inserção, atualização e remoção dos dados. Sua linguagem mais conhecida é o SQL (Structured Query Language), o qual foi criado pela IBM e assemelha-se muito à utilização da língua Inglês para a manipulação dos dados. Além disso, tais tipos de linguagem são fortemente baseadas em álgebra relacional para a manipulação dos dados (APPLEBY, 1991). No entanto, sabemos que paradigmas declarativos não são largamente utilizados nos dias de hoje. Talvez a complexidade empregada em tais linguagens, o nível de conhecimento em matemática necessário e a falta de ferramentas suficientemente produtivas tenham as feito permanecer restritas basicamente ao meio acadêmico, com pouca expansão, em vez de receberem investimento de grandes empresas e tornarem-se um padrão para o desenvolvimento de software. Só o fato de não haverem processadores capazes de processar diretamente processos construídos nessas linguagens já é algo relevante, pois, para cada programa desses, é feita uma transcrição de 11 um código declarativo para instruções imperativas. Isso gera relativa degradação de performance. Em uma conjuntura mais recente, diversos novos estilos de programação e tendências vêm surgindo e formando um novo paradigma para o desenvolvimento de sistema. Seriam alguns deles: a Intentional Programming, as Software Factories, a Generative Programming e a ModelDriven Architecture. Além disso, conceitos antigos também voltam à tona, como as Domain-Specific Languages. Todos esses assuntos e tendências têm algo em comum: tornar mais simples o ato de escrever software (DMITRIEV, 2005). Pensando nisso, Sergey Dmitriev (2005) sugere a criação de um novo paradigma de programação que viria a substituir a atual e tão utilizada Orientação a Objetos. Diferentemente dos paradigmas declarativos, a Programação Orientada a Linguagens visa a facilidade na compreensão do código fonte, fazendo com que muito mais pessoas possam escrever seu próprio programa sem precisar traduzir suas necessidades para instruções seqüenciais e algoritmos. Tomando como base o artigo citado, além de estudos nas mais modernas tendências do mundo da programação, faremos uma validação da proposta de Dmitriev (2005). Utilizaremos como metodologia a construção de um projeto baseado no paradigma orientado a objetos e a posterior confecção do mesmo sistema através do novo paradigma orientado a linguagens. Finalmente, faremos uma análise comparativa entre os dois, a fim de encontrar pontos que confirmem a eficiência da Language Oriented Programming. Contando com a motivação de podermos contribuir com algo bastante novo para a área do desenvolvimento de software, além de termos bastante interesse no assunto, nosso objetivo geral resume-se a analisar o paradigma proposto, verificando assim a possibilidade de ser utilizado em projetos reais. Finalmente, nosso objetivo específico será a sua comparação com o paradigma atualmente mais utilizado no mercado, a Orientação a Objetos. 12 2 FUNDAMENTOS TEÓRICOS 2.1 DSL – DOMAIN-SPECIFIC LANGUAGES Um dos termos mais utilizados atualmente para se designar pequenas linguagens relacionadas a um domínio específico de problema são as Domain-Specific Languages. Devido a sua abrangência de menor escala, elas fazem frente a outro grupo de linguagens, conhecido como General Pourpose Languages. Estas, por sua vez, são largamente utilizadas no desenvolvimento de software e atendem a quase todo o tipo de necessidade do programador (FOWLER, 2006). Vamos imaginar, por exemplo, a leitura de um arquivo de remessa bancária pelo sistema que estamos projetando. Neste caso, teríamos caracteres unidos uns aos outros, sem qualquer tipo de divisor, da seguinte maneira: /8,=,1∪&,2∋∃6,/9∃ )(51∃1∋2+(15,48(&∃5∋262 ,7∃0∃5)5∃1&2 Ao pensar neste problema, seguindo os princípios da Orientação a Objetos, começaríamos a modelar nossas classes de acordo com os conceitos que bem conhecemos. Além disso, em alguma etapa do projeto, pensaríamos no algoritmo que faria a leitura dos dados da remessa. Em algum lugar de nosso código, deveríamos instruir o computador a separar os campos, lendo do arquivo somente o que necessitamos. Um exemplo de algoritmo seria o seguinte: procedure LeiaLinha(const Linha: TLinha); begin if PegaCaracteres(Linha, 0, 1) = 0 then //HEADER begin NroBanco := PegaCaracteres(Linha, 2, 3); 13 DataGeracao := PegaCaracteres(Linha, 8, 8); HoraInicioGeracao := PegaCaracteres(Linha, 17, 4); end else if PegaCaracteres(Linha, 0, 1) = 1 then //REGISTRO begin NSR := PegaCaracteres(Linha, 4, 5); NomeCliente := PegaCaracteres(Linha, 9, 28); DataVencto := PegaCaracteres(Linha, 37, 8); ValorAPagar := PegaCaracteres(Linha, 45, 8); end; else if PegaCaracteres(Linha, 0, 1) = 9 then //TRAILER begin NroRegistros := PegaCaracteres(Linha, 2, 2); HoraTerminoGeracao := PegaCaracteres(Linha, 4, 4); end; end; Olhando para este trecho de código, conseguimos entender facilmente quais são os intervalos e seus campos correspondentes. Desta maneira, podemos conversar com um programador e passar as instruções para que ele implemente rapidamente o algoritmo. Isso acontece porque estamos utilizando uma linguagem de propósito geral. No caso, o Pascal. Contudo, para um leigo no assunto, talvez não fosse tão fácil conseguir tal percepção. Se pararmos para pensar, podemos analisar que, tal entendimento só nos é familiar porque já estamos acostumados a instruir o computador ao que ele deve fazer, utilizando a sua maneira de pensar. No entanto, para os não-programadores, pensar “fingindo ser uma máquina” não é uma tarefa fácil (FOWLER, 2006). Para o segundo grupo, poderíamos dizer a mesma coisa da seguinte maneira: LER LINHA Se linha for HEADER NroBanco inicia em 2, contando 3 caracteres, DataGeracao inicia em 8, contando 8 caracteres, HoraGeracao inicia em 17, contando 4 caracteres. 14 Se linha for REGISTRO NSR inicia em 4, contando 5 caracteres, NomeCliente inicia em 9, contando 28 caracteres, DataVencto inicia em 37, contando 8 caracteres, ValorAPagar inicia em 45, contando 8 caracteres. Neste caso, mesmo uma pessoa que não tenha contato freqüente com desenvolvimento de software entenderia o que estamos dizendo. Temos agora uma linguagem específica de domínio que acabamos de criar. Com isso, reduziríamos o tempo necessário para traduzir o problema em elementos impostos pelo paradigma em questão. Ou seja, em vez de nos forçarmos a pensar da maneira que o computador executaria nosso código, faríamos o inverso, ele passaria a entender o que passa em nossa mente (FOWLER, 2006). Por mais que não estejamos familiarizados com o conceito apresentado, Domain-Specific Languages (ou, simplesmente, DSLs) vêm nos rodeando há bastante tempo. Podemos observar na listagem a seguir que várias delas são nossas antigas conhecidas: • linguagem de macro do Microsoft Excel – personalização de cálculos e tarefas comuns da ferramenta; • OCL (Object Constraint Language) – critérios de busca e manipulação em modelos orientados a objeto (bastante utilizada junto a modelos desenhados na notação UML); • XPath – alteração e busca de elementos em documentos XML; • XSLT – transformação de arquivos no formato XML para outro formato qualquer (geralmente HTML); • CSound – criação e manipulação de arquivos de som; • SQL (Structured Query Language) – tratamento de estruturas e dados em bancos de dados relacionais. Por outro lado, temos as linguagens de propósito geral. Estas são muito utilizadas e também popularmente conhecidas simplesmente como 15 linguagens de programação. Alguns exemplos de General Pourpose Languages seriam: • Delphi Language (sucessora do Object Pascal); • C#; • C/C++; • Java; • Visual Basic; • Ruby. Cabe também ressaltar que o SQL, citado acima, é hoje uma linguagem bastante discutida pela comunidade em geral. Uma vez que podemos pensar em bancos de dados como um propósito tão abrangente que poderia torná-la pertencente ao segundo grupo, em vez do primeiro. 16 2.2 INTENTIONAL PROGRAMMING Nascida na década de 1990, nos laboratórios da Microsoft Research, a Intetional Programming quebra alguns conceitos básicos da programação tradicional, como, por exemplo, o da utilização de arquivos texto para a representação de códigos fonte. O primeiro artigo escrito sobre o tema foi do pesquisador da empresa, Charles Simonyi (1995). Tal documento segue uma linha um tanto radical de que as linguagens de programação supostamente iriam desaparecer em detrimento de um novo jeito de se construir programas. Simonyi (1995) foi o líder de um grande projeto, conhecido simplesmente como IP. Este foi um IDE para o desenvolvimento de sistemas utilizando o conceito que, infelizmente, foi descontinuado pela empresa próximo ao ano 2000. Segundo Simonyi (1995), as linguagens de programação atuais não nos permitem construir diversas coisas que, muitas vezes, nem nos damos conta por estarmos tão acostumados. Por exemplo, a maioria delas utiliza nomes como identificadores. No entanto, quando existem desenvolvimentos distintos de módulos de mesmo nome, não é possível utilizarmo-nos simultaneamente, pois haverá conflitos entre suas identidades. Em nossa linguagem natural, não há restrições em “importarmos” palavras e expressões estrangeiras para expressar melhor o que queremos dizer. Nas linguagens de programação, isso não é permitido. Indo além, não podemos escrever expressões como esta: a² + b³ 5. Linguagens tradicionais de programação, certamente indicariam um erro de compilação. Podemos criticar também os comentários que adicionamos aos nossos códigos fontes para facilitar sua compreensão. Pensemos: em vez de compilar somente o código, por que não é compilado nosso comentário? Certamente, seria muito mais fácil entender o que está sendo feito e não precisaria ser esclarecido por outras linhas de texto. Utilizando o IP, criamos nosso programa em um ambiente WYSIWYG (what you see is what you get). Em tal ambiente teremos um 17 suporte gráfico (e não em modo texto) para a criação do que é chamado de Toolbox. Após a criação deste, nosso programa é então criado com base em Intentions, e este é o porquê do nome “Intentional Programming” (SIMONYI, 1995). Para ficar mais claro o conceito de Intention, vejamos o código a seguir: for i := 0 to Collection.Count - 1 do begin if Collection[i] = Symbol do begin Found := true; break; end; end; Com base no exposto acima, qual seria a intenção do programador? Muito provavelmente a de procurar um símbolo igual ao conteúdo de Symbol na coleção Collection. Certo, no entanto, podemos perceber que alguma pessoa com pouco conhecimento na área não entenderia facilmente a intenção do desenvolvedor. Neste caso, estamos utilizando um problema “clássico” no mundo da programação, porém, se fosse algo mais complexo, seria difícil expressarmos nosso objetivo sem o uso de comentários no código. Sabemos também que tais comentários, diversas vezes, não são tão expressivos, ou não mantêm sincronia com o código após algumas alterações, dentre outros problemas. Utilizando a Intentional Programming, escreveríamos algo um tanto mais óbvio, expressando a intenção do autor, como, por exemplo: <<procurar o símbolo Symbol na coleção Collection>> Este seria possivelmente o comentário do programador sobre aquele trecho de código. 18 Desta maneira, o sistema utilizaria de Intentions para gerar o código do programa. Conseguimos então facilitar bastante a compreensão do código e não deixamos de expressar o que precisamos. Outra definição bastante importante na Intentional Programming são as Identities. Na programação tradicional, variáveis, entidades e conceitos são representados basicamente por uma palavra, um token significando seu nome e fazendo assim a identificação. Entretanto, imaginemos a troca de um nome de uma variável. No caso, podemos utilizar o famoso “search and replace” para executar esta tarefa, mas sabemos das deficiências do mesmo. Se houverem nomes parecidos no texto, teremos problemas. O conceito de identidade na programação intencional vai além de um simples nome, seria realmente um identificador. Deste modo, poderíamos ter “traduções” diferentes para uma mesma entidade: uma em inglês, outra em português e assim por diante. Estaríamos sempre nos referindo ao mesmo conceito sem problemas de diferenças de nomes (SIMONYI, 1995). Uma das grandes diferenças entre a programação tradicional e a Intentional Programming é a de que, na última, todas as declarações de conceitos ficariam em um mesmo lugar. Estas seriam posteriormente referenciadas, e não redeclaradas. Vale acrescentar que as alterações comentadas nos remetem a ferramentas de refactoring automatizado, nas quais podemos alterar nomes (no caso as identidades) de conceitos e o sistema se encarrega de alterar apenas os tokens corretos, em qualquer lugar do código fonte. Além disso, para facilitar o entendimento dos fontes do programa, o projeto IP nos disponibiliza estruturas hierárquicas, nas quais podemos obter visualizações das entidades mais genéricas ou mais detalhadas, dependendo do nível de detalhamento que estamos procurando. Em suma, talvez a ambição de Simonyi (1995) ao dizer que as linguagens de programação tradicionais iriam morrer tenha sido um tanto exagerada. Contudo, suas idéias têm bastante relevância e passamos a perceber que um software poderia ser escrito de uma maneira totalmente diferente e mais simples. 19 2.3 GENERATIVE PROGRAMMING Talvez a abordagem mais parecida com a Language Oriented Programming, até hoje especificada, seja a Generative Programming. Seus objetivos principais são: a maximização do reuso e adaptabilidade; a melhora do controle de complexidade; o melhor gerenciamento de um grande número de variáveis e um aumento na eficiência quanto ao desenvolvimento de software (CZARNECKI, EISENECKER, & STEYAERT, 1997). Além disso, também faz uso de Domain-Specific Languages, como veremos a seguir. Para entender como funciona o Generative Programming, devemos entender seus três elementos principais: espaço do problema, espaço da solução e configuração de conhecimento (CZARNECKI, 2002). • espaço do problema – compreende conceitos e funcionalidades entendidos por pessoas com um grande entendimento do problema, porém sem experiência com programação de software. É especificado, basicamente, com a ajuda de Domain-Specific Languages; • espaço da solução – é especificado com base em componentes de software, os quais devem estar maximamente combinados e com a mínima redundância; • configuração de conhecimento – provê regras, configurações de otimização, combinações e dependências para a transformação do espaço do problema em espaço da solução. A idéia principal desta abordagem consiste em tratar da mesma maneira uma família de elementos, em vez de tratá-los individualmente (CZARNECKI, EISENECKER, & STEYAERT, 1997). Por exemplo, para solucionar um determinado problema hipotético, utilizamos uma lista de números reais. No entanto, um modelo de listas genérico serviria como base para a geração de qualquer tipo de artefato relativo a este problema. No caso, tal modelo (também chamado de template) poderia servir como generator, pois, apenas configurando-o, chegaríamos à solução específica. 20 Muitas vezes, ao nos depararmos com um problema, utilizamos um assistente para a criação de algum artefato, seja ele um tipo de código, uma janela, um arquivo qualquer, uma planilha, etc. Assistentes nos ajudam a minimizar o tempo de criação de algo que é altamente automatizável. Mais do que isso, se passarmos a mesma configuração para ele, o mesmo resultado final será gerado (SELLS, 2001). Na prática, uma das maneiras de se aplicar o Generative Programming é inserindo templates em nosso código. Por exemplo, digamos que nosso problema consiste em procurar elementos em uma lista. Poderíamos utilizar um template pronto, para o qual passaríamos apenas alguns parâmetros e o código seria gerado. No caso nosso template ficaria algo como: int $RESULTADO$ = -1; for(int i = 0; i < $LISTA$.Count; i++) { if ($LISTA$[i] == $VALOR$) { $RESULTADO$ = i; break; } } Uma vez que a tarefa de se procurar em listas por um valor é sempre a mesma, utilizaríamos esse template sempre que fosse necessária tal ação. Desta maneira, estaríamos aumentando e muito a reutilização de nosso código. Além disso, deixaríamos o código final muito mais simples: public static void Main(String[] args) { Lista lista = new ColecaoDeNumerosPrimos(); Console.Write("Qual o número?"); double valor = Console.ReadLine(); 21 <<PROCURA_EM_LISTA=lista|valor|resultado>> if (resultado >= 0) Console.WriteLine("O número foi encontrado e ocupa a posição" + resultado.ToString() + " da lista."); else Console.WriteLine("O número foi encontrado e ocupa a posição " + resultado.ToString() + " da lista."); } Antes de compilarmos nosso código, precisaríamos submetê-lo a alguma ferramenta para fazer a geração do código final. Contudo, observando o que acabamos de escrever, já podemos imaginar como ficaria um grande programa com diversas classes e métodos. Conseguimos entender facilmente o que será feito naquela região do código. Talvez aqui caiba uma pergunta: esse não seria o mesmo papel de um procedimento ou uma função das linguagens de alto nível? E a resposta é bastante simples: sim. Mas, imaginemos classes inteiras, ou módulos inteiros sendo criados com a ajuda destes templates. Conseguiríamos chegar muito mais longe através desta abordagem. Para finalizar, após a passagem pela ferramenta de geração, chegaríamos a: public static void Main(String[] args) { Lista lista = new ColecaoDeNumerosPrimos(); Console.Write("Qual o número?"); double valor = Console.ReadLine(); int resultado = -1; for(int i = 0; i < lista.Count; i++) { if (lista[i] == valor) { resultado = i; break; 22 } } if (resultado >= 0) Console.WriteLine("O número foi encontrado e ocupa a posição " + resultado.ToString() + " da lista."); else Console.WriteLine("O número foi encontrado e ocupa a posição " + resultado.ToString() + " da lista."); } 23 2.4 SOFTWARE FACTORIES Mais um conceito amplamente estudado nos laboratórios da Microsoft Research. As Software Factories reduzem drasticamente o tempo do desenvolvimento do projeto, baseando-se no seguinte princípio: automatizar tudo o que é possível (GREENFIELD, 2004). Certamente, alguma vez, enquanto estávamos programando, já passamos pela situação de acharmos aquela tarefa em específico, algo “braçal” e que poderia ser feito automaticamente. Personalizaríamos somente o necessário e o nosso programa estaria pronto. Para resolvermos o problema em questão, uma das possíveis soluções a se pensar seria a componentização. Vários módulos de software trabalhando conjuntamente que, personalizados, produziriam o resultado desejado para cada situação. Um conceito muito importante que também está ligado a software factories são os templates. Através destes, conseguimos construir rapidamente nossas aplicações, pois contamos com artefatos prontos e personalizáveis (GREENFIELD, 2004). Por exemplo, um template de uma janela de cadastro em nosso sistema, teria uma ligação com um banco de dados, adicionaria uma opção ao menu, teria uma tela de pesquisa e mostraria uma tela de ajuda ao se pressionar a tecla F1. Deste modo, personalizaríamos apenas a parte que nos interessa e deixaríamos o resto do trabalho para o template. Uma arquitetura que está intimamente relacionada ao conceito em questão é a Model-Driven Architecture, na qual, criamos o modelo e o próprio sistema gera o código correspondente e faz o mapeamento dos objetos para a persistência em um local de armazenamento de dados. Neste ponto, podemos também comparar Software Factories com a Generative Programming. Ambas as abordagens estão intimamente ligadas, porém, diferem-se em sua amplitude. Enquanto a Generative Programming limita-se na geração de partes do código ou, eventualmente, da mistura de geração de código com código final, Software Factories vão além e pretendem dispor templates para a geração de um produto inteiro. 24 Logicamente, os estudos na área vão bastante além do que apresentaremos. Hoje, Software Factories é um tema bastante comentado em congressos e estudiosos acreditam ser uma das bases do futuro da programação. 2.5 MODEL-DRIVEN ARCHITECTURE Um dos maiores problemas da utilização da documentação formal para nossos projetos, em notações como a UML, está na falta de sincronia a qual podemos, eventualmente, chegar em momentos de excessiva pressão. Por exemplo, na fase de implantação de nosso produto, muitas vezes a documentação é deixada de lado em detrimento à alta produtividade necessária para a correção de todos os problemas ocasionados inesperadamente. A arquitetura dirigida ao modelo vai além. Contamos primeiramente com um elemento chamado PIM (Plataform-Independent Model), o qual seria um modelo totalmente independente da plataforma para a qual o software será compilado. Este deve ser escrito em uma linguagem específica para determinado domínio, como por exemplo, a notação UML, a qual é utilizada basicamente para a especificação de diagramas representando código orientado a objetos (BROWN, 2004). Após a elaboração do PIM, dado um PDM (Plataform Definition Model) contendo informações relativas á plataforma em questão, é possível chegarmos a PSMs (Plataform-Specific Models), os quais representaram o mesmo PIM nas mais diversas arquiteturas e linguagens (BROWN, 2004). Ou seja, poderíamos exemplificar a arquitetura da seguinte maneira: começamos elaborando um diagrama de classes na notação UML contendo algumas classes de nosso sistema. Esta seria a representação de nosso PIM e seria totalmente independente de linguagem ou plataforma para a qual queremos nosso programa compilado. Após tal elaboração, utilizamos uma ferramenta que irá utilizar um PDM para C# e gerar o código final (PSM). 25 Desta forma, as alterações são feitas basicamente nos diagramas e o código final é automaticamente gerado. E isso nos garante a sincronia constante entre a documentação e o código (BROWN, 2004). Vale lembrar também que a Model-Driven Architecture foi criada pelo Object Management Group (OMG) e oficialmente lançada em 2001. Além disso, já existem diversas ferramentas disponíveis no mercado para nos auxiliar em sua implementação. 26 3 LANGUA LA AGE OR RIENTED ED PROG GRAMMI MING Pode odemos perceber pe que q as tendências ias para o futuro ro do dese senvolvimen ento de softw ftware são o bastante b parecidas p s e envolvem em basicam mente os mesmos m princípios: pri a automaçã ção de proc ocessos rep epetitivos e a facilidad ade na tradu dução da linguagem li m natural e do pensa nsamento para pa algo iinteligívell pelo comp mputador. Tam mbém não ão nos é difícil d perc rceber que e o desenv envolvimento nto de softw ftware, hoje, je, ainda est stá muito preso pr à ling nguagem de programa mação escol colhida e ao IDE utiliza lizado para a se s program amar. Por exemplo, ex posso po utiliza izar a lingua uagem Java va, sendo editada e em m uma ferr erramenta como co o Ecl clipse, ou u o JBuilder er. Da mesm sma maneir eira, posso o utilizar u o Delphi D ou o C#. Entre tretanto, por or mais que ue não seja ja tão perce ceptível para ara alguns,, ainda a esta stamos muito uito amarrad ados a estru trutura que e nos n é forne rnecida (DMI MITRIEV, 2005). 20 Segu egundo Dmititriev (2005 05), linguage gens de pro ropósito ger eral (largam mente utiliza lizadas, atu tualmente)) são um tanto imp mprodutivas, as, pois, pa para utilizá izá-las, preci ecisamos transcrever tra r o que esta stamos pens nsando em algoritmos a os. Quanto o mais m espe pecializada a for f a lingua uagem utiliz ilizada, mais is rápida serrá a traduç ução e mais is fácil será rá o entendim dimento. Pod oderíamoss imaginar i r então e o seg eguinte gráfi áfico: Figura 1 - Análise A de Produtividade Pr 27 Dentre os principais itens para o desenvolvimento de software (DMITRIEV, 2005), destacam-se: • Tempo para implementar idéias: podemos pegar, por exemplo, a confecção de diagramas da Orientação a Objetos. Esses diagramas são fundamentais para que entendamos o funcionamento interno do programa, no entanto, não expressam a realidade como ela é. Precisamos de algum tempo para entender e compreender as relações para imaginar o funcionamento do programa. • Entendimento e manutenibilidade de código existente: entender código já existente, escrito por nós mesmos ou por outra pessoa não é uma tarefa simples. Precisamos traduzir mentalmente o quê aquele algoritmo está tentando fazer para o contexto de alto nível representado pelo problema. Uma solução comprovadamente fraca para facilitar essa tradução envolve o uso de comentários. • Curva de aprendizado do problema: uma das poucas maneiras de se estender uma linguagem orientada a objetos é utilizando bibliotecas (class libraries). No entanto, essas bibliotecas geralmente são expressas em um nível bastante baixo e bem distante do conceitual. Isso introduz problemas como a curva de aprendizado para entender a comunicação e o desenvolvimento utilizando-se das bibliotecas inseridas. Fazendo frente a todos estes problemas e reunindo as tendências já apresentadas, Dmitriev (2005) sugere a criação de um novo paradigma, a programação orientada a linguagens (LOP – Language Oriented Programming). Nesse novo jeito de se desenvolver software, utilizaríamos várias Domain-Specific Languages em conjunto. Uma para cada problema a ser solucionado. Caso essa linguagem ainda não exista, criamos uma e utilizamos no projeto. A intenção é promover a reutilização de linguagens de programação de uso específico. Por exemplo, se vamos construir um programa para uma instituição bancária, certamente utilizaríamos uma linguagem para tratamento de contas 28 entre diferentes pessoas físicas e jurídicas. Caso tal linguagem já tenha sido escrita e esteja disponível, será utilizada. Caso contrário, cria-se uma nova e utiliza-se no projeto em questão. Tal linguagem definiria alguns conceitos como: Conta, Operação, Saldo, CPMF, Limite, etc. E sua utilização para a Operação de Transferência seria algo como: TRANSFERÊNCIA Se saldo de conta fonte for suficiente Executar transação Retirar valor de conta fonte Adicionar valor em conta destino Cobrar CPMF de conta fonte No caso, a linguagem é auto-explicável. Esta operação seria finalmente traduzida para código final e, em conjunto com as demais, teríamos nosso programa. Uma linguagem, no paradigma Language Oriented Programming é dividida em 3 partes principais (DMITRIEV, 2005): • estrutura: o sintaxe abstrata da linguagem; o conceitos suportados; o como esses conceitos podem ser organizados. • editor: o sintaxe concreta; o edição/representação (em forma de texto, árvore, desenhos, estrutura de marcação). • semântica: o como deve ser interpretada; o como deve gerar o executável. 29 Ainda em nosso exemplo, a Estrutura de nossa linguagem definiria os conceitos anteriormente citados; seu Editor seria em modo texto e definiria alguns tokens como de, em, Adicionar e Retirar; e sua semântica definiria como seria feita a tradução de cada linha escrita para código final. A última, neste caso, seria certamente a parte mais difícil da elaboração da linguagem. Além disso, seria a que mais necessitaria de conhecimento sobre a linguagem de destino. Desta maneira, teríamos linguagens específicas para cada domínio de problema e conseguiríamos usufruir dos resultados almejados até o momento. A manutenção em uma linguagem desse tipo, por exemplo, seria muito mais fácil do que em algoritmos e seqüências de uma linguagem de propósito geral. Além disso, como comentado, não necessitaríamos de documentação adicional para explicar o que foi escrito. No entanto, para a criação dessas linguagens, necessitamos de ferramentas que nos dêem o devido suporte (FOWLER, 2005). Com esta finalidade, Dmitriev (2005) nos apresenta o projeto o qual vem desenvolvendo há algum tempo: o Meta-Programming System, ou simplesmente, MPS. Existem também outras ferramentas para a criação de DSLs, como o Microsoft DSL Tools, o qual segue uma abordagem baseada em modelos gráficos e será utilizado em nosso estudo de caso. 30 4 DESENVOLVIMENTO – ESTUDO DE CASO Com a finalidade de exemplificar a utilização da Language Oriented Programming na prática e analisar seus prós e contras, faremos uma comparação entre um projeto construído seguindo-se os princípios da Orientação a Objetos, e outro, baseado no paradigma proposto. Em busca de um tipo de aplicação que possua uma quantidade razoável de regras de negócio, facilitando assim a compreensão do exemplo, chegamos a um sistema para cálculo de folhas de pagamento dos funcionários de uma empresa. Como sabemos, o pagamento dos empregados de uma organização não é algo tão simples de se calcular e, hoje, algumas empresas ainda utilizam sistemas muito antigos e pesados, rodando em máquinas como mainframes, em detrimento da confiabilidade que os mesmos representam. Nosso projeto apresentará diversos conceitos comumente utilizados no mercado em geral e terá enfoque em uma grande fábrica de um produto qualquer, privando pelo pagamento de tudo o que é devido a cada um de seus funcionários. Seu fluxo principal será, basicamente, o de analisar cada funcionário, rodando regras de cálculo sobre cada um deles. Mais uma vez, pensando em fazer uma comparação entre os diferentes paradigmas, não utilizaremos nenhuma notação formal para especificar o projeto. Segundo Dmitriev (2005), ao utilizarmos uma especificação como a UML (Unified Modeling Language), estamos tendo o trabalho de abstrair o que queremos em modelos orientados a objetos, os quais podem não representar tão legivelmente a solução para o problema em questão. Façamos isso da mesma maneira que faríamos se estivéssemos explicando o sistema para o programador responsável pela implementação deste módulo. 4.1 ESCOPO DO PROJETO Por se tratar de um sistema para o cálculo de folhas de pagamento, nosso programa será alimentado por uma lista de funcionários e suas respectivas fichas de registro de freqüência (Listagem 1). Mensalmente, será 31 calculada a folha com base no fechamento em questão, que corresponde ao período entre o primeiro e o último dia do mês anterior. Por exemplo, no primeiro dia de maio, o cálculo será relativo a todo o mês de abril. Serão, depois de realizado o cálculo da folha, exportados arquivos (Listagem 4) contendo informações sobre o FGTS, sobre o valor a ser depositado no banco e também com a discriminação da folha salarial dos funcionários. Uma lista simples com os campos: matrícula, salário bruto mensal, nome, CPF, Lista de Funcionários número de dependentes, quantidade de faltas, período de férias no fechamento corrente e data de admissão. Irá apresentar apenas funcionários ativos no sistema e aptos a receber seu salário. Para cada dia do fechamento, teremos os seguintes dados: • Estado do ponto. Ex: Presença, Falta, Afastamento (férias, licenças, atestados, folgas...), Dia não útil (sábados, domingos e feriados) Ficha de Registro de Freqüência • Turno esperado o Horário de entrada esperado o Horário de saída esperado • Turno realizado o Horário realizado de entrada 32 o Horário de saída realizado Intervalo esperado • o Horário de entrada para o intervalo esperado o Horário de saída para o intervalo esperado Intervalo realizado • o Horário de entrada para o intervalo realizado o Horário de saída para o intervalo realizado Hora Extra • o Horário de entrada da hora extra o Horário de saída da hora extra Listagem 1 – Conceitos do sistema de folhas de pagamento Horas Extras Horas extras serão pagas como horário trabalhado normalmente, com a adição de um valor que corresponderá a um percentual de seu salário: • 50% quando realizadas no período diurno (das 08:00 às 20:00) de segunda a sexta • 100% quando realizadas no período noturno (das 20:00 às 33 08:00), ou em fins de semana e feriados Estados Todos os estados de ponto serão pagos, com exceção de faltas não devidamente justificadas (com atestado médico ou documento formal de igual valia), as quais serão descontadas INSS – Contribuição à Previdência Será descontado um percentual do Social valor recebido por cada funcionário, referente à contribuição para a Previdência Social. Esse desconto dependerá do valor de seu salário bruto e será calculado conforme os dados abaixo: • Até R$429,00 – Desconto de 7,65% • De R$429,01 a R$540,00 – Desconto de 8,65% • De R$540,01 a R$715,00 – Desconto de 9% • De R$715,01 a R$1.430,00 – Desconto de 11% • Acima de R$1.430,00 – Desconto de R$157,30 (teto) FGTS – Fundo de Garantia por Corresponde a 8% do valor bruto da Tempo de Serviço folha de cada funcionário. Não será descontado, apenas repassado para a Caixa Econômica Federal. IRF – Imposto de Renda na Fonte Sobre o valor bruto do salário de cada 34 funcionário, incide o desconto do imposto de renda, com base nas faixas a seguir. • Até R$1.058,00 – Isento • De R$1.058,01 a R$2.115,00 – 15% • Salário Família Acima de R$2.115,01 – 27,5% Será concedido um adicional ao funcionário por cada filho ou equiparado, de 0 a 14 anos de idade. Este valor é determinado proporcionalmente ao número de dias trabalhados no mês e somente aos funcionários que se enquadrarem nas seguintes faixas: • Até R$435,56 - R$22,34 por dependente • De R$435,57 a R$654,67 R$15,74 por dependente Férias Todos os funcionários terão direito a 30 dias de férias, exceto em causa de faltas injustificadas durante seu período aquisitivo (tempo trabalhado com duração de um ano), conforme os seguintes dados: • De 6 a 14 faltas – menos 6 dias • De 15 a 23 faltas – menos 12 dias • De 24 a 32 faltas – menos 18 dias 35 • Acima de 32 faltas – sem férias O valor recebido em suas férias será calculado então proporcionalmente ao número de dias dos quais terá para gozar. Será concedido também um adicional de um terço do valor calculado até o momento. Todavia, serão descontados, de todo esse montante, a contribuição para a previdência social e o imposto de renda. Previdência Social (INSS) – se o valor calculado ocorrerá ultrapassar a incidência R$1.400,91, 11% de desconto. No entanto, este valor não poderá ultrapassar os R$308,20. Imposto de Renda – será calculado após a dedução da contribuição para a previdência social. Contudo, caso o valor sem o desconto do INSS, não chegue a R$1.257,12, não haverá desconto de imposto de renda. O mesmo será calculado com base nas seguintes faixas: • De R$1.257,13 a R$2.512,08 – 15% • Acima de R$2.512,08 – 27,5% Deve-se também reduzir desse desconto os valores: • De R$1.257,13 a R$2.512,08 – R$188,57 36 Acima • de R$2.512,08 – 502,58% O colaborador poderá também transformar um terço de suas férias em abono pecuniário, ou seja, irá gozar de apenas dois terços e receberá o resto em dinheiro. Também será parcelamento das permitido férias. o Sendo assim, o cálculo dos adicionais e descontos serão feitos com base no total dos dias e o valor será então dividido entre os períodos de gozo. Porém, quando houver o parcelamento, os períodos deverão ser programados para meses diferentes. Décimo Terceiro Salário Será pago em duas parcelas: a primeira em novembro e a segunda em dezembro. A primeira parcela deve ser calculada com base no salário bruto do mês de novembro e será proporcional à metade do valor referente aos meses ou frações (maiores ou iguais a 15 dias) trabalhados no ano corrente. A segunda parcela será calculada base no salário bruto do mês de dezembro e será proporcional ao valor referente aos meses ou frações (maiores ou iguais a 15 dias) 37 trabalhados no ano corrente, descontando-se o valor da primeira parcela. Incidirão sobre o valor também os seguintes descontos: Previdência Social (INSS) – se o valor total calculado (referente às duas parcelas juntas) ultrapassar R$1.400,91, ocorrerá a incidência 11% de desconto. No entanto, este valor não poderá ultrapassar os R$308,20. Imposto de Renda – será calculado após a dedução da contribuição para a previdência social. Contudo, caso o valor sem o desconto do INSS, não chegue a R$1.257,12, não haverá desconto de imposto de renda. O mesmo será calculado com base nas seguintes faixas: • De R$1.257,13 a R$2.512,08 – 15% • Acima de R$2.512,08 – 27,5% Deve-se também reduzir desse desconto os valores: • De R$1.257,13 a R$2.512,08 – R$188,57 • Acima de R$502,58 Listagem 2 – Regras para o cálculo de cada funcionário R$2.512,08 – 38 • Ficha do Funcionário Turno Turno Intervalo Intervalo Hora Esp. Real. Esp. Real. Extra Dia não útil - - - - - 2/1/2007 Presença 08:00-18:00 08:05-18:00 12:00-14:00 11:55-14:00 - 3/1/2007 Presença 08:00-18:00 08:10-18:10 12:00-14:00 12:05-14:00 - 4/1/2007 Presença 08:00-18:00 07:50-18:05 12:00-14:00 12:00-14:00 18:30-20:30 5/1/2007 Falta - - - - - 6/1/2007 Presença 08:00-12:00 08:10-12:05 - - 14:00-16:00 7/1/2007 Dia não útil - - - - - 8/1/2007 Afastamento - - - - - 9/1/2007 Afastamento - - - - - 08:00-18:00 08:00-18:00 12:00-14:00 12:00-14:00 - Data Estado 1/1/2007 10/1/2007 • Presença Cálculos o 04/01 90 * 1.5 (hora extra paga em regime de 50% de acréscimo) 30 * 2.0 (hora extra paga em regime de 100% de acréscimo) 195 (TOTAL) o 05/01 Falta sem justificativa, será descontado o 06/01 120 * 2.0 (hora extra paga em regime de 100% de acréscimo) 240 (TOTAL) o Caso o salário do funcionário seja de R$1000.00, o cálculo seria então: 39 Dias úteis no mês = 26 Valores pagos = R$38,46(dia)–R$4,81(hora)–R$0,08 (minuto) Dias descontados = 1 – -R$38,46 Horas-extras = +435 (minutos) – R$34,80 Desconto INSS 11% = R$110,00 Desconto IRF: Isento Valor do FGTS 8%: R$80,00 Valor a Receber = R$1000,00 – R$38,46 + R$34,80 – R$110,00 = R$886,34 Listagem 3 - Exemplificação das regras Arquivo de Exportação do FGTS Arquivo texto simples, que conterá o nome dos funcionários, seus CPFs e os valores de FGTS a serem enviados ao banco Caixa Econômica Federal. Caracteres 1-20 21-31 = = e valores: Nome do Funcionário CPF do Funcionário 32-41 = FGTS calculado (valor do depósito) Arquivo de Exportação para o Banco Arquivo texto simples, que conterá a agência, a conta, o nome e o CPF dos funcionários, juntamente com o valor de seus salários no mês em questão, para que seja realizado o depósito em suas contas. Todos os funcionários possuem conta na mesma agência e banco. Além disso, seu número de 40 conta segue o formato: “123XXX”, sendo os caracteres X substituídos pelo número de sua matrícula. Caracteres 1-4 = e Agência valores: (sempre 1212) 5-10 = Conta (123 e Matrícula) 11-30 = Nome do Funcionário 31-41 = CPF do Funcionário 42-51 = Salário Líquido (valor do depósito) Arquivo de Exportação para a Gráfica Arquivo texto simples, que conterá nome, CPF, matrícula, salário bruto e líquido dos funcionários, além de todo o detalhamento de seus salários (valor do INSS, FGTS, etc.) para que a gráfica imprima o holerite de cada funcionário. Caracteres e valores (registro funcionário): 1-1 = Registro Novo Funcionário (sempre 2-4 5-24 “1”) = Matrícula do Funcionário Nome do Funcionário CPF do Funcionário = 25-35 = 36-45 = Salário Bruto do Funcionário 46-55 = Salário Líquido do Funcionário Caracteres e valores (registro detalhe): 1-1 = Registro Novo Detalhe (sempre “2”) 41 2-55 = Detalhe Listagem 4 - Arquivos de Exportação 4.2 CONSTRUÇÃO DA VERSÃO ORIENTADA A OBJETOS 4.2.1 ESCOLHENDO OS ELEMENTOS NECESSÁRIOS Ao planejarmos a implementação desse sistema, pensamos primeiro em qual metodologia de desenvolvimento de software utilizaríamos para a elaboração das classes e objetos. Para tal decisão, levamos em conta basicamente a experiência profissional que tínhamos com cada uma. Se escolhêssemos utilizar uma metodologia baseada fortemente em documentação como o RUP (Rational Unified Process), da empresa Rational, nosso foco seria a confecção de artefatos na notação UML e na posterior geração de código para tais. No entanto, preferimos a utilização de algo mais leve, ágil e flexível, para que pudéssemos exemplificar a implementação de um projeto de maneira muito simples, contudo, sem deixar de lado aspectos como a qualidade do código final e sua posterior manutenibilidade. Utilizando a metodologia Test-Driven Development, podemos garantir a não existência de classes que não irão funcionar desde sua elaboração, pois teremos testes nos indicando a necessidade real de um elemento novo. Isso nos faz programar sem desviarmos de nosso objetivo e nos concede certa garantia de que o código estará funcionando. Além disso, por se tratar de um sistema focado em regras de negócio, a utilização de testes unitários é fácil e altamente recomendada, já que não existem áreas complexas de se testar (como bancos de dados, concorrência, etc...). Quanto à documentação, uma vez que tenhamos as classes funcionando corretamente, utilizaremos uma ferramenta que faça a engenharia reversa de nosso código, construindo modelos com base em arquivos fontes já existentes. Assim, não desperdiçamos tempo elaborando modelos baseados somente em nossa percepção sobre o escopo, algo que não temos certeza se funcionará como esperado. Tais artefatos são criados com base em algo 42 consistente e que, desde sua primeira implementação, já funcionam corretamente. Após alguma pesquisa, resolvemos utilizar um software o qual já conhecemos muito bem e ao qual possuímos muita afinidade, o Borland Developer Studio 2006 (antigo Borland Delphi). Neste ambiente, encontra-se integrada uma ferramenta para construção de diagramas em notação UML, o Together. Torna-se então possível abrirmos um código fonte escrito em linguagem C# e requisitarmos a construção de um diagrama de classes na notação UML. Automaticamente, temos o artefato construído e podemos exportá-lo para uma imagem. Faremos isso no final da fase de implementação. Escolhida a metodologia e a forma de documentação, chega o momento de definirmos a arquitetura de nosso sistema. Apesar de ser um programa fictício, gostaríamos de aplicar técnicas e padrões que estão sendo utilizados pelo mercado em empresas do mundo todo. Por isso, decidimos utilizar o padrão Model View Controller, no qual, teremos de construir elementos de software específicos para tratar dados e informações provenientes de uma fonte qualquer; a interface do programa com o usuário/elemento externo; e os algoritmos que irão reger a iteração entre eles. Ainda na arquitetura, gostaríamos de uma solução bastante flexível quanto à interface com o usuário. Nosso sistema para o Cálculo de Folha de pagamento deverá ser independente a ponto de ser inserido em outro projeto como um módulo a parte, utilizando janelas e formulários totalmente redefinidos. Iremos também elaborar uma aplicação do tipo desktop para a iteração com o usuário final e não impediremos outros desenvolvedores de construírem interfaces web, serviços web e interfaces texto, de maneira simples e rápida para nosso programa. Ficamos então com três projetos/camadas: o de regras de negócio propriamente dito; o de testes do projeto principal; e o da interface gráfica do tipo desktop. Neste momento, ainda não temos planejado como ficará a estrutura final do projeto baseado no novo paradigma, mas tentaremos reconstruir apenas o módulo de regras de negócio, a fim de integrá-lo com nossa mesma interface. Veremos se isso vai ser possível. 43 Devemos escolher também uma linguagem que nos dê suporte a tal arquitetura e metodologia, bem como um ambiente de desenvolvimento que facilite nosso trabalho ao programar. Para tanto, um dos fatores avaliados foi nossa experiência em tais quesitos, pois não queremos despender tanto tempo programando este software. Se isso acontecer, será no nosso segundo projeto. A linguagem escolhida foi, após algumas análises, o Visual C#, da empresa Microsoft Corporation. Seu código gerado é compatível com a especificação .NET, uma plataforma desenvolvida pela mesma corporação, que permite, dentre outras coisas, a troca de código entre programas e módulos escritos em linguagens bastante diferentes. Ou seja, desenvolvedores de VB.NET, por exemplo, poderiam utilizar nosso programa principal em seu sistema contábil sem problema algum. Faremos uso também do ambiente de desenvolvimento Microsoft Visual Studio 2003. Tal ferramenta possui um compilador C# para o.NET Framework versão 1.1 e contem diversos recursos interessantes, igualando-se a outras bastante utilizadas no mercado atual. Para o desenvolvimento da interface gráfica, ela nos ajudará com formulários, nos quais arrastamos os componentes e já saberemos como ficarão nossas janelas em seu estado final. Finalmente, para a criação dos testes unitários, utilizaremos a ferramenta NUnit versão 2.2. Este é um framework no qual podemos ter nossos testes rodando de forma rápida e fácil, simplesmente carregando um módulo .NET (conhecido como Assembly) e enviando um comando Run. Após a execução (em poucos segundos), sabemos através de uma interface muito intuitiva, quais deles passaram e quais falharam pelas corres verde e vermelho, respectivamente. Ainda nos testes unitários, existem alguns tipos de testes que precisaram ser feitos sobre a comunicação entre uma classe e outra. Queremos saber se métodos estão sendo chamados na ordem certa e com os parâmetros corretos. Para isso, utilizaremos o padrão de testes Mock Objects, no qual, criamos classes “falsificadas”, apenas com o intuito de testar a comunicação de nosso módulo. E, para que possamos ter mais agilidade na confecção de tais objetos, utilizaremos um framework para a construção automatizada dos mesmos chamado NMock. 44 4.2.2 IMPLEMENTANDO O PROJETO PRINCIPAL Sabemos, até agora, que precisaremos ter, em nosso projeto principal, elementos que definam regras e outros para a exportação de arquivos. Gostaríamos de possuir a flexibilidade de podermos adicioná-los em nosso código da maneira mais simples possível. Talvez, apenas incluindo uma nova classe. Nossa implementação começa a partir do desenvolvimento do primeiro teste. Como queremos ter a flexibilidade já comentada, vamos criar uma única classe (a ser chamada de ProcessadorDeFolha) para controlar todas as outras e servir de “fachada” para nosso projeto. Ou seja, tudo o que for solicitado pelo usuário será feito primeiramente nessa classe e ela se encarregará de distribuir as funções para as outras. Precisaremos somente instanciá-la, configurá-la e chamar seu método para rodar o cálculo da folha. Antes de mais nada, criamos no Visual Studio dois projetos: o PayrollCalc (projeto principal) e o PayrollCalcTests (projeto de testes). Sendo o nome da Solution (grupo de projetos no Microsoft Visual Studio) também igual a PayrollCalc. Após configurar nosso ambiente, os projetos e a solução, vamos começar a programar. E nosso teste inicial fica da seguinte maneira: [Test] public void RodaFolhaCom0FuncionariosE0Regras() { proc = new ProcessadorDeFolha(); Folha folha = proc.Roda(null, null); Assert.AreEqual(0, folha.Count); } Queremos, com isso, instanciar um novo ProcessadorDeFolha, chamar o seu método Roda (o qual retornará um objeto do tipo Folha), e verificar a quantidade de itens retornados nessa folha (deve ser igual a 0). 45 Deste modo, sabemos que os próximos passos serão: criar tais classes; declarar e implementar o método; e ver nosso primeiro teste passar. Figura 2 - Inicio do desenvolvimento pelos testes Já temos nosso ponto de partida. Continuemos implementando. Nosso método Roda, apresenta dois parâmetros do tipo Array. O primeiro deve ser uma coleção de funcionários e o segundo deve ser o de regras a serem executadas. Continuamos seguindo os passos do Test-Driven Development criando os elementos à medida que são necessários e, rapidamente, chegamos à seguinte estrutura de classes: • ProcessadorDeFolha – Contém um único método Roda, que recebe como parâmetro uma lista de funcionários e um conjunto de regras a serem executadas sobre cada um deles. • Funcionario – Representa um funcionário com seus atributos, como nome e CPF. • FichaPonto – Cada Funcionario terá a sua ficha ponto. Refere-se ao fechamento corrente e representa uma coleção de Pontos. • Ponto – Os detalhes da FichaPonto. Cada dia é representado por um ponto e contém informações sobre o turno esperado, o turno realizado, o estado do ponto e horas extras realizadas. 46 • Folha – Resultante do método Roda do ProcessadorDeFolha, representa a folha de pagamento já calculada. Contém uma coleção de FolhaItems. • FolhaItem – Os detalhes da folha. Cada item de folha possui como atributo um Funcionario e um DetalhamentoFolha. • DetalhamentoFolha – Contem informações mais descritivas sobre o cálculo que fora realizado. É implementado como uma coleção de strings, nas quais temos informações sobre o resultado de cada regra. Ex: “Desconto de INSS: R$154,23”. • RegraFolha – Na verdade não é uma classe, mas uma Interface. Toda regra será uma classe que implementará essa interface e possuirá um único método: double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento); O saldo retornado por cada regra será somado ao salário bruto de cada Funcionario. Temos assim, o salário líquido de cada um deles. O algoritmo para o cálculo da folha seria basicamente: Percorrer a lista de Funcionarios Percorrer a lista de Regras Rodar cada regra para cada Funcionario gerando um FolhaItem Retornar a Folha contendo todos os FolhaItems E para que o conjunto com as regras não tenha de ser criado manualmente a cada cálculo da folha, iremos utilizar uma Fábrica de Regras (Factory). Esta seria basicamente uma classe contendo um único método, GetRegras, que devolverá um Array de RegraFolhas. Este vetor será o conjunto padrão para os cálculos, porém, nada impede um cálculo de ser feito com uma coleção diferente de regras. Deste modo, fica fácil criar uma regra. Precisamos apenas escrever uma classe que implemente a interface RegraFolha e “registrá-la” na fábrica de regras. 47 Seguindo estes passos, surgem então as seguintes classes (e seus respectivos testes): • RegraFGTS • RegraSalarioFamilia • RegraEstados • RegraHE • RegraINSS • RegraIRF • RegraDecimoTerceiro • RegraFerias Uma para cada regra da especificação. E assim, fechamos a parte de regras. Sendo que cada uma das classes está se preocupando apenas com o algoritmo que retorna um saldo a ser somado. Neste momento, resta apenas a implementação dos arquivos de exportação. Como estamos lidando com uma arquitetura bastante flexível, não podemos “amarrar” o algoritmo de processamento dos registros e seqüências com o ato de salvar o arquivo na máquina do usuário. Mais uma vez, outros desenvolvedores poderiam, em uma interface Web, disponibilizar as linhas do arquivo em uma página HTML, ou enviá-las diretamente para seu destino (banco ou gráfica). Portanto, nossas classes de exportação possuirão um método que retornará um array de strings. Estas, por sua vez, serão passadas para a camada de interface, a qual decidirá o que deve ser feito. Tentamos utilizar o mesmo padrão das regras para a exportação. Por isso, adicionamos um método à classe ProcessadorDeFolha que irá devolver as linhas. O processador não irá conhecer quem são os arquivos de exportação. Desta maneira, manteremos um baixo acoplamento. O método é escrito da seguinte maneira: public string[] Exporta(Folha folha, Exportador exportador) { 48 return exportador.Exporta(folha); } Como podemos observar, tal método necessita de um Exportador como parâmetro. Este tipo, na verdade, refere-se a outra interface que possui um único método: string[] Exporta(Folha folha); Criamos tal interface, além de três classes que a implementam. Fornecendo assim, suporte necessário à disponibilização dos campos nas posições corretas dos registros e devolvendo um array de strings referente ao conteúdo do arquivo. Seriam as classes: • ExportadorBanco • ExportadorFGTS • ExportadorGráfica Uma para cada tipo de arquivo de exportação. Após todas as classes do projeto terem sido implementadas, temos também diversos test-cases para elas. Todos (total de 84 testes), ao final, devem estar passando e temos nosso projeto principal finalizado. 49 Figura 3 - Todos os 84 testes passando Conforme informado anteriormente, faríamos uso de uma ferramenta para executar uma engenharia reversa em nosso código, construindo um diagrama de classes. Embora a metodologia Test-Driven Development não requisite tal artefato, nosso processo nos permite tal atitude, pois será feito automaticamente e posterior ao desenvolvimento do projeto. Achamos o diagrama necessário para facilitar a visualização das classes de forma gráfica. Decidimos por gerar um diagrama de classes apenas para o projeto PayrollCalc, porém, sem as regras e os exportadores. Tal decisão foi tomada com base nas classes que realmente deverão ser compreendidas. Ou seja, se outra pessoa necessitar modificar o sistema, necessitará entender apenas seus elementos base, e não suas customizações, pois estas estão facilmente visíveis. Nem tampouco o projeto de interface gráfica, que segue fielmente o padrão MVC. Uma vez que nossa metodologia não requisita tais artefatos, 50 estaremos assim, ajudando os próximos desenvolvedores, sem confundi-los por excesso de documentação. Infelizmente, ao gerar nosso diagrama, encontramos um problema. Estamos utilizando a classe System.Collections.ArrayList do .NET Framework para algumas coleções. Quando a ferramenta executa a engenharia reversa, acaba gerando classes sem relacionamento algum, pois não reconhece que esta coleção referencia outro elemento. Por isso, modificamos nosso diagrama, alterando os atributos de ArrayList para Elemento[] (trocando Elemento pela classe do vetor, por exemplo, Regra). Isso fez com que o Together passasse a gerar automaticamente uma referência entre as duas figuras. Tivemos de adicionar manualmente mais algumas dependências ao diagrama (objetos passados por parâmetro não geram referência alguma) e, rapidamente, chegamos ao artefato abaixo. 51 Figura 4 - Diagrama de classes gerado automaticamente 4.2.3 IMPLEMENTANDO O PROJETO DE INTERFACE GRÁFICA Este projeto, na verdade, possui uma construção muito simples. Porém, é preciso que tomemos algumas decisões. A entrada de dados (funcionários e suas fichas ponto) será feita na forma de um arquivo XML. Poderíamos ter utilizado diversas outras soluções 52 como: bancos de dados, arquivos texto, outros programas, através do usuário, web services, conexões socket, etc. No entanto, encontramos nesse formato um tipo de arquivo bastante flexível e portável. Além disso, não seria necessário instalar nenhum programa na máquina cliente para carregá-lo, pois o Microsoft Windows já possui nativamente uma ferramenta de Parsing. Um exemplo da formatação que iremos suportar em nosso programa seria: <?xml version="1.0" encoding="ISO-8859-1"?> <funcionarios> <funcionario matricula="123"> <nome>Fulano da Silva</nome> <cpf>12345678901</cpf> <salario>3500,00</salario> <nroDependentes>3</nroDependentes> <dataAdmissao>"01/01/2007"</dataAdmissao> <inicioFeriasNoMesCorrente>0</inicioFeriasNoMesCorrente> <fimFeriasNoMesCorrente>0</fimFeriasNoMesCorrente> <fichaPonto> <ponto data="01/01/2007"> <estado>P</estado> <entradaTurnoEsperado>08:00</entradaTurnoEsperado> <saídaTurnoEsperado>18:00</saídaTurnoEsperado> <entradaTurnoRealizado>08:05</entradaTurnoRealizado> <saídaTurnoRealizado>18:00</saídaTurnoRealizado> <entradaIntervaloEsperado>12:00</entradaIntervaloEsperado> <saidaIntervaloEsperado>14:00</saidaIntervaloEsperado> <entradaIntervaloRealizado>12:00</entradaIntervaloRealizado> <saidaIntervaloRealizado>13:50</saidaIntervaloRealizado> </ponto> <ponto data="02/01/2007"> <estado>P</estado> <entradaTurnoEsperado>08:00</entradaTurnoEsperado> <saídaTurnoEsperado>18:00</saídaTurnoEsperado> <entradaTurnoRealizado>08:00</entradaTurnoRealizado> <saídaTurnoRealizado>18:10</saídaTurnoRealizado> <entradaIntervaloEsperado>12:00</entradaIntervaloEsperado> <saidaIntervaloEsperado>14:00</saidaIntervaloEsperado> <entradaIntervaloRealizado>12:00</entradaIntervaloRealizado> <saidaIntervaloRealizado>14:15</saidaIntervaloRealizado> </ponto> <ponto data="03/01/2007"> <estado>N</estado> <entradaTurnoEsperado>08:00</entradaTurnoEsperado> <saídaTurnoEsperado>18:00</saídaTurnoEsperado> 53 <entradaTurnoRealizado>08:00</entradaTurnoRealizado> <saídaTurnoRealizado>18:00</saídaTurnoRealizado> <entradaIntervaloEsperado>12:00</entradaIntervaloEsperado> <saidaIntervaloEsperado>14:00</saidaIntervaloEsperado> <entradaIntervaloRealizado>12:00</entradaIntervaloRealizado> <saidaIntervaloRealizado>14:10</saidaIntervaloRealizado> </ponto> <!--Outros Pontos...--> </fichaPonto> </funcionario> <!--Outros Funcionários...--> </funcionarios> A exportação de arquivos será feita para o disco rígido. Clicando em um botão, será possível escolher o local para salvar, através de uma janela de diálogo. Após algum trabalho chegamos à interface a seguir: Figura 5 - Visualização da Interface Gráfica No formulário, o usuário tem acesso a todas as opções do projeto principal. Os detalhes da folha para cada funcionário são mostrados do lado diteito da tela, bem como seu salário líquido. Os botões para a exportação dos arquivos só são habilitados após o cálculo da folha. Em nosso exemplo, de apenas dois funcionários, temos os seguintes arquivos exportados: 54 FGTS )ΞΟ∆ΘΡΓ∆6ΛΟΨ∆&' %ΗΟΩΥ∆ΘΡ6ΡΞ]∆&' Gráfica )ΞΟ∆ΘΡΓ∆6ΛΟΨ∆&'' )∗76.5/0 ,166.5/0 6∆ΟιΥΛΡ)∆ΠτΟΛ∆.5/0 ,5).5/&0 )πΥΛ∆ς.5/0 3∆ΥΦΗΟ∆∋πΦΛΠΡ7ΗΥΦΗΛΥΡ.5/0 ∋ΗςΦΡΘΩΡ)∆ΟΩ∆.5/0 &%ΗΟΩΥ∆ΘΡ6ΡΞ]∆&'&' )∗76.5/0 ,166.5/0 6∆ΟιΥΛΡ)∆ΠτΟΛ∆.5/0 ,5).5/'0 )πΥΛ∆ς.5/0 3∆ΥΦΗΟ∆∋πΦΛΠΡ7ΗΥΦΗΛΥΡ.5/0 ∋ΗςΦΡΘΩΡ)∆ΟΩ∆.5/0 Banco )ΞΟ∆ΘΡΓ∆6ΛΟΨ∆&' &%ΗΟΩΥ∆ΘΡ6ΡΞ]∆&&' Os quais seguem fielmente a especificação. Internamente, temos para nosso projeto de Interface Gráfica, uma classe que representa o formulário e possui como atributo um objeto do tipo PayrollInterfaceController. Este, por sua vez, possui uma instância de ProcessadorDeFolha, chamando seus métodos quando necessário, além de ser responsável por carregar e salvar os arquivos de entrada e saída. Terminado o projeto de Interface Gráfica, nosso sistema para o cálculo de folhas de pagamento está pronto. Agora vamos partir para a 55 segunda parte de nosso trabalho, a de portá-lo para uma versão Language Oriented, fazendo assim a análise comparativa entre os dois paradigmas. 4.3 DESENVOLVIMENTO DA VERSÃO ORIENTADA A LINGUAGENS 4.3.1 ESCOLHENDO OS ELEMENTOS NECESSÁRIOS Para dar o primeiro passo, precisamos definir qual a ferramenta para a geração das DSLs iremos utilizar. Poderíamos também criá-las sem o auxílio de um software especializado, fazendo toda a construção de um editor, sua leitura e sua tradução para o código final. Todavia, acreditamos não ser esse o intuito do projeto. Após diversas pesquisas, chegamos a duas ferramentas bastante interessantes que nos dariam esse suporte: • Microsoft DSL Tools o Totalmente integrado ao Microsoft Visual Studio 2005. o Podemos customizar o código utilizando o Visual C# e o VB.NET. o Fortemente baseado em modelos gráficos para o desenho da linguagem e uso da mesma. o Pouca documentação disponível. • MPS o Voltado à plataforma Java. o Construído por Sergei Dmitriev (2005), autor do principal artigo na área do LOP. o Fortemente baseado em modelos de texto para o desenho da linguagem e uso da mesma. o Pouca documentação disponível. 56 Como um de nossos principais problemas é a nossa inexperiência com esse tipo de projeto, optamos por utilizar a primeira ferramenta, o Microsoft DSL Tools. Acreditamos ser esta a melhor escolha, por ser totalmente integrado em um ambiente ao qual já estamos familiarizados, bem como a sua linguagem base, à qual já tivemos bastante contato. 4.3.2 APRENDENDO A UTILIZAR A FERRAMENTA Em um primeiro momento, encontramos relativa dificuldade até começarmos efetivamente o inicio do desenvolvimento. Algumas semanas de entendimento e configuração foram necessárias até que pudéssemos desenhar nosso primeiro diagrama extremamente simples. Começamos procurando um local para fazer o download da ferramenta. Na página de suporte ao desenvolvedor da Microsoft, encontramos uma versão do pacote Visual Studio SDK 2005, o qual já continha o Microsoft DSL Tools. Este pacote de ferramentas é destinado à construção de extensões e outros elementos para o ambiente de desenvolvimento Microsoft Visual Studio 2005. Portanto, a grande maioria dos programas instalados não foram utilizados. Fizemos uso apenas do que se referia à parte de Domain-Specific Languages. Logo que terminamos o download do produto, descobrimos que esta se tratava de uma versão do tipo Beta, ou seja, que ainda não havia sido lançada no mercado. E isso nos deixou um pouco apreensivos quanto ao andamento do projeto. Nosso primeiro desafio foi instalar a versão do Microsoft Visual Studio 2005 para que, então, pudessemos instalar suas extensões. Após a instalação de ambos os projetos, pensamos estar tudo pronto para começarmos, porém, alguns problemas vieram a aparecer. O mais significativo foi devido ao fato de não termos instalado o compilador para C/C++ que acompanha o primeiro produto. Isso fez com que nem os próprios exemplos da ferramenta pudessem ser compilados. No entanto, depois de encontrar a solução, executá-la foi simples: apenas instalamos o compilador. 57 Conseguimos compilar o código fonte do primeiro exemplo da ferramenta e executá-lo sem problema algum. Porém, como esse foi nosso primeiro contato com o programa, não conseguimos entender prontamente como faríamos para criar nosso próprio projeto. Após alguma procura na internet, encontramos alguns vídeos de autores da ferramenta, explicando como ela seria quando fosse lançada. No entanto, não existia nenhum artigo de forma a nos explicar “passo a passo” como criar nossa própria linguagem. Nossa busca se encerra quando abrimos o arquivo de Help do programa. Lá, pudemos encontrar informações detalhadas sobre cada elemento do software que estaríamos utilizando. Quando pensamos em Domain-Specific Languages, imaginamos a sua criação pela definição dos elementos (estrutura), do editor e da semântica (DMITRIEV, 2005). No entanto, não conseguíamos encaixar os conceitos que havíamos levantado até agora na ferramenta. A grande diferença do Microsoft DSL Tools está na sua forma gráfica de se escrever linguagens e de se utilizá-la. Podemos então comparar este ambiente com o do projeto IP (SIMONYI, 1995), citado anteriormente, no qual, podemos representar um programa utilizando árvores e figuras, por exemplo. 4.3.3 MUDANÇAS NO PROJETO INICIAL Nosso primeiro desafio nesta fase foi migrar o projeto construído com o Microsoft Visual Studio 2003 para o Microsoft Visual Studio 2005. Tarefa esta, que não apresentou grandes dificuldades, pois existe uma compatibilidade de código entre os dois ambientes. Tivemos apenas de abrir nosso projeto no novo ambiente e um assistente de importação cuidou do resto. O projeto havia então sido importado e poderíamos começar a utilizar o novo IDE. 58 Figura 6 - Assistente de importação de projetos do Visual Studio A versão 2003 do ambiente, utiliza a linguagem Visual C# e compila para o Framework .NET 1.1. Já o Microsoft Visual Studio 2005, foi construído sobre uma versão nova da plataforma, o framework .NET 2.0. Isso significa que ganhamos algumas funcionalidades, provenientes da nova versão. Além disso, o código fonte que havíamos escrito é compatível e continua compilando normalmente. Tivemos também de fazer o download da versão mais nova do NUnit, pois, a que estávamos utilizando, havia sido compilada em C# 1.1. Fizemos isso e, após a instalação da ferramenta, tudo passou a funcionar normalmente, agora, na arquitetura nova (.NET Framwork 2.0). 59 Figura 7 - Testes passando na versão 2.0 do .NET Framework Após planejar bastante nosso projeto, tomando como base alguns exemplos da ferramenta e nosso conhecimento obtido até agora, decidimos por criar duas Domain-Specific Languages: 1. Payrule – Criação de regras para o cálculo da folha 2. Payxport – Criação de layouts para os arquivos de exportação Deste modo, poderemos demonstrar a criação de duas linguagens diferentes e a integração das duas com nosso projeto base. Nesta fase, após olhar inúmeros exemplos do próprio Microsoft DSL Tools e alguns outros disponíveis na internet, começamos a entender mais sobre o que viria a acontecer com nosso projeto. Na verdade, DSLs vão sempre gerar código para um domínio específico. Seja para uma linguagem de programação específica (C#, Java, Deplhi...), para o próprio sistema operacional (através de um aplicativo 60 executável), para um relatório (HTML, PDF, TXT) ou para um framework já construído. No nosso caso, nosso Framework será o projeto PayrollCalc. Um framework de aplicação orientado a objetos é definido como sendo basicamente uma aplicação inacabada, referente a um domínio de problema, a ser personalizado para cada situação. Ou seja, teremos um projeto com interfaces prontas para a extensão (RegraFolha e Exportador) e este será nosso framework. Nosso intuito será então o de desenhar duas linguagens (Payrule e Payxport) para facilitar a construção e manutenção de regras de folha de pagamento e arquivos de exportação. Desta maneira, imaginando uma eventual mudança na legislação vigente, um funcionário leigo na área de programação de computadores poderia facilmente compreender e modificar tais regras. Da mesma forma, os arquivos também poderiam sofrer alterações em sua estrutura, as quais seriam prontamente atendidas utilizando-se nossa linguagem. Voltando ao projeto PayrollCalc, com a finalidade de atender ao novo paradigma, separamo-lo na seguinte estrutura: • PayrollCalc – Classes comuns, framework para rodar as regras • PayrollCalc.Regras – Projeto com as regras e a fábrica de regras • PayrollCalc.Exportacao – Projeto com os exportadores para arquivos Deste modo, as linguagens Payrule e Payxport resultarão em Assemblies .NET. Estes, substituirão PayrollCalc.Regras e PayrollCalc.Exportacao, respectivamente (gerados utilizando-se o paradigma Orientado a Objetos), e poderão ser carregados pelo projeto PayrollInterface normalmente, sem diferença alguma. 4.3.4 UTILIZANDO O MICROSOFT DSL TOOLS Para a criação de uma nova Domain-Specific Language no Microsoft DSL Tools, devemos seguir basicamente os seguintes passos: 61 1. Cria-se um projeto de Domain-Specific Language. Figura 8 - Início da criação de uma Domain-Specific Language 2. Nele, podemos especificar atributos como: a extensão a ser utilizada nos arquivos de código-fonte da linguagem, um ícone para tais arquivos, um template (modelo base da esquematização dos elementos da linguagem), um arquivo de chave para proteger o projeto, etc. 3. Um projeto é criado com base no template que especificamos. 62 Figura 9 - Domain-Specific Language pronta para ser criada 4. Adicionamos, ao diagrama, componentes do tipo Domain Classes, que servirão como elementos do diagrama (editor) de nossa linguagem. 5. Podemos também adicionar propriedades a essas classes. 6. Adicionamos as relações entre as Domain Classes como: herança, composição e referência. 63 Figura 10 - Driagrama da Domain-Specific Language sendo editado 7. Configuramos os componentes no diagrama e suas inter-relações. 8. Adicionamos Shapes que corresponderão aos elementos gráficos para mostrar as classes e relações. 9. Adicionamos decoradores a esses Shapes para prover informações mais rapidamente aos usuários de nossa linguagem. 64 Figura 11 - Decoradores sendo editados 10. Adicionamos botões na barra de ferramentas do editor a ser criado, correspondendo às nossas Domain Classes e suas relações. 65 Figura 12 - Elementos da Toolbar sendo adicionados 11. Iniciamos o programa em modo de depuração (uma versão do Microsoft Visual Studio é aberta para utilizarmos nossa linguagem). Figura 13 - Utilização da Domain-Specific Language 12. Neste momento, podemos criar TextTemplatings que farão a tradução do modelo para o código final. Temos de especificar 66 toda e qualquer tradução que estará disponível. E tudo isso é feito utilizando-se ou C#, ou VB.NET. 13. Criamos nossos diagramas e seus elementos. 14. Rodamos os TextTemplatings que criamos anteriormente para que eles gerem o resultado final (geralmente código fonte). 15. Compilamos o projeto (poderá ser uma biblioteca ou um executável). 4.3.5 CRIAÇÃO DA PRIMEIRA DOMAIN-SPECIFIC LANGUAGE – PAYRULE Desde o momento em que começamos a desenhar no diagrama até o momento em que a primeira Domain-Specific Language estava pronta e testada, tivemos de reescrevê-la diversas vezes. Isso nos tomou bastante tempo. Contudo, não podemos levar isto em conta, uma vez que não temos experiência no ambiente. Primeiramente, pensamos em chamar nosso diagrama principal de Regra. Após isso, adicionamos uma Domain Class (elemento que representa um conceito da linguagem no Microsoft DSL Tools) chamada Funcionario. Este seria o ponto de partida para qualquer regra. Adicionamos também outra Domain Class chamada Saldo, o qual seria o ponto final. Até este ponto, tudo funcionou perfeitamente, no entanto, não havíamos feito um bom planejamento de nossa DSL antes de começar. Talvez a melhor técnica que inventamos e utilizamos para criar nossas Domain-Specific Languages foi compará-la com a metodologia que estávamos seguindo no projeto anterior. No Test-Driven Development, para todo código a ser escrito, devemos ter antes certeza do que estamos esperando. Para isso, escrevemos um teste e a codificação termina exatamente quando o teste passar. Como não conseguimos encontrar nenhum estudo nessa área, resolvemos utilizar um processo bastante simples: em uma folha de papel, desenhamos como gostaríamos que ficassem todas as regras que precisaríamos em nosso projeto, utilizando a nova DSL. Apesar de muito 67 esforço para tentar imaginar como ficariam os elementos de um diagrama desta maneira, conseguimos excelentes resultados. Pudemos planejar como ficariam todas as nossas classes e como gostaríamos de gerar nosso código. Após analisar cada regra que deveríamos gerar, chegamos a diversos pontos em comum a todas elas e, sobretudo, quais entradas deveríamos ter em nossos diagramas para que um cálculo pudesse ser realizado e um saldo a ser somado ao salário bruto do funcionário fosse retornado. A parte mais difícil neste momento foi definir como seriam feitas as entradas no diagrama. Não poderíamos ter apenas uma Domain Class Funcionario com vários atributos, como havíamos pensado primeiramente, pois, no Microsoft DSL Tools, faz-se necessário relacionar um elemento ao outro, e não um elemento a uma propriedade de outro. Talvez haja uma maneira de fazer isso, mas estamos preocupados também com a facilidade do uso de nossa linguagem. Portanto, decidimos criar um elemento (Domain Class) para cada uma das entradas do diagrama. Os seguintes foram criados: • Salário Bruto – Será traduzido para o salário bruto do funcionário em questão. Através de uma propriedade nessa Domain Class, o usuário da linguagem vai definir a granularidade a ser utilizada: Ano, Mês, Dia, Hora ou Minuto. • Qtd. Horas Extras no Período – Retorna a quantidade de horas extras realizadas no fechamento em um determinado período horário (das 08:00 às 20:00, por exemplo). • Qtd. Dias em Estado – Quantidade dos dias nos estados Presença, Falta, Afastamento ou Dia Não Útil, durante o fechamento. • Qtd. Meses Trabalhados – Quantidade de meses trabalhados no ano atual. • Qtd. Dias de Férias no Mês – Quantidade de dias de férias no fechamento. • Qtd. Filhos – Quantidade de filhos. 68 • Qtd. Faltas – Quantidade de faltas durante o ano (a ser utilizada na regra de cálculo de férias) • Mês Atual – Valor numérico representando o mês atual (1-12). • Valor – Um número especificado por uma propriedade. Nosso diagrama terá entradas e, após o cálculo, o resultado deverá ser enviado para um elemento final. Portanto, criamos outra Domain Class, cujo nome foi configurado como Saldo. Além disso, uma regra não é feita apenas de uma entrada sendo enviada para o elemento final. Percebemos que, nas regras que já havíamos implementado, havia duas ações a serem tomadas: efetuar um cálculo simples (Soma, Subtração, Multiplicação e Divisão) ou submeter a entrada a uma escolha, através de faixas de valores. Portanto, criamos mais três Domain Classes: • Cálculo – Tem como parâmetros uma entrada primária e uma entrada secundária (representando os operandos do cálculo). Além do tipo de cálculo a ser feito (operador). • Escolha – Uma entrada poderá ser ligada a vários elementos de escolha. Cada um destes terá as propriedades De e Até, que definirão uma faixa de valores. Caso Até seja configurada como 0 (zero), a condição não terá um limite superior. A Domain Class Escolha possui, além de uma entrada, um Valor relacionado. Será explicada em detalhes mais a diante. • Resultado Escolha – Um conjunto de Escolhas resulta em um Resultado Escolha com o Valor da Escolha resultante da operação. Levando em conta o fato do método principal de uma RegraFolha ser o CalculaERetornaSaldo, existe mais um detalhe que não deve ser deixado de lado: há um objeto Detalhamento, passado como parâmetro, e este deve ser preenchido com os valores corretos. Contudo, essa não foi uma tarefa complicada de se resolver. Inserimos mais uma Domain Class denominada Detalhe que possui uma propriedade Valor. Além disso, ela deve receber uma 69 Entrada qualquer. A propriedade pode ser configurada com qualquer mensagem (String), e terá a seqüência $VALOR$ substituída pelo valor de sua Entrada. No Microsoft DSL Tools, precisamos definir também os conectores que inter-relacionarão as Domain Classes. Tentamos, porém, criá-los com funções bem definidas. Seriam: • Resulta – Será utilizado para ligar uma Entrada qualquer a um componente de ação ou a um Saldo. • Entra – Será utilizado como valor para um objeto Escolha ou como entrada secundária de um Cálculo. • Detalha – Será utilizado para ligar uma Entrada a um objeto Detalhe. Neste ponto, notamos a necessidade da utilização de Herança em nossa DSL. Fazer a ligação de todos os elementos que podem representar Entradas aos seus próximos seria uma tarefa muito trabalhosa e deixaria difícil a compreensão de nosso diagrama. Uma ligação de herança, como na orientação a objetos, define que relações entre os elementos e propriedades sejam herdadas de uma classe hierarquicamente superior. Deste modo, a Domain Class SalarioBruto herdará Entrada e, com isso terá todas as suas propriedades e relações. Finalmente, para facilitar a compreensão, mostraremos um quadro com cada elemento e seus detalhes: Nome Herda de Propriedades VaiParaSaldo: Saldo (0..1) Toda entrada poderá ou não ir para um, e somente um, objeto do tipo Entrada - Saldo através de um conector Resulta. VaiParaCalculo: Calculo (0..1) Toda entrada poderá ou não ir para 70 um, e somente um, objeto do tipo Calculo através de um conector Resulta. VaiParaCalculoComoSegunda: Calculo[] (0..*) Toda entrada poderá ou não ir para um ou mais objetos do tipo Calculo significando um segundo operando através de um conector Entra. VaiParaEscolhas: Escolha[] (0..*) Toda entrada poderá ou não ir para um ou mais objetos do tipo Escolha através de um conector Resulta. VaiParaEscolhasComoValor: Escolha[] (0..*) Toda entrada poderá ou não ir para um ou mais objetos do tipo Escolha significando um valor através de um conector Entra. Detalhes: Detalhe[] (0..*) Toda entrada poderá ou não ir para um ou mais objetos do tipo Detalhe através de um conector Detalha. Granularidade: String SalarioBruto Entrada Poderá ser definida como Ano, Mês, Dia, Hora ou Minuto. De: String QtdHorasExtrasPeriodo Entrada Deverá seguir o formato hh:nn e representar o intervalo inferior do período. 71 Até: String Deverá seguir o formato hh:nn e representar o intervalo superior do período. Estado: String QtdDiasEstado Entrada Poderá ser definida como Presença, Falta, Afastamento ou Dia Não Útil. QtdMesesTrabalhados Entrada - QtdFeriasMes Entrada - QtdFilhos Entrada - QtdFaltas Entrada - MesAtual Entrada - Valor Entrada Numero: Ponto Flutuante Representa um valor real. Entrada: Entada (1..1) Todo cálculo necessita obrigatoriamente de um, e somente um, objeto do tipo Entrada através de um conector entrada Calculo Entrada Resulta. representará o Esta primeiro operando. SegundaEntrada: Entrada (1..1) Todo cálculo necessita obrigatoriamente de um, e somente um, objeto do tipo Entrada através de um conector Entra. Esta segunda entrada representará operando. o segundo 72 Operacao: String Poderá ser definida como: Soma, Subtração, Multiplicação ou Divisão. O cálculo será feito da seguinte forma: [Entrada] [Operacao] [SegundaEntrada] Entrada: Entrada (1..1) Toda escolha necessita obrigatoriamente de um, e somente um, objeto do tipo Entrada através de um conector Resulta. Esta entrada servirá como objeto de comparação com o intervalo da Escolha. EntradaValor: Entrada (1..1) Toda escolha necessita obrigatoriamente de um, e somente Escolha - um, objeto do tipo Entrada através de um conector Entra. Esta entrada será o valor resultante, caso a condição de intervalo seja atendida. ResultadoEscolha: ResultadoEscolha (1..1) Toda escolha irá obrigatoriamente para um, e somente um, objeto do tipo ResultadoEscolha através de um conector Resulta. De: Ponto Flutuante Representa o limite inferior da 73 condição. Ate: Ponto Flutuante Representa o limite inferior da condição. Essa condição será feita da seguinte forma: se [De] <= [Entrada] <= [Ate] retorna [EntradaValor] Escolhas: Escolha[] (1..*) Todo ResultadoEscolha necessita obrigatoriamente de um ou mais ResultadoEscolha Entrada objetos do tipo Escolha através de um conector Resulta. A escolha que for avaliada positivamente terá o seu valor no ResultadoEscolha em questão. Entrada: Entrada (1..1) Todo Saldo - Saldo necessita obrigatoriamente de um, e somente um, objeto do tipo Entrada. Este valor será retornado no método CalculaERetornaSaldo. Entrada: Entrada (1..1) Todo Detalhe necessita obrigatoriamente de um, e somente Detalhe - um, objeto do tipo Entrada. Este valor será utilizado na propriedade a seguir. Valor: String Representa a mensagem a ser 74 detalhada. A seqüência $VALOR$ será substituída pelo resultado da entrada. Listagem 5 - Elementos de Payrule e seus detalhes Concluídas propriedades, as iniciamos Domain a Classes, construção dos seus Shapes relacionamentos (formas) que e as representarão no diagrama. Bem como a dos conectores. Cada Shape tem os seus Decorators, os quais mostram o texto nas figuras. Nome SalarioBruto QtdHorasExtrasPeriod o QtdDiasEstado QtdMesesTrabalhados QtdFeriasMes QtdFilhos Shape 75 QtdFaltas MesAtual Valor Calculo Escolha ResultadoEscolha Saldo Detalhe 76 Resulta Entra Detalha Listagem 6 - Shapes de Payrule Além disso, mudamos a cor de fundo do diagrama para Light Yellow. Entretanto, quando iniciamos o teste de nossa linguagem, ainda não podemos adicionar nenhum elemento ao diagrama. Para isso, precisamos de elementos de Toolbar. Adicionamos todos os componentes que poderão ser inseridos e chegamos à seguinte barra: 77 Figura 14 - Toolbox gerada para a linguagem Payrule Estamos finalizando a construção de nossa DSL. Continuamos adicionando diversas validações às nossas Domain Classes, tais validações serão executadas sempre que um arquivo de nossa linguagem for aberto, salvo, ou sempre que o usuário selecionar a opção Validate no menu popup do diagrama. Estas necessitam ser escritas em forma de código e requerem um atributo especial (recurso do .NET Framework para modificar o comportamento de métodos e classes) indicando que o método será uma validação. Criamos então para algumas Domain Classes uma Partial Class (outro recurso do .NET Framework que permite a extensão de classes sem a utilização de herança), conforme a documentação do Microsoft DSL Tools. Domain Class Métodos de Validação OperacaoEnumeration Calculo Uma operação só poderá ser definida como: Soma, Subtração, Multiplicação ou Divisão. 78 IntervaloValido A propriedade De deve ser menor do Escolha que a propriedade Ate, a menos que a segunda contenha o valor 0 (zero). EstadoValido QtdDiasEstado O estado somente poderá ser definida como Presença, Falta, Afastamento ou Dia Não Útil. GranularidadeEnumeration SalarioBruto A granuralidade somente poderá ser definida como Ano, Mês, Dia, Hora ou Minuto. Listagem 7 - Validações de Payrule Obviamente, o desenvolvimento de tudo o que foi mostrado levou um tempo considerável e necessitou de diversos testes e modificações antes de ficar totalmente pronto. Contudo, estamos finalmente com nosso editor pronto para ser utilizado. Abaixo, alguns exemplos de uso da linguagem: Exemplos Cálculo 79 Multiplicação entre o Salário Bruto Mensal por 75% Escolha Se o Salário Bruto Mensal estiver entre 0 e 1000, o resultado será 0; Se o Salário Bruto Mensal estiver entre 1000 e 2000, o resultado será 0,5; Se o Salário Bruto Mensal for maior do que 1000, o resultado será 1. Listagem 8 - Exemplos de uso de Payrule É chegada a etapa final da construção da primeira Domain-Specific Language: a Semântica. Precisamos construir TextTemplates que irão ler nosso diagrama, gerando texto no formato de código fonte em C# e, em seguida, compilados em um Assembly .NET chamado PayrollCalc.Regras. A construção dos TextTemplates é feita em formato texto, basicamente adicionando-se Tags especiais ao código a ser gerado. Por exemplo, de acordo com nosso modelo, para percorrermos a lista de entradas, gerando código para cada uma delas, utilizaríamos um TextTemplate de forma semelhante a: 80 <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='Regra.pr'" #> public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { Saldo = <# foreach (Entrada entrada in this.Regra.Entradas) { if (entrada is Valor) { WriteLine((entrada as Valor).Numero); break; } else if (entrada is QtdFilhos) { WriteLine("funcionario.NroDependentes"); break; } } #>; } Isso faria com que percorrêssemos a lista de entradas e, caso encontrássemos uma Entrada do tipo Valor, imprimiríamos o conteúdo de sua propriedade Numero. Caso encontrássemos primeiro uma QtdFilhos, imprimiríamos funcionario.NroDependentes. Além disso, imprimiríamos o corpo do método CalculaERetornaSaldo em volta do resultado. A tarefa da tradução semântica foi, nesse caso, a mais complicada e demorada, pois não sabíamos o que nos esperava ao construirmos nosso diagrama. Mesmo assim, pensamos da seguinte forma: 1. Partindo do elemento Saldo da Regra, chamamos um método para pegar o valor de sua Entrada. a. Se sua Entrada for i. Calculo – chamaremos recursivamente para os a dois mesma função operandos concatenaremos o sinal da operação entre eles. e 81 ii. Escolha – criaremos um método na classe final e no corpo do mesmo, colocaremos uma estrutura de vários IFs, comparando as Entradas e retornando um Valor. Para as Entradas e para o Valor, chamaremos o mesmo método utilizado para retornar o valor de uma Entrada, recursivamente. iii. Outra – faremos a tradução simples, de acordo com as propriedades e requisitos de cada elemento. b. Para cada Detalhe, geraremos um método que chame detalhamento.AddItem, traduzindo a palavra $VALOR$ e retornando o mesmo valor de entrada do método como resultado da função. 2. Procuraremos todos os Detalhes, cujas Entradas não levam ao Saldo e geraremos código e método para eles conforme descrito acima. Depois de um tempo considerável, chegamos a uma primeira versão estável de nosso TextTemplate principal. No entanto, sabemos que o mesmo gerador de código será utilizado para todas as regras. Por isso, chamamos seu arquivo de Payrule.ttimport e decidimos que, para cada regra nova, o usuário terá de criar: • Um diagrama novo (com a extensão .pr); • Um TextTemplate novo (com a extensão .tt). Este apenas importará nosso gerador de código genérico. O código de um TextTemplate para uma regra, supondo que o nome do arquivo de seu diagrama seja RegraQualquer.pr, será apenas: <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraQualquer.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> 82 Alem disso, criamos mais um TextTemplate para a classe RegraFolhaArrayFactory. Entretanto, como o código final será sempre o mesmo, o TextTemplate ficou igual ao código gerado. Finalmente, organizamos os arquivos nas seguintes pastas: • AdditionalCode – Código adicional. Até o momento, temos apenas o TextTemplate da classe RegraFolhaArrayFactory. • Base – Diretório para os arquivos base. No caso, apenas o gerador de código genérico. • Regras – Lugar para os arquivos referentes a cada regra. Feito isso, nossa primeira Domain-Specific Language está pronta e podemos utilizá-la para começar a construção das regras. Ao final, apenas adicionaremos o nome de cada uma na classe RegraFolhaArrayFactory e teremos nosso Assembly .NET gerado e pronto para ser usado. 4.3.6 CRIAÇÃO DAS REGRAS UTILIZANDO A LINGUAGEM PAYRULE Após a criação da primeira linguagem, passamos à figura de Desenvolvedor do Software, não mais de Desenvolvedor da Linguagem. No entanto, como esta foi nossa primeira Domain-Specific Language, tivemos de voltar algumas vezes à edição da mesma, pois havíamos esquecido alguns detalhes. Por exemplo, a Domain Class QtdFaltas só foi descoberta no momento de desenharmos a regra para o cálculo das férias. Contudo, o desenvolvimento de todas as regras levou pouco mais de uma hora pra ficar pronto. E isso foi certamente mais rápido do que ter de escrever código para todas elas. Além disso, agora está muito mais simples fazer a manutenção das regras, pois estamos lidando apenas com conceitos próprios do domínio Cálculo de Folha de Pagamento. Muito provavelmente, um leigo em matéria de desenvolvimento de software poderia modificar qualquer uma das regras escritas sem grandes esforços, além de criar suas próprias outras. 83 Com todas as regras prontas, chegamos à tarefa mais difícil e “temida” até o momento: compilar submeter o Assembly gerado ao projeto de testes, construído para o projeto Orientado a Objetos. Conforme esperávamos, nem todos os testes passaram na primeira tentativa. Cometemos alguns enganos na tradução (como esquecer das aspas para os valores) e no desenho das regras (multiplicar por um valor errado, ou especificar uma operação diferente da esperada). Todavia, consertados os erros mais simples, em pouco tempo, estávamos com nosso novo Assembly totalmente funcional, rodando perfeitamente sobre o projeto de interfaces e sobre o projeto de testes. 4.3.7 CRIAÇÃO DA SEGUNDA DOMAIN-SPECIFIC LANGUAGE – PAYXPORT Como a experiência adquirida na construção da primeira DomainSpecific Language havia sido bastante grande, levamos apenas 4 horas para desenvolver a segunda linguagem. Provavelmente, menos do que um terço do esforço feito para a confecção da primeira. Novamente, perdemos um tempo considerável planejando como ficariam os diagramas gerados pela nossa linguagem. No entanto, algumas das atitudes tomadas anteriormente tiveram um retorno bastante positivo. Estas foram reaproveitadas em detrimento das que não deram tão certo. Uma das lições que aprendemos foi quanto ao desenho prévio dos diagramas em uma folha de papel. Além disso, aproveitamos para imaginar, desta vez, como ficariam nossos TextTemplates ao final do processo. Nossa primeira dificuldade foi a mesma que tivemos anteriormente: como ficariam os valores de entrada? Como seriam especificados os campos das linhas? E as próprias linhas do arquivo? Decidimos utilizar, desta vez, outra maneira de referência entre as linhas e os campos: as linhas serão figuras as quais irão conter os campos, em vez de referenciá-los simplesmente. Ou seja, não necessitaremos de um conector nesse caso. As figuras de campo serão posicionadas dentro das de linha. 84 Decidimos também que teremos dois tipos de linha: Funcionário e Detalhe. No entanto, não teremos duas Domain Classes para isto. Teremos apenas uma e seu tipo será especificado por uma propriedade. Além disso, as linhas serão colocadas soltas no diagrama, mas possuirão uma propriedade especificando a sua ordem de aparição no arquivo. Poderemos então haver, por exemplo, 3 linhas de Funcionário e duas de Detalhe. Assim, para cada registro da folha de pagamento, retornarão, em ordem: • 3 linhas com dados do Funcionário em questão; • 2 linhas para o detalhamento. Em alguns casos, não teremos linhas de Detalhe, porém, ao menos uma linha de qualquer tipo deve ser adicionada ao arquivo. Para os campos, também teremos uma propriedade representando sua ordem. Contudo, o usuário terá de especificar também seu tamanho e, desta vez, faremos uso de outra funcionalidade muito interessante do Microsoft DSL Tools: as propriedades calculadas. Aos campos, serão adicionadas duas destas: De e Ate. Ou seja, sempre que houver uma modificação nas propriedades Ordem e Tamanho de um campo, suas propriedades De e Ate serão recalculadas para mostrar as posições inicial e final atualizadas na linha corrente. Aproveitando o uso das propriedades calculadas, vamos adicionar na Linha uma propriedade Tamanho, que será calculada somando-se o tamanho de todos os campos inseridos. Ainda no que se refere aos campos, seu valor dependerá da Domain Class utilizada e também utilizaremos herança em sua definição. Ficaremos com os seguintes tipos de campo: • Texto – Segue o mesmo princípio do elemento Valor, da linguagem anterior. O usuário poderá especificar qualquer texto como valor para este campo. • Funcionario – Retorna o valor de uma propriedade do funcionário em questão. 85 • Detalhe – A mensagem de detalhe em questão. • SalarioLiquido – Retorna o Salário Líquido calculado. Todos eles herdando de Campo. Porém, considerando nosso conjunto de campos, como faremos para representar o valor do FGTS de um Funcionário no arquivo? No sistema baseado no paradigma Orientação a Objetos, sendo o valor do FGTS apenas exportado e não influenciando no Salário Líquido, fazemos uso da Regra de FGTS na classe do Exportador. Portanto, não achamos consistente, incluir uma Domain Class para isto. Seria extremamente específico e fugiria ao contexto. Vamos criar, em vez disso, mais um campo, o Custom, que terá apenas uma propriedade Nome e, posteriormente, o usuário especificará o código necessário para o cálculo deste valor. Fechamos então com as seguintes Domain Classes: Nome Herda de Propriedades Campos: Campo[] (1..*) Toda linha terá obrigatoriamente um ou mais objetos do tipo Campo. Ordem: Inteiro Representa a ordem da linha no Linha - arquivo. Existirá uma ordem para linhas do tipo Funcionário e outras para linhas do tipo Detalhe. Deve começar a partir do número 1. Tamanho: Inteiro (Calculada) Será calculada somando-se o tamanho de todos os campos da linha. Campo - Linha: Linha (1..1) Todo campo estará em um, e somente 86 um, objeto do tipo Linha. Ordem: Inteiro Representa a ordem do campo na linha. Deve começar a partir do número 1. Tamanho: Inteiro O número de caracteres que o campo deve ocupar na linha. De: Inteiro (Calculada) Calculada com base na posição inicial do campo na linha. Ate: Inteiro (Calculada) Calculada com base na posição final do campo na linha. Texto Campo Valor: String O valor do texto. Funcionario: String Funcionario Campo Propriedade da classe Funcionario, que aparecerá como valor do campo. Detalhe Campo - SalarioLiquido Campo - Custom Campo - Listagem 9 - Elementos de Payxport Concluídas as Domain Classes e suas propriedades, iniciamos a construção dos Shapes (formas) que as representarão no diagrama. 87 Nome Shape Linha Texto Funcionario Detalhe SalarioLiquido Custom Listagem 10 - Shapes de Payxport Modificamos então a cor de fundo do diagrama para Honeydew. Construímos também algumas regras de validação: 88 Domain Class Métodos de Validação OrdemDasLinhasFuncionario As linhas do tipo Funcionário devem estar ordenadas e devem começar por 1. OrdemDasLinhasDetalhe As linhas do tipo Detalhe devem estar ordenadas e devem começar por 1. Arquivo TamanhoDasLinhas Recomenda-se que o tamanho das linhas seja o mesmo. A diferença desta validação é que ela não gera um erro, mas sim um aviso de Warning para o usuário que, ainda assim, consegue compilar seu programa. TamanhoDoCampo Campo O tamanho do campo não pode ser menor do que o valor 1. PropriedadeEnumeration Funcionario A propriedade Propriedade só pode receber Matrícula, Nome, CPF ou Salário. OrdemDosCampos Os campos devem estar ordenados e Linha devem começar por 1. TipoEnumeration O tipo da linha só Funcionário ou Detalhe. pode ser 89 Listagem 11 - Validações de Payxport Além disso, escrevemos algumas Partial Classes para as propriedades calculadas, comentadas anteriormente. Podemos então, chegar a um exemplo de uso da linguagem. Exemplos Arquivo com Linhas de Funcionário e Detalhe Linha Funcionário com: 1. Campo Texto com valor “1” e tamanho 1; 2. Campo Funcionario com propriedade Nome e tamanho 20; 3. Campo SalarioLiquido de tamanho 10. Linha Detalhe com: 1. Campo Texto com valor “2” e tamanho 1; 2. Campo Detalhe de tamanho 30; Listagem 12 - Exemplos de uso de Payxport A etapa da semântica, desta vez, foi muito mais rápida do que a anterior. O planejamento prévio nos ajudou na hora de escrever o código. Além disso, o problema atual é muito mais simples de se resolver. O pensamento para a tradução foi baseado em: 90 1. Para cada linha de Funcionário a. Percorrer a lista de campos, retornando seu valor; b. Adicionar código para adequar o valor ao seu tamanho correto. 2. Para cada linha de Detalhe a. Fazer o mesmo que na linha do tipo Funcionário. Ao esbarrar na geração de código para o campo Custom, criamos mais um arquivo chamado CustomValues.ttcustom, o qual é referenciado pelo Payxport.ttimport (análogo ao Payrule.ttimport da linguagem anterior). E nesse arquivo deixamos um método parcialmente implementado, para que o usuário pudesse preencher com o código de cada Campo Custom adicionado ao diagrama. <#+ public String GetCustomValue(Campo campo, ref bool isCampoFloat) { isCampoFloat = false; return ""; }#> Também deve ser retornado se o valor especificado é ou não de ponto flutuante, para que possamos tomar as atitudes necessárias ao tamanho do campo na geração do arquivo. Mantemos a mesma estrutura de pastas da linguagem anterior, com a diferença da pasta CustomCode, que irá conter apenas o arquivo CustomValues.ttcustom. Finalmente, chegamos ao fim da construção da segunda linguagem. Iniciemos a utilização da mesma para a construção dos Exportadores. 4.3.8 CRIAÇÃO DOS EXPORTADORES UTILIZANDO A LINGUAGEM PAYXPORT Muito mais rápido do que na linguagem anterior, em aproximadamente 7 minutos estávamos com os três exportadores prontos e funcionando. Apenas o valor do FGTS não havia sido incluído nesse tempo. 91 Para tal valor, tivemos de adicionar código extra também ao Assembly PayrollCalc.Regras, gerado com a linguagem Payrule. Construímos uma Partial Class para a RegraFGTS, inserindo na mesma um método chamado RetornaValorFGTS, o qual aceita um parâmetro referente ao Salário do Funcionário. Sua implementação foi bem simples, apenas multiplicar o valor por 0,08 (8%). Além disso, modificamos o método GetCustomValue para criar uma nova RegraFGTS e calcular este valor. Se pararmos para pensar, podemos ver a flexibilidade que ganhamos nessa segunda linguagem, primeiro com a utilização de propriedades calculadas e, segundo, com a adição de código personalizável. Certamente, essas duas práticas serão um padrão em próximas linguagens que eventualmente construiremos. Finalmente e, não surpreendentemente, ao submetermos nosso Assembly gerado (PayrollCalc.Exportacao) ao projeto de testes, todos eles rodaram e passaram na primeira tentativa de execução. Isso talvez se deva ao fato de, agora, termos um domínio bastante menor e de termos ganho alguma experiência com a primeira linguagem. 4.3.9 INTEGRANDO OS PROJETOS A integração dos projetos envolveu, basicamente, mudar a referência do projeto PayrollInterface para os novos Assemblies PayrollCalc.Regras e PayrollCalc.Exportacao, gerados através das linguagens Payrule e Payxport, respectivamente. Como tínhamos muitos testes unitários (84 testes, ao todo) nos passando a garantia de que nossas regras e exportadores haviam funcionado conforme esperado, não ficamos surpresos ao ver toda essa integração funcionando na primeira tentativa também. A interface ficou a mesma e retornou os mesmos valores. 92 Figura 15 - Programa rodando com base nos resultados das DSLs 4.4 ANÁLISE COMPARATIVA Ao utilizarmos o paradigma proposto para desenvolver o mesmo sistema, anteriormente construído segundo a orientação a objetos, pudemos entender muitos aspectos que só podem ser observados na prática. Logicamente, quando lemos artigos, assistimos a alguns vídeos, etc., temos uma percepção um pouco diferente da qual temos quando construímos nosso primeiro projeto. Um dos pontos interessantes que podemos destacar está relacionado à geração de código final do projeto. Antes de iniciarmos a implementação, não sabíamos exatamente o que resultaria como produto final de nosso desenvolvimento. Tal questão somente nos foi respondida quando começamos a olhar exemplos de Domain-Specific Languages prontas. Como explicado anteriormente, DSLs gerarão código para um ambiente, plataforma, programa ou framework específico. Ou seja, não fizemos um programa inteiro utilizando nossa DSL conforme imaginamos, mas sim, criamos duas linguagens que compilavam para um framework construído por nós mesmos. Poderíamos, sim, elaborar outra linguagem para a criação de 93 aplicações que contém funcionários e pontos, além de uma outra para a criação de interfaces, mas, ainda assim, precisaríamos escolher para qual contexto iríamos gerar código. Ao final, teremos algumas linguagens específicas de domínio e seus resultados iterarão uns com os outros. Felizmente, esta conclusão bate exatamente com o que já havíamos lido sobre o assunto. Para enfatizar, peguemos como exemplo a linguagem SQL (Structured Query Language), uma linguagem para a manipulação de dados em bancos de dados relacionais. O código resultante da mesma nem pode ser visto por nós, pois é interpretado por sistemas gerenciadores de bancos de dados. Ou seja, aí está o seu fim, da mesma maneira que em nosso projeto temos nosso pequeno framework. Muitas vezes já precisamos automatizar algo em nosso processo de desenvolvimento. Em nossas empresas, certamente já foi necessária a criação de uma ferramenta para a qual passávamos algumas entradas e ela gerava algum tipo de código fonte. Isso seria uma Software Factory bastante simples e o conceito está intimamente ligado ao paradigma em questão. Criamos basicamente um gerador de código para regras de cálculo de folhas de pagamento e exportadores de arquivo, compiláveis para nosso framework. Neste ponto da geração de código, pudemos ver observar conceitos fortemente relacionados: Generative Programming e Software Factories. Talvez, um bom estudo em ambas as áreas nos tragam diversos benefícios para a construção de DSLs. Quanto às ferramentas que utilizamos, provavelmente tenham sido as mais adequadas, pois todas elas possuem fácil integração entre si e, às quais, já possuímos grande afinidade. No entanto, talvez para a segunda linguagem (Payxport) seria mais fácil a utilização de um ambiente que não fosse tão “gráfico”. Em outras palavras, utilizar a notação textual em vez de diagramas poderia ser mais simples. Seres humanos assimilam melhor formas geométricas do que textos. Isso é fato. No entanto, o tempo gasto arrastando-se e desenhando-se em 94 diagramas é um pouco maior do que escrevendo em poucas linhas o necessário. O ideal seria podermos ter um ambiente para a construção de DSLs de forma gráfica (como o Microsoft DSL Tools), mas que também suportasse a forma textual, à qual estamos mais acostumados (como o Meta-Programming System, da JetBrains). Finalmente, a grande questão da comparação entre os dois paradigmas se encontra na produtividade do segundo em relação ao primeiro. Conforme já comentado, de acordo com a evolução das linguagens e paradigmas de programação, pode-se observar um constante crescimento de pessoas capazes de construir seu próprio software. Quanto a este ponto, não temos dúvida de que qualquer pessoa possuidora de conhecimento sobre o domínio poderia editar ou criar elementos de software em Domain-Specific Languages. A curva de aprendizado seria incomparavelmente menor. Para fazermos um teste, convidamos 3 pessoas com alguma experiência em folhas de pagamento e pedimos para que elas fizessem determinadas alterações nas regras e nos exportadores sem nenhuma documentação. Depois de pouco tempo, todas conseguiram fazer as modificações solicitadas corretamente. No entanto, essa facilidade não se estende à criação das linguagens propriamente dita, pois temos nela uma fase que ainda é muito complicada: a parte semântica e geração de código. Para criar validações e gerar código corretamente, ainda necessitaremos de programadores com muita experiência. Tanto no domínio, quanto no ambiente de destino. Quanto ao tempo gasto para se construir os dois projetos, nossas expectativas não foram alcançadas. A utilização do paradigma proposto foi consideravelmente mais demorada. Logicamente, isso se deve a diversos fatores: 1. Pouca experiência com o paradigma em questão – Foram nossas primeiras Domain-Specific Languages. Por isso, tivemos de aprender tanto o conceito como a utilização da ferramenta. 95 2. Pouca documentação disponível – Hoje, ainda não existem muitas fontes para quem quer se inteirar sobre o assunto, o que tornou mais difícil nosso aprendizado. 3. Construção de todas as DSLs – Segundo o paradigma, quando formos desenvolver um sistema, pesquisaremos por uma DSL que resolva nosso problema e, caso não encontremos, criaremos uma. Se já tivéssemos experiência com o paradigma, ou se houvesse mais documentação, levaríamos muito menos tempo para a implementação de nosso segundo projeto. Já no caso de existirem previamente as DSLs as quais precisávamos, não teríamos levado mais de poucas horas para finalizar o desenvolvimento. Isso seria indubitavelmente mais rápido. No que se refere à documentação do código fonte, o ganho é incomparável. Para um sistema Orientado a Objetos, necessitamos de algum tipo de notação específica, com a finalidade de melhorarmos o entendimento do software por ambas as partes (analista e desenvolvedor). Infelizmente, isso também acontece para o código fonte das DSLs, mesmo que ferramentas gráficas, como Microsoft DSL Tools, já nos forneçam uma documentação praticamente pronta das mesmas. Já para um sistema Orientado a Linguagens, provavelmente não precisaremos de nenhum tipo de documentação, levando em conta que os diagramas (ou outros elementos) já fazem parte do código fonte do software. Em outras palavras, estamos lidando com um tipo de código “autodocumentável”. Podemos analisar o objetivo da documentação como sendo: fornecer informações relevantes, facilitando assim a compreensão sobre certo domínio. E como a principal função da Language Oriented Programming é a de tentar fazer o computador entender o que queremos através da nossa própria linguagem, estamos escrevendo apenas a documentação e não precisamos de algo para esclarecê-la. Talvez seja necessário somente melhorar a compreensão sobre eventuais códigos customizados. No entanto, todo o resto já está pronto. Isso nos remete a um conceito bastante atual, a Model-Driven 96 Architecture, na qual modelamos nosso projeto Orientado a Objetos, ou seja, confeccionamos sua documentação, e o código fonte é gerado automaticamente. Contudo, talvez Symonyi (1995) não estivesse tão errado em suas idéias com o projeto IP. A Intentional Programming é algo formidável, mas nada acontece de uma hora pra outra. Porém, notamos que a Language Oriented Programming nos permite esclarecer muita coisa em nosso código fonte sem precisar escrever linhas de código, uma das idéias básicas do projeto IP. Em suma, o paradigma Language Oriented Programming necessita de bastante amadurecimento e levará algum tempo para que empresas passem a utilizá-lo realmente em seus projetos. Serão necessárias ferramentas que agilizem a confecção de Domain-Specific Languages, além de bastante documentação sobre o assunto. Quando tais quesitos forem atendidos e tenhamos uma boa gama de DSLs disponíveis na internet, não vemos o porquê de tal paradigma não se tornar um padrão utilizado por todos. Pensamos só ter a ganhar utilizando algo que irá possibilitar a mais pessoas construir e alterar seu próprio software além de automatizar processos muitas vezes mecânicos. Para simplificar, podemos resumir nossa análise na seguinte tabela comparativa: Tabela 1 - Comparação entre os paradigmas Quesito Programação Language Oriented Orientada a Objetos Programming (DSLs/Sistema) Médio Alto/Baixo Compreensão Difícil Muito Difícil/Muito Fácil Curva de Aprendizado Grande Grande/Muito Pequena Quantidade de Baixa Muito Baixa/Alta Tempo de Desenvolvimento Profissionais Capazes 97 de Programar com Proficiência Manutenção Posterior Média Difícil/Muito Fácil Documentação Necessita de Notação Necessita de Notação Específica Específica/AutoDocumentável 98 4.5 FERRAMENTAS E MÉTODOS UTILIZADOS NOS PROJETOS 4.5.1 TEST-DRIVEN DEVELOPMENT Certamente, na vida profissional de praticamente todos os desenvolvedores de software mais experientes, já aconteceu a seguinte situação: foi lhe passada a tarefa de realizar uma modificação em um módulo do sistema, o qual é utilizado por vários outros. Para piorar, o programador sabe que, caso faça algo errado, estará “quebrando” outras classes do projeto inteiro. Em tal momento, avalia-se a real necessidade de tal alteração. Se for apenas para facilitar a compreensão do código, ou para outro fim de sentido semelhante, muitas vezes, nada é realizado. No entanto, sabemos que o bom entendimento do código é de fundamental importância para futuras manutenções, pela mesma ou por outra equipe. Baseado em outras metodologias ágeis presentes no mercado (como o eXtreme Programming, o Scrum, o dX, o Cristal Clear, etc.), o TestDriven Development foi elaborado para não precisarmos passar pelo tipo de situação citado. Através de testes unitários, os quais são do tipo caixa-branca (“enxergam” elementos como classes e métodos) e escritos geralmente na mesma linguagem do programa, teremos uma cobertura de testes em 100% do código fonte. Ou seja, na situação acima, não teríamos nenhum receio em melhorar a estrutura de nosso código, pois, se alguma modificação danificasse outro módulo, teríamos o aviso no mesmo instante e poderíamos consertar rapidamente. Para que isso aconteça, por mais que não pareça natural, escreveremos um teste unitário antes de iniciar a implementação. Seguiremos os passos do que é chamado de “mantra do TDD” (BECK, 2002): 1. Escrever um teste mesmo que não haja código correspondente; 2. Tentar compilar o código e ver o processo falhar; 3. Consertar o código com o mínimo necessário para a compilação; 4. Rodar os testes e vê-los falhar (mostrando que a funcionalidade não fora ainda implementada); 99 5. Consertar o código fazendo o mínimo possível para que os testes passem; 6. Rodar os testes e vê-los passar; 7. Eliminar duplicações e melhorar estrutura; 8. Rodar os testes e vê-los passar; 9. Escrever o próximo teste. Quando todos os testes estiverem passando, é sinal de que terminamos. Caso ainda falte alguma coisa, escreveremos um teste e implementaremos em seguida. Em resumo, nenhum código é escrito, a menos que haja um teste falhando para nos indicar o que deve ser feito (BECK, 2002). Desta maneira, traduzimos o que queremos para testes e os mesmos vão guiando nossa codificação. Por isso, a atividade de programação torna-se muito menos estressante e cansativa (BECK, 2002). No entanto, com a finalidade de aumentar a agilidade na hora de desenvolver, utilizamos algumas ferramentas (frameworks) para automatizar a execução dos testes unitários. Uma das ferramentas mais famosas para a construção desse tipo de testes é o JUnit, no qual é possível elaborarmos testes unitários na linguagem Java. Através de uma interface amigável, rodamos os testes e recebemos uma cor: verde (se os mesmos passaram) ou vermelho (no caso de erros terem acontecido). Outra ferramenta bastante utilizada e mais relacionada ao contexto de nosso projeto é o NUnit. Nesta, também temos a interface amigável com as mesmas cores, no entanto, podemos escrever testes em qualquer uma das linguagens da plataforma .NET para rodar no ambiente. Para exemplificar, vamos construir uma classe de cálculos muito simples, de apenas dois métodos: Soma e Subtrai. Utilizaremos código escrito em C# e a ferramenta NUnit para rodar nossos testes unitários automatizados. Seguindo o roteiro, nosso primeiro passo é escrever um teste: [Test] public void TesteSoma() 100 { Calc calc = new Calc(); Assert.AreEqual(3, calc.Soma(1, 2)); } Estamos criando uma instância da classe Calc e verificando se o resultado do método Soma, para os argumentos 1 e 2, é igual a 3. Ao tentarmos compilar nosso código, logicamente, receberemos uma mensagem de erro dizendo que a classe Calc ainda não existe, bem como seu método Soma. Por isso, nosso próximo passo é fazer o programa compilar. Escrevemos então: public class Calc { public double Soma(double a, double b) { return 0; } } Lembrando que nossa intenção é apenas compilar o programa. Quando rodamos os testes, chegamos ao nosso próximo passo, também conhecido como Red Flag: ver os testes falhar. Figura 16 - Bandeira vermelha representando que o teste não passou Precisamos agora fazer o mínimo para o teste passar. Se quisermos algo diferente, vamos ter de fazer os testes nos requisitarem tal modificação. Portanto, alteramos nosso método Soma simplesmente para: public double Soma(double a, double b) 101 { return 3; } Desta maneira, ao rodarmos nossos testes, chegamos à nossa primeira “bandeira verde”. Figura 17 - Bandeira verde indicando que o teste passou Chega a fase de eliminarmos as duplicações. No entanto, este é nosso primeiro método e ainda não conseguimos enxergar nenhuma duplicação. Portanto, vamos voltar para o início, mas, em vez de adicionarmos outro teste, vamos melhorar o primeiro: [Test] public void TesteSoma() { Calc calc = new Calc(); Assert.AreEqual(3, calc.Soma(1, 2)); Assert.AreEqual(5, calc.Soma(2, 3)); } Desta maneira, precisamos encontrar uma maneira simples de fazermos nosso teste passar. Após visualizá-lo falhando, alteramos o método soma para: public double Soma(double a, double b) { return a + b; 102 } Esta é a maneira mais simples de ver o teste passar e estamos com o método implementado. Após a visualização da barra verde no NUnit, vamos fazer o mesmo para a operação de subtração. E seguindo os mesmos passos da operação anterior, chegamos ao teste e ao método Subtração a seguir: [Test] public void TesteSubtracao() { Calc calc = new Calc(); Assert.AreEqual(5, calc.Soma(10, 5)); Assert.AreEqual(3, calc.Soma(12, 9)); } public double Subtrai(double a, double b) { return a - b; } E para finalizar, eliminamos uma duplicação na nossa classe de testes, deixando-a no seguinte estado: [TestFixture] public class CalcTests { Calc calc; [SetUp] public void Inicio() { calc = new Calc(); } [Test] public void TesteSoma() { Assert.AreEqual(3, calc.Soma(1, 2)); Assert.AreEqual(5, calc.Soma(2, 3)); 103 } [Test] public void TesteSubtracao() { Assert.AreEqual(5, calc.Soma(10, 5)); Assert.AreEqual(3, calc.Soma(12, 9)); } } O método com o atributo SetUp é chamado antes da execução de cada teste. Por isso, instanciamos o objeto calc no mesmo. Resumindo, estamos agora com 100% de nossa classe testada e, caso desejemos fazer alguma modificação, estaremos certos de que nada será quebrado. E isso funcionaria tanto para pequenas classes quanto para consideráveis módulos de grandes sistemas. Pudemos observar que a construção de testes unitários é bastante simples. No entanto, alguns contextos exigem um maior esforço para serem testados. Por exemplo, bancos de dados, acessos ao disco, conexões socket, etc. Nestes casos, o importante para nós é o teste da funcionalidade em si, ou seja, de nossas classes e objetos. Não queremos depender de fatores externos, mesmo que tenhamos alguma forma de interação com esses elementos. Com a finalidade de facilitar a elaboração de testes para os mais difíceis cenários, nos quais lidamos com fatores externos, surge um padrão de testes conhecido como Mock Objects. Para exemplificar, imaginemos uma classe responsável pelo Log de informações em arquivos no disco rígido. Neste caso, não gostaríamos de depender de problemas referentes ao sistema de arquivos (como falta de espaço ou permissões negadas). Portanto, criaremos um Mock Object que irá simular um disco rígido e rodaremos nossos testes sobre ele, fazendo as devidas verificações. Para tornar mais fácil a construção de Mock Objects, foram criadas diversas ferramentas que, hoje estão disponíveis na internet para download. 104 Estas, automatizam desde a criação do objeto até suas verificações. E para o ambiente .NET Framework, uma das mais utilizadas possui o nome de NMock. Nesta, indicamos uma interface, é gerado um Mock Object, chamamos métodos do objeto a ser testado e podemos finalmente fazer nossas asserções. A ferramenta toma conta de toda a comunicação com a classe simulada. 4.5.2 MODEL VIEW CONTROLLER – MVC Com o crescimento dos projetos de software que elaboramos, cresce também a complexidade dos seus códigos fonte. Deste modo, nasce a necessidade de organizarmos a estrutura interna de nossos sistema com a finalidade de facilitar sua compreensão, expansão e eventual manutenção. Observamos que a grande maioria dos modelos dos mais variados projetos de software seguem uma seguinte separação: interface, dados e regras. Desta maneira, podemos trabalhar em apenas um desses elementos sem influenciar nos outros dois. E isso nos garante bastante flexibilidade e segurança. Sabemos que alterações em códigos ditos “bagunçados” ou “misturados” são muito mais difíceis de serem executadas. O Model View Controller nasceu como um padrão de projeto focado em ajudar na organização e distinção das classes e objetos de nosso sistema. Neste modelo temos a distinção bem definida de três segmentos (SUN MICROSISTEMS, 2002): • Model – Deve englobar tudo o que se refere aos dados e às regras de negócio que os envolvem. Possui a característica de ser independente dos outros dois segmentos, os quais podem ser facilmente substituídos e as regras para o domínio se mantêm. • View – Tudo o que é relativo à interface e a como os valores devem ser mostrados aos usuários. • Controller – Responsável pelas traduções e requisições entre o segmento Model e o segmento View. Além disso, uma de suas principais funções está em atualizar o View quando necessário. 105 Caso consigamos portar todas as nossas classes para o padrão apresentado, certamente, teremos uma facilidade muito grande em alterá-las e compreendê-las posteriormente, pois estaremos lidando com segmentos bem definidos. Reconheceremos de maneira mais simples o local onde cada elemento deverá estar, bem como a função que deverá exercer. 4.5.3 .NET FRAMEWORK Semelhante à plataforma Java, o .NET Framework é a solução apresentada pela empresa Microsoft Corporation para o desenvolvimento e execução de sistemas orientados a objetos com suporte a diversas funcionalidades comentadas a seguir. Uma de suas principais vantagens está na sua estrutura. Pegando, por exemplo, uma linguagem como o C#, seu código fonte compilado irá gerar um módulo binário. Este módulo recebe o nome de Assembly e pode estar no formato executável (extensão .EXE) ou de biblioteca (extensão .DLL). Seu código interno estará compilado em uma linguagem conhecida como MSIL (Microsoft Intermediate Language), o qual é análogo ao Bytecode da plataforma Java (MICROSOFT CORPORATION, 2007). A grande vantagem em existir uma linguagem intermediária para os Assemblies se encontra na possibilidade de várias linguagens de alto nível gerarem o mesmo código. Exemplos de linguagens de alto nível que são compiladas para MSIL seriam: • Visual C# • VisualBasic.NET • C++ for .NET • J# • Delphi for .NET • Cobol.NET Estes seriam apenas alguns exemplos. Porém, existem muitas outras disponíveis no mercado. Isso se deve à especificação do Framework ser 106 aberta para qualquer um que queira estudá-la e criar um compilador compatível. Como todas estas linguagens compilam para um mesmo código e geram um elemento conhecido como Assembly, existem módulos responsáveis por executá-los nas mais diversas plataformas. Estes são conhecidos como CLRs (Common Language Runtime) e seriam análogos às máquinas virtuais da plataforma Java. Em outras palavras, podemos ter CLRs para qualquer tipo de sistema operacional. Estes compilariam e executariam os Assemblies (MICROSOFT CORPORATION, 2007). A Microsoft construiu CLRs para praticamente todas as versões do seu sistema operacional, o Windows. Além disso, existem outros projetos, como o Mono, responsáveis pela criação de CLRs para outros sistemas operacionais. Em suma, podemos desenvolver software em qualquer uma das linguagens existentes compatíveis com o .NET Framework, integrando umas com as outras e fazendo uso de todas as bibliotecas e funcionalidades existentes. Isso quer dizer que, em termos de orientação a objetos, é possível criarmos uma classe em C# e herdarmo-la em Delphi for .NET, por exemplo. Além disso, podemos fazer uso de componentes e módulos construídos nas mais diversas linguagens em nossos próprios sistemas. O .NET Framework, além de muitas outras funcionalidades, nos oferece suporte fácil a (MICROSOFT CORPORATION, 2007): • Acesso a bancos de dados – Através de uma arquitetura simples e robusta, baseada na notação XML, conhecida como ADO.NET, temos acesso simples e flexível aos mais diversos bancos de dados disponíveis no mercado. • Desenvolvimento de Aplicações Web – Com o módulo chamado ASP.NET, temos suporte ao desenvolvimento rápido à formulários na Web. Além disso, podemos facilmente utilizar outros Assemblies nestas mesmas aplicações. 107 • Desenvolvimento de WebServices – Ainda na parte de ASP.NET, a construção de serviços na web torna-se muito simples com os recursos da plataforma. • Desenvolvimento de Aplicações Desktop – Para tanto, utilizaremos a seção de Windows Forms, a qual possui diversos componentes prontos para serem adicionados a formulários de programas do tipo desktop. • Desenvolvimento de Aplicações para PDAs – Uma versão específica do framework, o .NET Compact Framework, pode ser utilizada para a construção de programas para celulares e computadores de bolso. • Criptografia, Segurança, Conectividade, etc. 108 5 CONCLUSÕES A evolução do desenvolvimento de software no mundo segue para um mesmo ponto: tornar-se cada vez mais fácil, a ponto de permitir que mais pessoas possam criar seus próprios programas de maneira bastante simples. A Language Oriented Programming está fortemente ligada ao ato da geração automática de código. No entanto, esta não é uma tarefa fácil de se resolver. Para a escrita de Domain-Specific Languages ainda precisaremos de programadores com muito conhecimento na plataforma de destino do código gerado. Por outro lado, pessoas conhecedoras apenas do domínio em questão são capazes de utilizar uma DSL para construir/alterar um sistema inteiro. Através de uma análise comparativa, esperávamos traçar pontos positivos e negativos entre os dois paradigmas em questão (a atual Orientação a Objetos e a nova Orientação a Linguagens), estabelecendo assim uma espécie de validação da proposta de Dmitriev (2005). E através da construção de um projeto para o cálculo de folhas de pagamentos em ambos os paradigmas, pudemos avaliar positivamente a Language Oriented Programming, chegando à conclusão de só termos a ganhar fazendo uso da mesma. No entanto, sabemos também que ainda deverá haver um período de maturação para que esse se torne um padrão para o desenvolvimento de software. A tabela comparativa mostrada anteriormente (Tabela 1) relata com mais exatidão a análise realizada. Com essa contribuição, alguns trabalhos futuros poderão surgir, como por exemplo: • Construção de ferramenta para a elaboração de Domain-Specific Languages – Criação de uma ferramenta com a finalidade de possibilitar a especificação tanto de DSLs baseadas em modelos gráficos (como o Microsoft DSL Tools), como de DSLs em modo texto (como o MPS, da JetBrains). • Pesquisa e avaliação da eficácia da utilização de Domain-Specific Languages por não-programadores – Construção de um programa com o paradigma Language Oriented Programming e 109 pesquisa para avaliar a adaptação de não-programadores à sua modificação. • Desenvolvimento de padrões para a construção de DSLs – Criação de padrões para a especificação de DSLs, seguindo o modelo de design patterns (os quais são para a Orientação a Objetos). Fazendo algumas previsões para um futuro não distante, podemos imaginar empresas especializadas somente na elaboração de DSLs, o que seria muito bom para o mercado do desenvolvimento de software. E, certamente, haverá uma distinção bastante grande entre os papéis de Desenvolvedor de Linguagens e Desenvolvedor de Sistemas (que utilizam as linguagens criadas pelo primeiro). Sabemos que o mundo hoje caminha para a automação. E isso também serve para o desenvolvimento de software. O próximo paradigma certamente possibilitará a inclusão de muitas pessoas nesse contexto. Mesmo não aparecendo com o nome Programação Orientada a Linguagens, certamente, vai conter muitos dos conceitos aqui estudados. 110 6 REFERÊNCIAS APPLEBY, D. (1991). PROGRAMMING LANGUAGES: Paradigm and Practice. Singapore: McGraw-Hill, Inc. BECK, K. (2002). Test-Driven Development: by Example. Addison-Wesley Professional. BROWN, A. (2004, Fevereiro 17). An introduction to Model Driven Architecture. Retrieved Julho 18, 2007, from IBM: http://www.ibm.com/developerworks/rational/library/3100.html CZARNECKI, K. (2002, Novembro 28). Generative Core Concepts. Retrieved Julho 18, 2007, from Generative Core Concepts: http://www.programtransformation.org/Transform/GenerativeCoreConcepts CZARNECKI, K., EISENECKER, U. W., & STEYAERT, P. (1997). Beyond Objects: Generative Programming. Acesso em 9 de Março de 2007, disponível em http://www-ia.tu-ilmenau.de/~czarn/aop97.html DMITRIEV, S. (2005). Language Oriented Programming. The Next Programming Paradigm. Acesso em 10 de Março de 2007, disponível em http://www.onboard.jetbrains.com/is1/articles/04/10/lop/ FOWLER, M. (2006). Introduction to Domain Specific Languages. Acesso em 13 de Março de 2007, disponível em http://www.infoq.com/presentations/domain-specific-languages FOWLER, M. (2005). Language Workbenches: The Killer-App for Domain Specific Languages? Acesso em 11 de Março de 2007, disponível em http://martinfowler.com/articles/languageWorkbench.html GREENFIELD, J. (2004, Novembro). Software Factories: Assembling Applications with Patterns, Models, Frameworks, and Tools. Retrieved Julho 19, 2007, from Microsoft: http://msdn2.microsoft.com/en-us/library/ms954811.aspx MICROSOFT CORPORATION. (2007). .NET Framework Developer Center. Retrieved Julho 18, 2007, from Microsoft: http://msdn2.microsoft.com/ptbr/netframework/default.aspx 111 OMG. (n.d.). Model Driven Architecture. Retrieved Julho 18, 2007, from OMG: http://www.omg.org/mda/ PROLOG. (2001, Agosto). Retrieved Julho 19, 2007, from Wikipedia: http://en.wikipedia.org/wiki/Prolog SELLS, C. (2001, Dezembro). Generative Programming: Modern Techniques to Automate Repetitive Programming Tasks. Retrieved Julho 18, 2007, from Generative Programming: Modern Techniques to Automate Programming Repetitive Tasks: http://msdn.microsoft.com/msdnmag/issues/01/12/GenProg/ SIMONYI, C. (1995). The Death Of Computer Languages, the Birth of Intentional Programming. Acesso em 13 de Março de 2007, disponível em http://research.microsoft.com/research/pubs/view.aspx?msr_tr_id=MSR-TR-9552 SUN MICROSISTEMS. (2002). Model-View-Controller. Retrieved Julho 18, 2007, from detailed.html Java BluePrints: http://java.sun.com/blueprints/patterns/MVC- 112 7 ANEXOS 7.1 PROJETO PAYROLLCALC DetalhamentoFolha.cs using System; using System.Collections; namespace PayrollCalc { public class DetalhamentoFolha { private ArrayList itens = new ArrayList(); public void AddItem(string item) { itens.Add(item); } public void RemItemAt(int pos) { itens.RemoveAt(pos); } public string GetItem(int pos) { return (string) itens[pos]; } public int Count { get { return itens.Count; } } public string this[int pos] { get { return (string) itens[pos]; } set { itens[pos] = value; } } } } 113 Exportador.cs using System; namespace PayrollCalc.Exportacao { public interface Exportador { string[] Exporta(Folha folha); } } FichaPonto.cs using System; using System.Collections; namespace PayrollCalc { public class FichaPonto { private ArrayList pontos = new ArrayList(); public void AddPonto(Ponto ponto) { pontos.Add(ponto); } public void RemPontoAt(int pos) { pontos.RemoveAt(pos); } public Ponto GetPonto(int pos) { return (Ponto) pontos[pos]; } public int Count { get { return pontos.Count; } } public Ponto this[int pos] { get { return (Ponto) pontos[pos]; } set { pontos[pos] = value; } 114 } public int ContaDiasEmEstado(Ponto.EstadoPonto estado) { int result = 0; for (int i = 0; i < pontos.Count; i++) if (((Ponto) pontos[i]).Estado == estado) result++; return result; } public double GetQtdHENoPeriodo(DateTime inicio, DateTime fim) { double result = 0; for (int i = 0; i < pontos.Count; i++) { Ponto ponto = (Ponto) pontos[i]; TimeSpan entrada; TimeSpan saida; if (fim < inicio) { if (ponto.EntradaHoraExtra.TimeOfDay > fim.TimeOfDay) entrada = ponto.EntradaHoraExtra.TimeOfDay; else entrada = fim.TimeOfDay; if (ponto.SaidaHoraExtra.TimeOfDay < inicio.TimeOfDay) saida = ponto.SaidaHoraExtra.TimeOfDay; else saida = inicio.TimeOfDay; if (saida > entrada) result = saida.Subtract(entrada).TotalHours; result = ponto.SaidaHoraExtra.TimeOfDay.Subtract(ponto.EntradaHoraExtra.TimeOfD ay).TotalHours - result; } else { if (ponto.EntradaHoraExtra.TimeOfDay > inicio.TimeOfDay) entrada = ponto.EntradaHoraExtra.TimeOfDay; else entrada = inicio.TimeOfDay; if (ponto.SaidaHoraExtra.TimeOfDay < fim.TimeOfDay) saida = ponto.SaidaHoraExtra.TimeOfDay; else saida = fim.TimeOfDay; if (saida > entrada) result = saida.Subtract(entrada).TotalHours; } } 115 return result; } } } Folha.cs using System; using System.Collections; namespace PayrollCalc { public class Folha { private ArrayList itens = new ArrayList(); public void AddItem(FolhaItem item) { itens.Add(item); } public void RemItemAt(int pos) { itens.RemoveAt(pos); } public FolhaItem GetItem(int pos) { return (FolhaItem) itens[pos]; } public int Count { get { return itens.Count; } } public FolhaItem this[int pos] { get { return (FolhaItem) itens[pos]; } set { itens[pos] = value; } } } } 116 FolhaItem.cs using System; namespace PayrollCalc { public class FolhaItem { Funcionario funcionario; double salarioLiquido; DetalhamentoFolha detalhamento; public FolhaItem(Funcionario funcionario, double salarioLiquido, DetalhamentoFolha detalhamento) { if ((funcionario == null) || (detalhamento == null)) throw new Exception("Não é possível criar um ítem de folha sem especificar um funcionário e um detalhamento."); this.funcionario = funcionario; this.salarioLiquido = salarioLiquido; this.detalhamento = detalhamento; } public Funcionario Funcionario { get { return funcionario; } } public double SalarioLiquido { get { return salarioLiquido; } } public DetalhamentoFolha Detalhamento { get { return detalhamento; } } } } Funcionario.cs using System; namespace PayrollCalc { 117 public class Funcionario { private int matricula; private double salario; private string nome; private string cpf; private int nroDependentes; private int qtdFaltas; private int inicioFeriasNoMesCorrente; private int fimFeriasNoMesCorrente; private DateTime dataAdmissao; private FichaPonto fichaPonto; public Funcionario(int matricula, double salario, string nome, string cpf, int nroDependentes, FichaPonto fichaPonto, int qtdFaltas, DateTime dataAdmissao, int inicioFeriasNoMesCorrente, int fimFeriasNoMesCorrente) { if (fichaPonto == null) throw new Exception("É necessário configurar uma FichaPonto"); this.matricula = matricula; this.salario = salario; this.nome = nome; this.cpf = cpf; this.nroDependentes = nroDependentes; this.fichaPonto = fichaPonto; this.qtdFaltas = qtdFaltas; this.dataAdmissao = dataAdmissao; this.inicioFeriasNoMesCorrente = inicioFeriasNoMesCorrente; this.fimFeriasNoMesCorrente = fimFeriasNoMesCorrente; } public int Matricula { get { return this.matricula; } } public double Salario { get { return this.salario; } } public string Nome { get { return this.nome; } } public string Cpf { get { 118 return this.cpf; } } public int NroDependentes { get { return this.nroDependentes; } } public FichaPonto FichaPonto { get { return this.fichaPonto; } } public double SalarioDia { get { int diasUteis = this.fichaPonto.Count; diasUteis -= this.fichaPonto.ContaDiasEmEstado(Ponto.EstadoPonto.DiaNaoUtil); return this.Salario/diasUteis; } } public int QtdFaltas { get { return this.qtdFaltas; } } public DateTime DataAdmissao { get { return this.dataAdmissao; } } public int InicioFeriasNoMesCorrente { get { return this.inicioFeriasNoMesCorrente; } } public int FimFeriasNoMesCorrente { get { 119 return this.fimFeriasNoMesCorrente; } } public int DiasDeFerias { get { return this.FimFeriasNoMesCorrente this.InicioFeriasNoMesCorrente; } } public int GetQtdMesesTrabalhadosSemAno(int Ate) { if (this.DataAdmissao.Year == this.FichaPonto[0].EntradaTurnoRealizado.Year) { return (Ate + 1) - this.DataAdmissao.Month; } else { return Ate; } } public double SalarioHora { get { return this.SalarioDia / (((this.fichaPonto[0].SaidaTurnoEsperado.TimeOfDay.TotalMinutes this.fichaPonto[0].EntradaTurnoEsperado.TimeOfDay.TotalMinutes) (this.fichaPonto[0].SaidaIntervaloEsperado.TimeOfDay.TotalMinutes this.fichaPonto[0].EntradaIntervaloEsperado.TimeOfDay.TotalMinutes)) / 60); } } } } Ponto.cs using System; namespace PayrollCalc { public class Ponto { public enum EstadoPonto {Presenca, Falta, Afastamento, DiaNaoUtil}; private private private private private EstadoPonto estado; DateTime entradaTurnoEsperado; DateTime saidaTurnoEsperado; DateTime entradaTurnoRealizado; DateTime saidaTurnoRealizado; 120 private private private private private private DateTime DateTime DateTime DateTime DateTime DateTime entradaIntervaloEsperado; saidaIntervaloEsperado; entradaIntervaloRealizado; saidaIntervaloRealizado; entradaHoraExtra; saidaHoraExtra; public Ponto(EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, DateTime saidaHoraExtra) { this.estado = estado; this.entradaTurnoEsperado = entradaTurnoEsperado; this.saidaTurnoEsperado = saidaTurnoEsperado; this.entradaTurnoRealizado = entradaTurnoRealizado; this.saidaTurnoRealizado = saidaTurnoRealizado; this.entradaIntervaloEsperado = entradaIntervaloEsperado; this.saidaIntervaloEsperado = saidaIntervaloEsperado; this.entradaIntervaloRealizado = entradaIntervaloRealizado; this.saidaIntervaloRealizado = saidaIntervaloRealizado; this.entradaHoraExtra = entradaHoraExtra; this.saidaHoraExtra = saidaHoraExtra; } public EstadoPonto Estado { get { return this.estado; } } public DateTime EntradaTurnoEsperado { get { return this.entradaTurnoEsperado; } } public DateTime SaidaTurnoEsperado { get { return this.saidaTurnoEsperado; } } public DateTime EntradaTurnoRealizado { get { return this.entradaTurnoRealizado; } } public DateTime SaidaTurnoRealizado 121 { get { return this.saidaTurnoRealizado; } } public DateTime EntradaIntervaloEsperado { get { return this.entradaIntervaloEsperado; } } public DateTime SaidaIntervaloEsperado { get { return this.saidaIntervaloEsperado; } } public DateTime EntradaIntervaloRealizado { get { return this.entradaIntervaloRealizado; } } public DateTime SaidaIntervaloRealizado { get { return this.saidaIntervaloRealizado; } } public DateTime EntradaHoraExtra { get { return this.entradaHoraExtra; } } public DateTime SaidaHoraExtra { get { return this.saidaHoraExtra; } } } } 122 ProcessadorDeFolha.cs using System; using PayrollCalc.Exportacao; namespace PayrollCalc { public class ProcessadorDeFolha { public Folha Roda(Funcionario[] funcionarios, RegraFolha[] regras) { Folha folha = new Folha(); double salarioLiquido; DetalhamentoFolha detalhamento; for (int i = 0; i < funcionarios.Length; i++) { salarioLiquido = funcionarios[i].Salario; detalhamento = new DetalhamentoFolha(); for (int j = 0; j < regras.Length; j++) { salarioLiquido += regras[j].CalculaERetornaSaldo(funcionarios[i], detalhamento); } folha.AddItem(new FolhaItem(funcionarios[i], salarioLiquido, detalhamento)); } return folha; } public string[] Exporta(Folha folha, Exportador exportador) { return exportador.Exporta(folha); } } } RegraFolha.cs using System; namespace PayrollCalc { public interface RegraFolha { double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento); } } 123 7.2 PROJETO PAYROLLCALC.REGRAS (O.O.) RegraDecimoTerceiro.cs using System; namespace PayrollCalc.Regras { public class RegraDecimoTerceiro: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { int mesCalculo = funcionario.FichaPonto[0].EntradaTurnoRealizado.Month; double valorParcela = 0; if ((mesCalculo == 11) || (mesCalculo == 12)) { int mesesTrabalhados = 0; double desconto = 0; mesesTrabalhados = funcionario.GetQtdMesesTrabalhadosSemAno(mesCalculo); valorParcela = ((funcionario.Salario / 2) * mesesTrabalhados) / mesCalculo; if (valorParcela > 1400.91) { desconto = valorParcela * 0.11; if (desconto > 308.2) { desconto = 308.2; } valorParcela -= desconto; } if ((valorParcela >= 1257.13) && (valorParcela <= 2512.08)) { valorParcela -= valorParcela * 0.15; valorParcela += 188.57; } else if (valorParcela > 2512.08) { valorParcela -= valorParcela * 0.275; valorParcela += 502.58; } detalhamento.AddItem("Parcela Décimo Terceiro: R$" + valorParcela.ToString("#,##0.00")); } 124 return valorParcela; } } } RegraEstados.cs using System; namespace PayrollCalc.Regras { public class RegraEstados: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double saldo = 0; double salarioDia = funcionario.SalarioDia; saldo = (salarioDia * funcionario.FichaPonto.ContaDiasEmEstado(Ponto.EstadoPonto.Falta)); detalhamento.AddItem("Desconto Falta: R$" + saldo.ToString("#,##0.00")); return saldo * -1; } } } RegraFerias.cs using System; namespace PayrollCalc.Regras { public class RegraFerias: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double valor = this.CalculaValorBruto(funcionario, this.RetornaDiasDeFerias(funcionario)); //Desconto INSS if (valor > 1400.91) { double desconto = valor * 0.11; if (desconto > 308.2) { desconto = 308.2; } 125 valor -= desconto; } if (valor > 1257.12) { if ((valor >= 1257.13) && (valor <= 2512.08)) { valor -= (valor * 0.15); valor += 188.57; } else { valor -= (valor * 0.275); valor += 502.58; } } if (valor == 0) { detalhamento.AddItem("Sem férias no período"); } else { detalhamento.AddItem("Férias: R$" + valor.ToString("#,##0.00")); } return valor; } public int RetornaDiasDeFerias(Funcionario funcionario) { int dias = funcionario.DiasDeFerias; int qtdFaltas = funcionario.QtdFaltas; if ((qtdFaltas >=6) && (qtdFaltas <= 14)) dias -= 6; else if ((qtdFaltas >= 15) && (qtdFaltas <= 23)) dias -= 12; else if ((qtdFaltas >= 24) && (qtdFaltas <= 32)) dias -= 18; else if (qtdFaltas > 32) dias = 0; if (dias < 0) dias = 0; return dias; } public double CalculaValorBruto(Funcionario funcionario, int diasDeFerias) { double valorTotal = funcionario.SalarioDia * diasDeFerias; return valorTotal + valorTotal/3; } } } 126 RegraFGTS.cs using System; namespace PayrollCalc.Regras { public class RegraFGTS: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { detalhamento.AddItem("FGTS: R$" + RetornaValorFGTS(funcionario.Salario).ToString("#,##0.00")); return 0; } public double RetornaValorFGTS(double salario) { return salario * 0.08; } } } RegraFolhaArrayFactory.cs using System; namespace PayrollCalc.Regras { public class RegraFolhaArrayFactory { public static RegraFolha[] GetRegras() { //Devolve um array com todas as regras que incidem sobre o cálculo da folha return new RegraFolha[] { new RegraFGTS(), new RegraSalarioFamilia(), new RegraEstados(), new RegraHE(), new RegraINSS(), new RegraIRF(), new RegraDecimoTerceiro(), new RegraFerias()}; } } } RegraHE.cs using System; namespace PayrollCalc.Regras { public class RegraHE : RegraFolha { 127 public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double saldoNormal = 0; double saldoAdicional = 0; double salarioHora = 0; Ponto ponto = null; for (int i = 0; i < funcionario.FichaPonto.Count; i++) { ponto = funcionario.FichaPonto[i]; salarioHora = this.GetSalarioHora(funcionario, ponto); DateTime saidaHE = Convert.ToDateTime(ponto.SaidaHoraExtra.ToString("HH:mm")); DateTime entradaHE = Convert.ToDateTime(ponto.EntradaHoraExtra.ToString("HH:mm")); DateTime limiteInferior = Convert.ToDateTime("08:00"); DateTime limiteSuperior = Convert.ToDateTime("20:00"); //Cálculo das horas Normais if ((entradaHE >= limiteInferior) && (entradaHE < limiteSuperior) && (saidaHE >= limiteInferior) && (saidaHE <= limiteSuperior)) { saldoNormal += ((saidaHE.TimeOfDay.TotalHours entradaHE.TimeOfDay.TotalHours) * salarioHora) * 0.5; } else if (entradaHE < limiteSuperior) { if (saidaHE > limiteSuperior) { saldoNormal += ((limiteSuperior.TimeOfDay.TotalHours entradaHE.TimeOfDay.TotalHours) * salarioHora) * 0.5; } else if (saidaHE > limiteInferior) { saldoNormal += ((saidaHE.TimeOfDay.TotalHours limiteInferior.TimeOfDay.TotalHours) * salarioHora) * 0.5; } } else if (entradaHE >= limiteSuperior) { if ((saidaHE > limiteInferior) && (ponto.SaidaHoraExtra.Day > ponto.EntradaHoraExtra.Day)) { saldoNormal += ((saidaHE.TimeOfDay.TotalHours limiteInferior.TimeOfDay.TotalHours) * salarioHora) * 0.5; } } //Cálculo dos Adicionais 128 if (entradaHE >= limiteSuperior) { if((saidaHE <= limiteInferior) || (saidaHE > limiteSuperior)) { saldoAdicional += (saidaHE.TimeOfDay.TotalHours entradaHE.TimeOfDay.TotalHours) * salarioHora; } else if (saidaHE > limiteInferior) { saldoAdicional += ((limiteSuperior.TimeOfDay.TotalHours entradaHE.TimeOfDay.TotalHours) + 12) * salarioHora; } } else if (entradaHE < limiteInferior) { if (saidaHE > limiteInferior) { saldoAdicional += (limiteInferior.TimeOfDay.TotalHours entradaHE.TimeOfDay.TotalHours) * salarioHora; } } else if (entradaHE < limiteSuperior) { if (saidaHE > limiteSuperior) { saldoAdicional += (saidaHE.TimeOfDay.TotalHours limiteSuperior.TimeOfDay.TotalHours) * salarioHora; } } } double saldo = saldoAdicional + saldoNormal; detalhamento.AddItem("Horas Extras: R$" + saldo.ToString("#,##0.00")); return saldo; } private double GetSalarioHora(Funcionario funcionario, Ponto ponto) { double salarioDia = funcionario.SalarioDia; double tempoEscala = 0; if (ponto.EntradaTurnoEsperado.TimeOfDay.TotalMinutes < ponto.SaidaTurnoEsperado.TimeOfDay.TotalMinutes) { tempoEscala = ponto.SaidaTurnoEsperado.TimeOfDay.TotalMinutes ponto.EntradaTurnoEsperado.TimeOfDay.TotalMinutes; tempoEscala -= ponto.SaidaIntervaloEsperado.TimeOfDay.TotalMinutes - ponto.EntradaIntervaloEsperado.TimeOfDay.TotalMinutes; } else { DateTime endDay = Convert.ToDateTime("23:59:59"); tempoEscala = ponto.SaidaTurnoEsperado.TimeOfDay.TotalHours + (endDay.TimeOfDay.TotalHours ponto.EntradaTurnoEsperado.TimeOfDay.TotalHours); tempoEscala -= ponto.SaidaIntervaloEsperado.TimeOfDay.TotalMinutes - ponto.EntradaIntervaloEsperado.TimeOfDay.TotalMinutes; 129 } if (tempoEscala > 0) { return salarioDia/(tempoEscala/60); } else { return salarioDia; } } } } RegraINSS.cs using System; namespace PayrollCalc.Regras { /// <summary> /// Summary description for RegraINSS. /// </summary> public class RegraINSS : RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double saldo; double salario = funcionario.Salario; if (salario <= 429.00) { saldo = 0.0765 * salario; } else if ((salario > 429.00) && (salario <= 540.00)) { saldo = 0.0865 * salario; } else if ((salario > 540.00) && (salario <= 715.00)) { saldo = 0.09 * salario; } else if ((salario > 715.00) && (salario <= 1430.00)) { saldo = 0.11 * salario; } else { saldo = 157.30; } detalhamento.AddItem("INSS: R$" + saldo.ToString("#,##0.00")); return saldo * -1; 130 } } } RegraIRF.cs using System; namespace PayrollCalc.Regras { public class RegraIRF : RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double saldo = 0; double salario = funcionario.Salario; if (salario <= 1058.00) { saldo = 0; } else if ((salario > 1058.00) && (salario <= 2115.00)) { saldo = 0.15 * salario; } else { saldo = 0.275 * salario; } detalhamento.AddItem("IRF: R$" + saldo.ToString("#,##0.00")); return saldo * -1; } } } RegraSalarioFamilia.cs using System; namespace PayrollCalc.Regras { public class RegraSalarioFamilia: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { double saldo = 0; 131 if (funcionario.NroDependentes > 0) { if (funcionario.Salario <= 435.56) { saldo = (22.34 * funcionario.NroDependentes); } else if ((funcionario.Salario >= 435.57) && (funcionario.Salario <= 654.67)) { saldo = (15.74 * funcionario.NroDependentes); } else saldo = 0; } saldo = saldo * GetPercentualDiasTrabalhados(funcionario.FichaPonto); detalhamento.AddItem("Salário Família: R$" + (saldo).ToString("#,##0.00")); return saldo; } private double GetPercentualDiasTrabalhados(FichaPonto ficha) { double qtdPresencas = ficha.ContaDiasEmEstado(Ponto.EstadoPonto.Presenca); double qtdFaltas = ficha.ContaDiasEmEstado(Ponto.EstadoPonto.Falta); return qtdPresencas / (qtdPresencas + qtdFaltas); } } } 7.3 PROJETO PAYROLLCALC.EXPORTACAO (O.O.) ExportacaoUtils.cs using System; namespace PayrollCalc.Exportacao { public class ExportacaoUtils { public static string CortaString(string source, int max) { if (source.Length > max) return source.Remove(0, source.Length - max); else return source; } } } 132 ExportadorBanco.cs using System; using System.Collections; namespace PayrollCalc.Exportacao { public class ExportadorBanco: Exportador { public string[] Exporta(Folha folha) { ArrayList resultado = new ArrayList(); FolhaItem item; string linha; for (int i = 0; i < folha.Count; i++) { item = folha[i]; linha = "1212"; //Agência linha += "123" + item.Funcionario.Matricula.ToString(); //Conta linha += ExportacaoUtils.CortaString( item.Funcionario.Nome.PadLeft(20, ' '), 20); //Nome linha += item.Funcionario.Cpf; //CPF linha += ExportacaoUtils.CortaString( item.SalarioLiquido.ToString("0.00").PadLeft(10, '0').Replace(',', '.'), 10); //Valor resultado.Add(linha); } return (string[]) resultado.ToArray(typeof(string)); } } } ExportadorFGTS.cs using System; using System.Collections; using PayrollCalc.Regras; namespace PayrollCalc.Exportacao { public class ExportadorFGTS: Exportador { public string[] Exporta(Folha folha) { RegraFGTS regra = new RegraFGTS(); ArrayList resultado = new ArrayList(); FolhaItem item; string linha; double valor; for (int i = 0; i < folha.Count; i++) 133 { item = folha[i]; valor = regra.RetornaValorFGTS(item.Funcionario.Salario); linha = ExportacaoUtils.CortaString(item.Funcionario.Nome.PadLeft(20, ' '), 20); //Nome linha += item.Funcionario.Cpf; //CPF linha += ExportacaoUtils.CortaString(valor.ToString("0.00").PadLeft(10, '0').Replace(',', '.'), 10); //Valor resultado.Add(linha); } return (string[]) resultado.ToArray(typeof(string)); } } } ExportadorGrafica.cs using System; using System.Collections; namespace PayrollCalc.Exportacao { public class ExportadorGrafica: Exportador { public string[] Exporta(Folha folha) { ArrayList resultado = new ArrayList(); FolhaItem item; string linha; DetalhamentoFolha detalhamento; for (int i = 0; i < folha.Count; i++) { item = folha[i]; detalhamento = item.Detalhamento; linha = "1"; //Novo Funcionario linha += item.Funcionario.Matricula.ToString(); //Matrícula linha += ExportacaoUtils.CortaString( item.Funcionario.Nome.PadLeft(20, ' '), 20); //Nome linha += item.Funcionario.Cpf; //CPF linha += ExportacaoUtils.CortaString( item.Funcionario.Salario.ToString("0.00").PadLeft(10, '0').Replace(',', '.'), 10); //Salário Bruto linha += ExportacaoUtils.CortaString( item.SalarioLiquido.ToString("0.00").PadLeft(10, '0').Replace(',', '.'), 10); //Salário Líquido resultado.Add(linha); for (int j = 0; j < detalhamento.Count; j++) { 134 linha = "2"; //Novo Detalhe linha += ExportacaoUtils.CortaString( detalhamento[j].ToString().PadLeft(54, ' '), 54); //Descricao resultado.Add(linha); } } return (string[]) resultado.ToArray(typeof(string)); } } } 7.4 PROJETO PAYROLLCALCTESTS DetalhamentoFolhaTests.cs using System; using NUnit.Framework; namespace PayrollCalc.Tests { [TestFixture] public class DetalhamentoFolhaTests { DetalhamentoFolha detalhamento; [SetUp] public void Inicializa() { detalhamento = new DetalhamentoFolha(); } [Test] public void AdicionaEVerificaCount() { string item = "detalhe"; detalhamento.AddItem(item); Assert.AreEqual(1, detalhamento.Count); } [Test] public void AdicionaRemoveEVerificaCount() { string item = "detalhe"; detalhamento.AddItem(item); detalhamento.RemItemAt(0); Assert.AreEqual(0, detalhamento.Count); } [Test] 135 public void Adiciona3EVerificaCount() { string item = "detalhe"; detalhamento.AddItem(item); detalhamento.AddItem(item); detalhamento.AddItem(item); Assert.AreEqual(3, detalhamento.Count); } [Test] public void Adiciona3Remove2EVerificaCount() { string item = "detalhe"; detalhamento.AddItem(item); detalhamento.AddItem(item); detalhamento.AddItem(item); detalhamento.RemItemAt(0); detalhamento.RemItemAt(0); Assert.AreEqual(1, detalhamento.Count); } [Test] public void AdicionaEVerificaItem() { string item = "detalhe"; detalhamento.AddItem(item); Assert.AreEqual(item, detalhamento.GetItem(0)); Assert.AreEqual(item, detalhamento[0]); } [Test] public void Adiciona3EVerificaItens() { string item1 = "detalhe1"; string item2 = "detalhe2"; string item3 = "detalhe3"; detalhamento.AddItem(item1); detalhamento.AddItem(item2); detalhamento.AddItem(item3); Assert.AreEqual(item1, Assert.AreEqual(item1, Assert.AreEqual(item2, Assert.AreEqual(item2, Assert.AreEqual(item3, Assert.AreEqual(item3, detalhamento.GetItem(0)); detalhamento[0]); detalhamento.GetItem(1)); detalhamento[1]); detalhamento.GetItem(2)); detalhamento[2]); } [Test] public void Adiciona3Remove2EVerificaItem() { string item1 = "detalhe1"; string item2 = "detalhe2"; string item3 = "detalhe3"; 136 detalhamento.AddItem(item1); detalhamento.AddItem(item2); detalhamento.AddItem(item3); detalhamento.RemItemAt(0); detalhamento.RemItemAt(1); Assert.AreEqual(item2, detalhamento.GetItem(0)); Assert.AreEqual(item2, detalhamento[0]); } } } FichaPontoTests.cs using System; using NUnit.Framework; using PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests { [TestFixture] public class FichaPontoTests { FichaPonto ficha; [SetUp] public void Inicializa() { ficha = new FichaPonto(); } [Test] public void AdicionaEVerificaCount() { Ponto ponto = CriaPontoNovo(); ficha.AddPonto(ponto); Assert.AreEqual(1, ficha.Count); } [Test] public void AdicionaRemoveEVerificaCount() { Ponto ponto = CriaPontoNovo(); ficha.AddPonto(ponto); ficha.RemPontoAt(0); Assert.AreEqual(0, ficha.Count); } [Test] public void Adiciona3EVerificaCount() { Ponto ponto = CriaPontoNovo(); 137 ficha.AddPonto(ponto); ficha.AddPonto(ponto); ficha.AddPonto(ponto); Assert.AreEqual(3, ficha.Count); } [Test] public void Adiciona3Remove2EVerificaCount() { Ponto ponto = CriaPontoNovo(); ficha.AddPonto(ponto); ficha.AddPonto(ponto); ficha.AddPonto(ponto); ficha.RemPontoAt(0); ficha.RemPontoAt(0); Assert.AreEqual(1, ficha.Count); } [Test] public void AdicionaEVerificaPonto() { Ponto ponto = CriaPontoNovo(); ficha.AddPonto(ponto); Assert.AreSame(ponto, ficha.GetPonto(0)); Assert.AreSame(ponto, ficha[0]); } [Test] public { Ponto Ponto Ponto void Adiciona3EVerificaPontos() ponto1 = CriaPontoNovo(); ponto2 = CriaPontoNovo(); ponto3 = CriaPontoNovo(); ficha.AddPonto(ponto1); ficha.AddPonto(ponto2); ficha.AddPonto(ponto3); Assert.AreSame(ponto1, Assert.AreSame(ponto1, Assert.AreSame(ponto2, Assert.AreSame(ponto2, Assert.AreSame(ponto3, Assert.AreSame(ponto3, ficha.GetPonto(0)); ficha[0]); ficha.GetPonto(1)); ficha[1]); ficha.GetPonto(2)); ficha[2]); } [Test] public { Ponto Ponto Ponto void Adiciona3Remove2EVerificaPonto() ponto1 = CriaPontoNovo(); ponto2 = CriaPontoNovo(); ponto3 = CriaPontoNovo(); ficha.AddPonto(ponto1); ficha.AddPonto(ponto2); 138 ficha.AddPonto(ponto3); ficha.RemPontoAt(0); ficha.RemPontoAt(1); Assert.AreSame(ponto2, ficha.GetPonto(0)); Assert.AreSame(ponto2, ficha[0]); } [Test] public void ContaDiasTrabalhados() { PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Presenca, 10); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.DiaNaoUtil, 5); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Afastamento, 10); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Falta, 6); Assert.AreEqual(10, ficha.ContaDiasEmEstado(Ponto.EstadoPonto.Presenca)); Assert.AreEqual(5, ficha.ContaDiasEmEstado(Ponto.EstadoPonto.DiaNaoUtil)); Assert.AreEqual(10, ficha.ContaDiasEmEstado(Ponto.EstadoPonto.Afastamento)); Assert.AreEqual(6, ficha.ContaDiasEmEstado(Ponto.EstadoPonto.Falta)); } private Ponto CriaPontoNovo() { return PayrollTestUtils.CriaPontoNovo(Ponto.EstadoPonto.Presenca); } } } FolhaItemTests.cs using System; using NUnit.Framework; namespace PayrollCalc.Tests { [TestFixture] public class FolhaItemTests { [Test] public void CriaFolhaItemEVerificaPropriedades() { CriaEVerificaFolhaItem( new Funcionario(0, 0, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0), 3543.21, new DetalhamentoFolha()); } [Test, ExpectedException(typeof(Exception), "Não é possível criar um ítem de folha sem especificar um funcionário e um detalhamento.")] public void CriaFolhaItemSemFuncionario() { 139 CriaEVerificaFolhaItem( new Funcionario(0, 0, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0), 3543.21, null); } [Test, ExpectedException(typeof(Exception), "Não é possível criar um ítem de folha sem especificar um funcionário e um detalhamento.")] public void CriaFolhaItemSemDetalhamento() { CriaEVerificaFolhaItem( null, 3543.21, new DetalhamentoFolha()); } private void CriaEVerificaFolhaItem(Funcionario funcionario, double salarioLiquido, DetalhamentoFolha detalhamento) { FolhaItem folhaItem = new FolhaItem(funcionario, salarioLiquido, detalhamento); Assert.AreSame(funcionario, folhaItem.Funcionario); Assert.AreEqual(salarioLiquido, folhaItem.SalarioLiquido); Assert.AreSame(detalhamento, folhaItem.Detalhamento); } } } FolhaTests.cs using System; using NUnit.Framework; namespace PayrollCalc.Tests { [TestFixture] public class FolhaTests { Folha folha; [SetUp] public void Inicializa() { folha = new Folha(); } [Test] public void AdicionaEVerificaCount() { FolhaItem item = CriaItemNovo(); folha.AddItem(item); Assert.AreEqual(1, folha.Count); } 140 [Test] public void AdicionaRemoveEVerificaCount() { FolhaItem item = CriaItemNovo(); folha.AddItem(item); folha.RemItemAt(0); Assert.AreEqual(0, folha.Count); } [Test] public void Adiciona3EVerificaCount() { FolhaItem item = CriaItemNovo(); folha.AddItem(item); folha.AddItem(item); folha.AddItem(item); Assert.AreEqual(3, folha.Count); } [Test] public void Adiciona3Remove2EVerificaCount() { FolhaItem item = CriaItemNovo(); folha.AddItem(item); folha.AddItem(item); folha.AddItem(item); folha.RemItemAt(0); folha.RemItemAt(0); Assert.AreEqual(1, folha.Count); } [Test] public void AdicionaEVerificaFolhaItem() { FolhaItem item = CriaItemNovo(); folha.AddItem(item); Assert.AreSame(item, folha.GetItem(0)); Assert.AreSame(item, folha[0]); } [Test] public void Adiciona3EVerificaFolhaItens() { FolhaItem item1 = CriaItemNovo(); FolhaItem item2 = CriaItemNovo(); FolhaItem item3 = CriaItemNovo(); folha.AddItem(item1); folha.AddItem(item2); folha.AddItem(item3); Assert.AreSame(item1, folha.GetItem(0)); 141 Assert.AreSame(item1, Assert.AreSame(item2, Assert.AreSame(item2, Assert.AreSame(item3, Assert.AreSame(item3, folha[0]); folha.GetItem(1)); folha[1]); folha.GetItem(2)); folha[2]); } [Test] public void Adiciona3Remove2EVerificaFolhaItem() { FolhaItem item1 = CriaItemNovo(); FolhaItem item2 = CriaItemNovo(); FolhaItem item3 = CriaItemNovo(); folha.AddItem(item1); folha.AddItem(item2); folha.AddItem(item3); folha.RemItemAt(0); folha.RemItemAt(1); Assert.AreSame(item2, folha.GetItem(0)); Assert.AreSame(item2, folha[0]); } private FolhaItem CriaItemNovo() { return new FolhaItem(new Funcionario(0, 0, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0), 0, new DetalhamentoFolha()); } } } FuncionarioTests.cs using System; using NUnit.Framework; namespace PayrollCalc.Tests { [TestFixture] public class FuncionarioTests { [Test] public void CriaFuncionarioEVerificaPropriedades() { CriaEVerificaFuncionario(123, 1000.55, "ABC", "12345678901", 5, new FichaPonto() ); } 142 [Test, ExpectedException(typeof(Exception), "É necessário configurar uma FichaPonto")] public void CriaFuncionarioSemFichaPontoEVerificaPropriedades() { CriaEVerificaFuncionario(123, 1000.55, "ABC", "12345678901", 5, null ); } private void CriaEVerificaFuncionario(int matricula, double salario, string nome, string cpf, int nroDependentes, FichaPonto fichaPonto) { Funcionario func = new Funcionario(matricula, salario, nome, cpf, nroDependentes, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(matricula, func.Matricula); Assert.AreEqual(salario, func.Salario); Assert.AreEqual(nome, func.Nome); Assert.AreEqual(cpf, func.Cpf); Assert.AreEqual(nroDependentes, func.NroDependentes); Assert.AreEqual(fichaPonto, func.FichaPonto); } } } PayrollTestUtils.cs using System; namespace PayrollCalc.Tests.Utils { public class PayrollTestUtils { public static Ponto CriaPontoNovo(Ponto.EstadoPonto estado) { return new Ponto(estado, Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00"), Convert.ToDateTime("00:00") ); } public static void AddPontos(FichaPonto ficha, Ponto.EstadoPonto estado, int qtd) { 143 for (int i = 0; i < qtd; i++) ficha.AddPonto(CriaPontoNovo(estado)); } } } PontoTests.cs using System; using NUnit.Framework; namespace PayrollCalc.Tests { [TestFixture] public class PontoTests { [Test] public void CriaPontoCompletoEVerificaPropriedades() { CriaEVerificaPonto(Ponto.EstadoPonto.Falta, Convert.ToDateTime("01:00"), Convert.ToDateTime("02:00"), Convert.ToDateTime("03:00"), Convert.ToDateTime("04:00"), Convert.ToDateTime("05:00"), Convert.ToDateTime("06:00"), Convert.ToDateTime("07:00"), Convert.ToDateTime("08:00"), Convert.ToDateTime("09:00"), Convert.ToDateTime("10:00") ); } [Test] public void CriaPontoSemHEEVerificaPropriedades() { CriaEVerificaPonto(Ponto.EstadoPonto.Falta, Convert.ToDateTime("01:00"), Convert.ToDateTime("02:00"), Convert.ToDateTime("03:00"), Convert.ToDateTime("04:00"), Convert.ToDateTime("05:00"), Convert.ToDateTime("06:00"), Convert.ToDateTime("07:00"), Convert.ToDateTime("08:00"), Convert.ToDateTime(null), Convert.ToDateTime(null) ); } private void CriaEVerificaPonto(Ponto.EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, 144 DateTime saidaHoraExtra) { Ponto ponto = new Ponto(estado, entradaTurnoEsperado, saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra); Assert.AreEqual(estado, ponto.Estado); Assert.AreEqual(entradaTurnoEsperado, ponto.EntradaTurnoEsperado); Assert.AreEqual(saidaTurnoEsperado, ponto.SaidaTurnoEsperado); Assert.AreEqual(entradaTurnoRealizado, ponto.EntradaTurnoRealizado); Assert.AreEqual(saidaTurnoRealizado, ponto.SaidaTurnoRealizado); Assert.AreEqual(entradaIntervaloEsperado, ponto.EntradaIntervaloEsperado); Assert.AreEqual(saidaIntervaloEsperado, ponto.SaidaIntervaloEsperado); Assert.AreEqual(entradaIntervaloRealizado, ponto.EntradaIntervaloRealizado); Assert.AreEqual(saidaIntervaloRealizado, ponto.SaidaIntervaloRealizado); Assert.AreEqual(entradaHoraExtra, ponto.EntradaHoraExtra); Assert.AreEqual(saidaHoraExtra, ponto.SaidaHoraExtra); } } } ProcessadorDeFolhaTests.cs using using using using using using System; System.Collections; NUnit.Framework; PayrollCalc.Tests.Mocks; PayrollCalc.Regras; PayrollCalc.Exportacao; namespace PayrollCalc.Tests { [TestFixture] public class ProcessadorDeFolhaTests { ProcessadorDeFolha proc; [SetUp] public void Inicializa() { proc = new ProcessadorDeFolha(); } [Test] public void RodaFolhaCom0FuncionariosE0Regras() { RodaFuncionariosERegras(0, 0); } 145 [Test] public void RodaFolhaCom1FuncionarioE1Regra() { RodaFuncionariosERegras(1, 1); } [Test] public void RodaFolhaCom3FuncionarioE1Regra() { RodaFuncionariosERegras(3, 1); } [Test] public void RodaFolhaCom1FuncionarioE3Regra() { RodaFuncionariosERegras(1, 3); } [Test] public void RodaFolhaCom3FuncionarioE3Regra() { RodaFuncionariosERegras(3, 3); } [Test] public void Exporta() { Folha folha = new Folha(); ExportadorMock exportador = new ExportadorMock(); proc.Exporta(folha, exportador); exportador.Verifica(folha); } private void RodaFuncionariosERegras(int qtdFuncionarios, int qtdRegras) { ArrayList funcs = new ArrayList(); ArrayList regras = new ArrayList(); for (int i = 0; i < qtdFuncionarios; i++) { funcs.Add(new Funcionario(0, 0, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0)); } for (int i = 0; i < qtdRegras; i++) { regras.Add(new RegraFolhaMock()); } Folha folha = proc.Roda((Funcionario[]) funcs.ToArray(typeof(Funcionario)), (RegraFolha[]) regras.ToArray(typeof(RegraFolha))); Assert.AreEqual(qtdFuncionarios, folha.Count); for (int i = 0; i < qtdRegras; i++) { ((RegraFolhaMock) regras[regras.Count - 1]).Verifica(funcs); } 146 } } } ExportacaoUtilsTests.cs using System; using NUnit.Framework; using PayrollCalc.Exportacao; namespace PayrollCalc.Tests.Exportacao { [TestFixture] public class ExportacaoUtilsTests { [Test] public void TestaNormal() { Assert.AreEqual("cdef", ExportacaoUtils.CortaString("abcdef", 4)); Assert.AreEqual("cdef", ExportacaoUtils.CortaString("cdef", 6)); } } } ExportacaoBancoTests.cs using System; using NUnit.Framework; using PayrollCalc.Exportacao; namespace PayrollCalc.Tests.Exportacao { [TestFixture] public class ExportadorBancoTests { ExportadorBanco exportador; Folha folha; [SetUp] public void Inicializa() { exportador = new ExportadorBanco(); folha = new Folha(); } [Test] public void ExportaFolhaVazia() { string[] resultado = exportador.Exporta(folha); Assert.AreEqual(0, resultado.Length); } 147 [Test] public void Exporta1Pessoa() { FolhaItem item = CriaFolhaItem(folha, CriaFuncionario(123, "fulano da silva", "12345678901"), 1233.21); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(1, resultado.Length); Assert.AreEqual("1212123123 fulano da silva123456789010001233.21", resultado[0]); } [Test] public void Exporta3Pessoas() { FolhaItem item = CriaFolhaItem(folha, CriaFuncionario(123, "fulano da silva", "12345678901"), 111211.11); folha.AddItem(item); item = CriaFolhaItem(folha, CriaFuncionario(456, "beltrano souza", "99999999999"), 120.0); folha.AddItem(item); item = CriaFolhaItem(folha, CriaFuncionario(789, "ciclano pereira", "11111111111"), 3.21); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(3, resultado.Length); Assert.AreEqual("1212123123 fulano da silva123456789010111211.11", resultado[0]); Assert.AreEqual("1212123456 beltrano souza999999999990000120.00", resultado[1]); Assert.AreEqual("1212123789 ciclano pereira111111111110000003.21", resultado[2]); } private FolhaItem CriaFolhaItem(Folha folha, Funcionario funcionario, double salarioLiquido) { return new FolhaItem(funcionario, salarioLiquido, new DetalhamentoFolha()); } private Funcionario CriaFuncionario(int matricula, string nome, string cpf) { return new Funcionario(matricula, 0, nome, cpf, 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); } } } 148 ExportadorFGTSTests.cs using System; using NUnit.Framework; using PayrollCalc.Exportacao; namespace PayrollCalc.Tests.Exportacao { [TestFixture] public class ExportadorFGTSTests { ExportadorFGTS exportador; Folha folha; [SetUp] public void Inicializa() { exportador = new ExportadorFGTS(); folha = new Folha(); } [Test] public void ExportaFolhaVazia() { string[] resultado = exportador.Exporta(folha); Assert.AreEqual(0, resultado.Length); } [Test] public void Exporta1Pessoa() { FolhaItem item = CriaFolhaItem(folha, CriaFuncionario("fulano da silva", "12345678901", 1233.21)); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(1, resultado.Length); Assert.AreEqual(" fulano da silva123456789010000098.66", resultado[0]); } [Test] public void Exporta3Pessoas() { FolhaItem item = CriaFolhaItem(folha, CriaFuncionario("fulano da silva", "12345678901", 111211.11)); folha.AddItem(item); item = CriaFolhaItem(folha, CriaFuncionario("beltrano souza", "99999999999", 120.0)); folha.AddItem(item); item = CriaFolhaItem(folha, CriaFuncionario("ciclano pereira", "11111111111", 3.21)); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(3, resultado.Length); 149 Assert.AreEqual(" resultado[0]); Assert.AreEqual(" resultado[1]); Assert.AreEqual(" resultado[2]); } fulano da silva123456789010008896.89", beltrano souza999999999990000009.60", ciclano pereira111111111110000000.26", private FolhaItem CriaFolhaItem(Folha folha, Funcionario funcionario) { return new FolhaItem(funcionario, 0, new DetalhamentoFolha()); } private Funcionario CriaFuncionario(string nome, string cpf, double salario) { return new Funcionario(0, salario, nome, cpf, 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); } } } ExportadorGraficaTests.cs using System; using NUnit.Framework; using PayrollCalc.Exportacao; namespace PayrollCalc.Tests.Exportacao { [TestFixture] public class ExportadorGraficaTests { ExportadorGrafica exportador; Folha folha; DetalhamentoFolha detalhamento; [SetUp] public void Inicializa() { exportador = new ExportadorGrafica(); folha = new Folha(); detalhamento = new DetalhamentoFolha(); } [Test] public void ExportaFolhaVazia() { string[] resultado = exportador.Exporta(folha); Assert.AreEqual(0, resultado.Length); } [Test] public void ExportaSemDetalhes() { 150 FolhaItem item = CriaFolhaItem(folha, CriaFuncionario(123, "fulano da silva", "12345678901", 1000), 1233.21, detalhamento); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(1, resultado.Length); Assert.AreEqual("1123 fulano da silva123456789010001000.000001233.21", resultado[0]); } [Test] public void Exporta3Detalhes() { FolhaItem item = CriaFolhaItem(folha, CriaFuncionario(123, "fulano da silva", "12345678901", 1000), 1233.21, detalhamento); detalhamento.AddItem("detalheA"); detalhamento.AddItem("detalheB"); detalhamento.AddItem("detalheC"); folha.AddItem(item); string[] resultado = exportador.Exporta(folha); Assert.AreEqual(4, resultado.Length); Assert.AreEqual("1123 fulano da silva123456789010001000.000001233.21", resultado[0]); Assert.AreEqual("2 detalheA", resultado[1]); Assert.AreEqual("2 detalheB", resultado[2]); Assert.AreEqual("2 detalheC", resultado[3]); } private FolhaItem CriaFolhaItem(Folha folha, Funcionario funcionario, double salarioLiquido, DetalhamentoFolha detalhamento) { return new FolhaItem(funcionario, salarioLiquido, detalhamento); } private Funcionario CriaFuncionario(int matricula, string nome, string cpf, double salario) { return new Funcionario(matricula, salario, nome, cpf, 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); } } } ExportadorMock.cs using System; 151 using NUnit.Framework; using PayrollCalc.Exportacao; namespace PayrollCalc.Tests.Mocks { public class ExportadorMock: Exportador { private Folha folha; public string[] Exporta(Folha folha) { this.folha = folha; return new string[] {}; } public void Verifica(Folha folha) { Assert.AreSame(folha, this.folha); } } } RegraFolhaMock.cs using using using using System; System.Collections; NUnit.Framework; PayrollCalc.Regras; namespace PayrollCalc.Tests.Mocks { public class RegraFolhaMock: RegraFolha { private ArrayList funcionarios = new ArrayList(); public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { funcionarios.Add(funcionario); return 0; } public void Verifica(ArrayList funcionarios) { Assert.AreEqual(funcionarios.Count, this.funcionarios.Count); for (int i = 0; i < funcionarios.Count; i++) Assert.AreSame(funcionarios[i], this.funcionarios[i]); } } } 152 RegraDecimoTerceiroTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraDecimoTerceiroTests { private RegraDecimoTerceiro regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraDecimoTerceiro(); detalhamento = new DetalhamentoFolha(); } [Test] public void CalculaPrimeiraParcelaIntegralSemDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 07:00"), Convert.ToDateTime("01/11/2007 09:00"))); Funcionario func = new Funcionario(0, 1000.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/01/2007"), 0, 0); Assert.AreEqual(500.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$500,00", detalhamento[0]); } [Test] public void CalculaPrimeiraParcelaProporcionalSemDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 16:30"), 153 Convert.ToDateTime("01/11/2007 Convert.ToDateTime("01/11/2007 Convert.ToDateTime("01/11/2007 Convert.ToDateTime("01/11/2007 Convert.ToDateTime("01/11/2007 16:45"), 16:30"), 16:45"), 07:00"), 09:00"))); Funcionario func = new Funcionario(0, 1100.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/06/2007"), 0, 0); Assert.AreEqual(300.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$300,00", detalhamento[0]); } [Test] public void CalculaPrimeiraParcelaIntegralComDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 07:00"), Convert.ToDateTime("01/11/2007 09:00"))); Funcionario func = new Funcionario(0, 5000.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/01/2007"), 0, 0); Assert.AreEqual(2079.82, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$2.079,82", detalhamento[0]); } [Test] public void CalculaPrimeiraParcelaProporcionalComDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 12:00"), Convert.ToDateTime("01/11/2007 18:15"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 16:30"), Convert.ToDateTime("01/11/2007 16:45"), Convert.ToDateTime("01/11/2007 07:00"), Convert.ToDateTime("01/11/2007 09:00"))); 154 Funcionario func = new Funcionario(0, 5500.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/06/2007"), 0, 0); Assert.AreEqual(1323.32, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$1.323,32", detalhamento[0]); } [Test] public void CalculaSegundaParcelaIntegralSemDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 07:00"), Convert.ToDateTime("01/12/2007 09:00"))); Funcionario func = new Funcionario(0, 1000.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/01/2007"), 0, 0); Assert.AreEqual(500.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$500,00", detalhamento[0]); } [Test] public void CalculaSegundaParcelaProporcionalSemDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 07:00"), Convert.ToDateTime("01/12/2007 09:00"))); Funcionario func = new Funcionario(0, 1200.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/06/2007"), 0, 0); Assert.AreEqual(350.00, regra.CalculaERetornaSaldo(func, detalhamento)); 155 Assert.AreEqual("Parcela Décimo Terceiro: R$350,00", detalhamento[0]); } [Test] public void CalculaSegundaParcelaIntegralComDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 07:00"), Convert.ToDateTime("01/12/2007 09:00"))); Funcionario func = new Funcionario(0, 5000.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/01/2007"), 0, 0); Assert.AreEqual(2079.82, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$2.079,82", detalhamento[0]); } [Test] public void CalculaSegundaParcelaProporcionalComDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 12:00"), Convert.ToDateTime("01/12/2007 18:15"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 16:30"), Convert.ToDateTime("01/12/2007 16:45"), Convert.ToDateTime("01/12/2007 07:00"), Convert.ToDateTime("01/12/2007 09:00"))); Funcionario func = new Funcionario(0, 5400.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/06/2007"), 0, 0); Assert.AreEqual(1380.0575, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Parcela Décimo Terceiro: R$1.380,06", detalhamento[0]); } [Test] public void SemDecimoTerceiro() { 156 FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("01/10/2007 12:00"), Convert.ToDateTime("01/10/2007 18:15"), Convert.ToDateTime("01/10/2007 12:00"), Convert.ToDateTime("01/10/2007 18:15"), Convert.ToDateTime("01/10/2007 16:30"), Convert.ToDateTime("01/10/2007 16:45"), Convert.ToDateTime("01/10/2007 16:30"), Convert.ToDateTime("01/10/2007 16:45"), Convert.ToDateTime("01/10/2007 07:00"), Convert.ToDateTime("01/10/2007 09:00"))); Funcionario func = new Funcionario(0, 5400.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("01/06/2007"), 0, 0); Assert.AreEqual(0, regra.CalculaERetornaSaldo(func, detalhamento)); } private Ponto GeraPonto(Ponto.EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, DateTime saidaHoraExtra) { return new Ponto(estado, entradaTurnoEsperado, saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra); } } } RegraEstadosTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraEstadosTests { private RegraEstados regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraEstados(); 157 detalhamento = new DetalhamentoFolha(); } [Test] public void RodaNormal() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime(null), Convert.ToDateTime(null))); Funcionario func = new Funcionario(0, 10.8, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(0, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Desconto Falta: R$0,00", detalhamento[0]); } [Test] public void RodaFalta() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Falta, Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null), Convert.ToDateTime(null))); Funcionario func = new Funcionario(0, 10, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-10.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Desconto Falta: R$10,00", detalhamento[0]); } private Ponto GeraPonto(Ponto.EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, 158 DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, DateTime saidaHoraExtra) { return new Ponto(estado, entradaTurnoEsperado, saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra); } } } RegraFeriasTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraFeriasTests { private RegraFerias regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraFerias(); detalhamento = new DetalhamentoFolha(); } [Test] public void CalculaFeriasZeroFaltasSemDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("07:00"), Convert.ToDateTime("09:00"))); Funcionario func = new Funcionario(0, 30.0, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 1, 31); Assert.AreEqual(1200, regra.CalculaERetornaSaldo(func, detalhamento)); 159 Assert.AreEqual("Férias: R$1.200,00", detalhamento[0]); } [Test] public void CalculaFeriasZeroFaltasComDesconto() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("07:00"), Convert.ToDateTime("09:00"))); Funcionario func = new Funcionario(0, 50, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 1, 31); Assert.AreEqual(1701.57, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Férias: R$1.701,57", detalhamento[0]); } [Test] public void CalculaFeriasComFaltas() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("07:00"), Convert.ToDateTime("09:00"))); Funcionario func = new Funcionario(0, 100, "", "", 0, fichaPonto, 10, Convert.ToDateTime("1/1/2007"), 1, 31); Assert.AreEqual(2599.135, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Férias: R$2.599,14", detalhamento[0]); } private Ponto GeraPonto(Ponto.EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, 160 DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, DateTime saidaHoraExtra) { return new Ponto(estado, entradaTurnoEsperado, saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra); } } } RegraFGTSTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraFGTSTests { private RegraFGTS regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraFGTS(); detalhamento = new DetalhamentoFolha(); } [Test] public void RodaNormal() { Funcionario func = new Funcionario(0, 10.8, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(0, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("FGTS: R$0,86", detalhamento[0]); } [Test] public void RodaSalZero() { Funcionario func = new Funcionario(0, 0, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(0, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("FGTS: R$0,00", detalhamento[0]); } } } 161 RegraHETests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraHETests { private RegraHE regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraHE(); detalhamento = new DetalhamentoFolha(); } [Test] public void RodaHE50Cheia() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("16:00"), Convert.ToDateTime("18:00"))); Funcionario func = new Funcionario(0, 600, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(100.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Horas Extras: R$100,00", detalhamento[0]); } [Test] public void RodaHE100Cheia() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), 162 Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("21:00"), Convert.ToDateTime("23:00"))); Funcionario func = new Funcionario(0, 600, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(200.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Horas Extras: R$200,00", detalhamento[0]); } [Test] public void RodaHEMetade50Metade100() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), Convert.ToDateTime("08:00"), Convert.ToDateTime("14:15"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("10:30"), Convert.ToDateTime("10:45"), Convert.ToDateTime("19:00"), Convert.ToDateTime("21:00"))); Funcionario func = new Funcionario(0, 600, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(150.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Horas Extras: R$150,00", detalhamento[0]); } [Test] public void RodaHEMetade100Metade50() { FichaPonto fichaPonto = new FichaPonto(); fichaPonto.AddPonto(this.GeraPonto(Ponto.EstadoPonto.Presenca, Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("12:00"), Convert.ToDateTime("18:15"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("16:30"), Convert.ToDateTime("16:45"), Convert.ToDateTime("07:00"), Convert.ToDateTime("09:00"))); 163 Funcionario func = new Funcionario(0, 600, "", "", 0, fichaPonto, 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(150.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("Horas Extras: R$150,00", detalhamento[0]); } private Ponto GeraPonto(Ponto.EstadoPonto estado, DateTime entradaTurnoEsperado, DateTime saidaTurnoEsperado, DateTime entradaTurnoRealizado, DateTime saidaTurnoRealizado, DateTime entradaIntervaloEsperado, DateTime saidaIntervaloEsperado, DateTime entradaIntervaloRealizado, DateTime saidaIntervaloRealizado, DateTime entradaHoraExtra, DateTime saidaHoraExtra) { return new Ponto(estado, entradaTurnoEsperado, saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra); } } } RegraINSSTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraINSSTests { private RegraINSS regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraINSS(); detalhamento = new DetalhamentoFolha(); } [Test] public void RodaSalFaixa1() { Funcionario func = new Funcionario(0, 400, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-30.6, regra.CalculaERetornaSaldo(func, detalhamento)); 164 Assert.AreEqual("INSS: R$30,60", detalhamento[0]); } [Test] public void RodaSalFaixa2() { Funcionario func = new Funcionario(0, 500, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-43.25, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("INSS: R$43,25", detalhamento[0]); } [Test] public void RodaSalFaixa3() { Funcionario func = new Funcionario(0, 700, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-63.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("INSS: R$63,00", detalhamento[0]); } [Test] public void RodaSalFaixa4() { Funcionario func = new Funcionario(0, 1000, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-110.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("INSS: R$110,00", detalhamento[0]); } [Test] public void RodaSalFaixa5() { Funcionario func = new Funcionario(0, 7000, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-157.30, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("INSS: R$157,30", detalhamento[0]); } } } 165 RegraIRFTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraIRFTests { private RegraIRF regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraIRF(); detalhamento = new DetalhamentoFolha(); } [Test] public void RodaIsento() { Funcionario func = new Funcionario(0, 1000, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(0, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("IRF: R$0,00", detalhamento[0]); } [Test] public void RodaSalFaixa1() { Funcionario func = new Funcionario(0, 2000, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-300.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("IRF: R$300,00", detalhamento[0]); } [Test] public void RodaSalFaixa3() { Funcionario func = new Funcionario(0, 5000, "", "", 0, new FichaPonto(), 0, Convert.ToDateTime("1/1/2007"), 0, 0); Assert.AreEqual(-1375.00, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual("IRF: R$1.375,00", detalhamento[0]); } } } 166 RegraSalarioFamiliaTests.cs using using using using System; NUnit.Framework; PayrollCalc.Regras; PayrollCalc.Tests.Utils; namespace PayrollCalc.Tests.Regras { [TestFixture] public class RegraSalarioFamiliaTests { private RegraSalarioFamilia regra; private DetalhamentoFolha detalhamento = null; [SetUp] public void Inicializa() { regra = new RegraSalarioFamilia(); } [Test] public void TestSemFilhos() { RodaTeste(000.00, 0, 30, 0, RodaTeste(200.00, 0, 30, 0, RodaTeste(500.00, 0, 30, 0, RodaTeste(800.00, 0, 30, 0, } 0, 0, 0, 0, 0, 0, 0, 0, 00.00, 00.00, 00.00, 00.00, 00.00); 00.00); 00.00); 00.00); [Test] public void Test1Filho1Faixa() { RodaTeste(000.00, 1, 30, 0, 0, 0, 22.34, 00.00); RodaTeste(200.00, 1, 30, 0, 0, 0, 22.34, 00.00); RodaTeste(435.56, 1, 30, 0, 0, 0, 22.34, 00.00); } [Test] public void Test1Filho2Faixa() { RodaTeste(435.57, 1, 30, 0, 0, 0, 15.74, 00.00); RodaTeste(500.00, 1, 30, 0, 0, 0, 15.74, 00.00); RodaTeste(654.67, 1, 30, 0, 0, 0, 15.74, 00.00); } [Test] public void Test1Filho3Faixa() { RodaTeste(654.68, 1, 30, 0, 0, 0, 00.00, 00.00); RodaTeste(800.00, 1, 30, 0, 0, 0, 00.00, 00.00); } [Test] public void Test3Filhos1Faixa() { RodaTeste(000.00, 3, 30, 0, 0, 0, 22.34, 00.00); RodaTeste(200.00, 3, 30, 0, 0, 0, 22.34, 00.00); RodaTeste(435.56, 3, 30, 0, 0, 0, 22.34, 00.00); } 167 [Test] public void Test3Filhos2Faixa() { RodaTeste(435.57, 3, 30, 0, 0, 0, 15.74, 00.00); RodaTeste(500.00, 3, 30, 0, 0, 0, 15.74, 00.00); RodaTeste(654.67, 3, 30, 0, 0, 0, 15.74, 00.00); } [Test] public void Test3Filhos3Faixa() { RodaTeste(654.68, 3, 30, 0, 0, 0, 00.00, 00.00); RodaTeste(800.00, 3, 30, 0, 0, 0, 00.00, 00.00); } [Test] public void Test3Filhos1FaixaComFaltas() { RodaTeste(000.00, 3, 20, 0, 10, 0, 22.34, 22.34); RodaTeste(200.00, 3, 20, 0, 10, 0, 22.34, 22.34); RodaTeste(435.56, 3, 20, 0, 10, 0, 22.34, 22.34); } [Test] public void Test3Filhos2FaixaComFaltas() { RodaTeste(435.57, 3, 20, 0, 10, 0, 15.74, 15.74); RodaTeste(500.00, 3, 20, 0, 10, 0, 15.74, 15.74); RodaTeste(654.67, 3, 20, 0, 10, 0, 15.74, 15.74); } [Test] public void Test3Filhos1FaixaComTodosEstados() { RodaTeste(000.00, 3, 10, 7, 10, 3, 22.34, 33.51); RodaTeste(200.00, 3, 10, 7, 10, 3, 22.34, 33.51); RodaTeste(435.56, 3, 10, 7, 10, 3, 22.34, 33.51); } [Test] public void Test3Filhos2FaixaComTodosEstados() { RodaTeste(435.57, 3, 10, 7, 10, 3, 15.74, 23.61); RodaTeste(500.00, 3, 10, 7, 10, 3, 15.74, 23.61); RodaTeste(654.67, 3, 10, 7, 10, 3, 15.74, 23.61); } private void RodaTeste(double salario, int nroDependentes, int nroPresencas, int nroDiasNaoUteis, int nroFaltas, int nroAfastamentos, double salLiquidoEsperado, double descontoEsperado) { detalhamento = new DetalhamentoFolha(); Funcionario func = CriaFuncionario(salario, nroDependentes, nroPresencas, nroDiasNaoUteis, nroFaltas, nroAfastamentos); Assert.AreEqual((salLiquidoEsperado * nroDependentes) descontoEsperado, regra.CalculaERetornaSaldo(func, detalhamento)); Assert.AreEqual(1, detalhamento.Count); 168 Assert.AreEqual("Salário Família: R$" + ((salLiquidoEsperado * nroDependentes) descontoEsperado).ToString("#,##0.00"), detalhamento.GetItem(0)); } private Funcionario CriaFuncionario(double salario, int nroDependentes, int nroPresencas, int nroDiasNaoUteis, int nroFaltas, int nroAfastamentos) { FichaPonto ficha = new FichaPonto(); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Presenca, nroPresencas); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.DiaNaoUtil, nroDiasNaoUteis); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Falta, nroFaltas); PayrollTestUtils.AddPontos(ficha, Ponto.EstadoPonto.Afastamento, nroAfastamentos); return new Funcionario(0, salario, "", "", nroDependentes, ficha, 0, Convert.ToDateTime("1/1/2007"), 0, 0); } } } 7.5 PROJETO PAYROLLINTERFACE PayrollInterfaceController.cs using using using using using using using System; System.Collections; System.IO; System.Xml; PayrollCalc; PayrollCalc.Regras; PayrollCalc.Exportacao; namespace PayrollInterface { public class PayrollInterfaceController { private ProcessadorDeFolha processador = new ProcessadorDeFolha(); private Folha folha = null; private Funcionario[] funcionarios = null; private void LoadFuncionarios() { ArrayList funcs = new ArrayList(); XmlTextReader reader = new XmlTextReader("funcionarios.xml"); int matricula = 0; double salario = 0; string nome = ""; string cpf = ""; int nroDependentes = 0; 169 int qtdFaltas = 0; int inicioFeriasNoMesCorrente = 0; int fimFeriasNoMesCorrente = 0; DateTime dataAdmissao = Convert.ToDateTime(null); FichaPonto fichaPonto = null; DateTime data = Convert.ToDateTime(null); Ponto.EstadoPonto estado = Ponto.EstadoPonto.Presenca; DateTime entradaTurnoEsperado = Convert.ToDateTime(null); DateTime saidaTurnoEsperado = Convert.ToDateTime(null); DateTime entradaTurnoRealizado = Convert.ToDateTime(null); DateTime saidaTurnoRealizado = Convert.ToDateTime(null); DateTime entradaIntervaloEsperado = Convert.ToDateTime(null); DateTime saidaIntervaloEsperado = Convert.ToDateTime(null); DateTime entradaIntervaloRealizado = Convert.ToDateTime(null); DateTime saidaIntervaloRealizado = Convert.ToDateTime(null); DateTime entradaHoraExtra = Convert.ToDateTime(null); DateTime saidaHoraExtra = Convert.ToDateTime(null); try { while (reader.Read()) { if ((reader.Name != "funcionarios") && ((reader.NodeType == XmlNodeType.Element) || (reader.NodeType == XmlNodeType.EndElement))) { if (reader.Name == "funcionario") { if (reader.NodeType == XmlNodeType.EndElement) { funcs.Add(new Funcionario(matricula, salario, nome, cpf, nroDependentes, fichaPonto, qtdFaltas, dataAdmissao, inicioFeriasNoMesCorrente, fimFeriasNoMesCorrente)); } else { matricula = Convert.ToInt32(reader.GetAttribute("matricula")); salario = 0; nome = ""; cpf = ""; nroDependentes = 0; qtdFaltas = 0; dataAdmissao = Convert.ToDateTime(null); inicioFeriasNoMesCorrente = 0; fimFeriasNoMesCorrente = 0; fichaPonto = null; } } else if (reader.Name == "fichaPonto") { if (reader.NodeType == XmlNodeType.Element) { fichaPonto = new FichaPonto(); } } else if (reader.Name == "ponto") { if (reader.NodeType == XmlNodeType.EndElement) { fichaPonto.AddPonto(new Ponto(estado, entradaTurnoEsperado, 170 saidaTurnoEsperado, entradaTurnoRealizado, saidaTurnoRealizado, entradaIntervaloEsperado, saidaIntervaloEsperado, entradaIntervaloRealizado, saidaIntervaloRealizado, entradaHoraExtra, saidaHoraExtra)); } else { data = Convert.ToDateTime(reader.GetAttribute("data")); estado = Ponto.EstadoPonto.Presenca; entradaTurnoEsperado = Convert.ToDateTime(null); saidaTurnoEsperado = Convert.ToDateTime(null); entradaTurnoRealizado = Convert.ToDateTime(null); saidaTurnoRealizado = Convert.ToDateTime(null); entradaIntervaloEsperado = Convert.ToDateTime(null); saidaIntervaloEsperado = Convert.ToDateTime(null); entradaIntervaloRealizado = Convert.ToDateTime(null); saidaIntervaloRealizado = Convert.ToDateTime(null); entradaHoraExtra = Convert.ToDateTime(null); saidaHoraExtra = Convert.ToDateTime(null); } } else if (reader.NodeType == XmlNodeType.Element) { if (reader.Name == "salario") salario = Convert.ToDouble(reader.ReadString()); else if (reader.Name == "nome") nome = reader.ReadString(); else if (reader.Name == "cpf") cpf = reader.ReadString(); else if (reader.Name == "nroDependentes") nroDependentes = Convert.ToInt32(reader.ReadString()); else if (reader.Name == "qtdFaltas")qtdFaltas = Convert.ToInt32(reader.ReadString()); else if (reader.Name == "dataAdmissao")dataAdmissao = Convert.ToDateTime(reader.ReadString()); else if (reader.Name == "inicioFeriasNoMesCorrente")inicioFeriasNoMesCorrente = Convert.ToInt32(reader.ReadString()); else if (reader.Name == "fimFeriasNoMesCorrente")fimFeriasNoMesCorrente = Convert.ToInt32(reader.ReadString()); else if (reader.Name == "estado") estado = RetornaEstado(reader.ReadString()); else if (reader.Name == "entradaTurnoEsperado") entradaTurnoEsperado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "saidaTurnoEsperado") saidaTurnoEsperado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "entradaTurnoRealizado") entradaTurnoRealizado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "saidaTurnoRealizado") saidaTurnoRealizado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "entradaIntervaloEsperado") entradaIntervaloEsperado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "saidaIntervaloEsperado") saidaIntervaloEsperado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "entradaIntervaloRealizado") entradaIntervaloRealizado = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "saidaIntervaloRealizado") saidaIntervaloRealizado = RetornaDataEHora(data, reader.ReadString()); 171 else if (reader.Name == "entradaHoraExtra") entradaHoraExtra = RetornaDataEHora(data, reader.ReadString()); else if (reader.Name == "saidaHoraExtra") saidaHoraExtra = RetornaDataEHora(data, reader.ReadString()); } } } } finally { reader.Close(); } funcionarios = (Funcionario[]) funcs.ToArray(typeof(Funcionario)); } private DateTime RetornaDataEHora(DateTime data, string hora) { DateTime dtHora = Convert.ToDateTime(hora); data = data.AddHours(dtHora.Hour); data = data.AddMinutes(dtHora.Minute); data = data.AddSeconds(dtHora.Second); return data; } private Ponto.EstadoPonto RetornaEstado(string estado) { if (estado == "P") return Ponto.EstadoPonto.Presenca; else if (estado == "F") return Ponto.EstadoPonto.Falta; else if (estado == "A") return Ponto.EstadoPonto.Afastamento; else return Ponto.EstadoPonto.DiaNaoUtil; } public void Calcular() { LoadFuncionarios(); folha = processador.Roda(funcionarios, RegraFolhaArrayFactory.GetRegras()); } public void ExportarFGTS(string caminho) { WriteFile(processador.Exporta(folha, new ExportadorFGTS()), caminho); } public void ExportarBanco(string caminho) { WriteFile(processador.Exporta(folha, new ExportadorBanco()), caminho); } public void ExportarGrafica(string caminho) { WriteFile(processador.Exporta(folha, new ExportadorGrafica()), caminho); } 172 private void WriteFile(string[] linhas, string caminho) { StreamWriter sw = new StreamWriter(caminho); try { for (int i = 0; i < linhas.Length; i++) sw.WriteLine(linhas[i]); } finally { sw.Close(); } } public Funcionario[] Funcionarios { get { return funcionarios; } } public DetalhamentoFolha GetDetalhamentoPara(int funcIndice) { return folha[funcIndice].Detalhamento; } public double GetSalarioLiquidoPara(int funcIndice) { return folha[funcIndice].SalarioLiquido; } } } fmPayrollCalc.cs using using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; PayrollCalc; namespace PayrollInterface { public class fmPayrollCalc : System.Windows.Forms.Form { private System.Windows.Forms.ListBox lbFuncionarios; private System.Windows.Forms.Panel pnlTitle; private System.Windows.Forms.Label lblTitle; private System.Windows.Forms.Button btCalcular; private System.Windows.Forms.Button btExportFGTS; private System.Windows.Forms.Button btExportGrafica; private System.Windows.Forms.Button btExportBanco; private System.ComponentModel.Container components = null; private System.Windows.Forms.SaveFileDialog saveDialog; 173 private System.Windows.Forms.TextBox txtSalLiquido; private System.Windows.Forms.TextBox txtDetalhamento; private PayrollInterfaceController controller = new PayrollInterfaceController(); public fmPayrollCalc() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.pnlTitle = new System.Windows.Forms.Panel(); this.lblTitle = new System.Windows.Forms.Label(); this.btCalcular = new System.Windows.Forms.Button(); this.lbFuncionarios = new System.Windows.Forms.ListBox(); this.btExportFGTS = new System.Windows.Forms.Button(); this.btExportGrafica = new System.Windows.Forms.Button(); this.btExportBanco = new System.Windows.Forms.Button(); this.txtSalLiquido = new System.Windows.Forms.TextBox(); this.saveDialog = new System.Windows.Forms.SaveFileDialog(); this.txtDetalhamento = new System.Windows.Forms.TextBox(); this.pnlTitle.SuspendLayout(); this.SuspendLayout(); // // pnlTitle // this.pnlTitle.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyl es.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.pnlTitle.BackColor = System.Drawing.Color.White; this.pnlTitle.Controls.Add(this.lblTitle); this.pnlTitle.ForeColor = System.Drawing.Color.Black; this.pnlTitle.Location = new System.Drawing.Point(0, 0); this.pnlTitle.Name = "pnlTitle"; this.pnlTitle.Size = new System.Drawing.Size(480, 64); this.pnlTitle.TabIndex = 0; // // lblTitle // 174 this.lblTitle.Font = new System.Drawing.Font("Verdana", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); this.lblTitle.ForeColor = System.Drawing.Color.Navy; this.lblTitle.Location = new System.Drawing.Point(8, 8); this.lblTitle.Name = "lblTitle"; this.lblTitle.Size = new System.Drawing.Size(440, 48); this.lblTitle.TabIndex = 0; this.lblTitle.Text = "Payroll Calculation"; // // btCalcular // this.btCalcular.Location = new System.Drawing.Point(8, 72); this.btCalcular.Name = "btCalcular"; this.btCalcular.Size = new System.Drawing.Size(144, 23); this.btCalcular.TabIndex = 1; this.btCalcular.Text = "Calcular"; this.btCalcular.Click += new System.EventHandler(this.btCalcular_Click); // // lbFuncionarios // this.lbFuncionarios.Location = new System.Drawing.Point(160, 72); this.lbFuncionarios.Name = "lbFuncionarios"; this.lbFuncionarios.Size = new System.Drawing.Size(144, 199); this.lbFuncionarios.TabIndex = 2; this.lbFuncionarios.SelectedIndexChanged += new System.EventHandler(this.lbFuncionarios_SelectedIndexChanged); // // btExportFGTS // this.btExportFGTS.Enabled = false; this.btExportFGTS.Location = new System.Drawing.Point(8, 184); this.btExportFGTS.Name = "btExportFGTS"; this.btExportFGTS.Size = new System.Drawing.Size(144, 23); this.btExportFGTS.TabIndex = 5; this.btExportFGTS.Text = "Exportar FGTS"; this.btExportFGTS.Click += new System.EventHandler(this.btExportFGTS_Click); // // btExportGrafica // this.btExportGrafica.Enabled = false; this.btExportGrafica.Location = new System.Drawing.Point(8, 216); this.btExportGrafica.Name = "btExportGrafica"; this.btExportGrafica.Size = new System.Drawing.Size(144, 23); this.btExportGrafica.TabIndex = 6; this.btExportGrafica.Text = "Exportar Gráfica"; this.btExportGrafica.Click += new System.EventHandler(this.btExportGrafica_Click); // // btExportBanco // this.btExportBanco.Enabled = false; this.btExportBanco.Location = new System.Drawing.Point(8, 248); this.btExportBanco.Name = "btExportBanco"; this.btExportBanco.Size = new System.Drawing.Size(144, 23); this.btExportBanco.TabIndex = 7; this.btExportBanco.Text = "Exportar Banco"; this.btExportBanco.Click += new System.EventHandler(this.btExportBanco_Click); 175 // // txtSalLiquido // this.txtSalLiquido.Location = new System.Drawing.Point(312, 251); this.txtSalLiquido.Name = "txtSalLiquido"; this.txtSalLiquido.ReadOnly = true; this.txtSalLiquido.Size = new System.Drawing.Size(160, 20); this.txtSalLiquido.TabIndex = 8; this.txtSalLiquido.Text = ""; // // txtDetalhamento // this.txtDetalhamento.Location = new System.Drawing.Point(312, 72); this.txtDetalhamento.Multiline = true; this.txtDetalhamento.Name = "txtDetalhamento"; this.txtDetalhamento.ReadOnly = true; this.txtDetalhamento.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; this.txtDetalhamento.Size = new System.Drawing.Size(160, 176); this.txtDetalhamento.TabIndex = 3; this.txtDetalhamento.Text = ""; // // fmPayrollCalc // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(480, 278); this.Controls.Add(this.txtSalLiquido); this.Controls.Add(this.btExportBanco); this.Controls.Add(this.btExportGrafica); this.Controls.Add(this.btExportFGTS); this.Controls.Add(this.txtDetalhamento); this.Controls.Add(this.lbFuncionarios); this.Controls.Add(this.btCalcular); this.Controls.Add(this.pnlTitle); this.Name = "fmPayrollCalc"; this.Text = "Payroll Calculation"; this.pnlTitle.ResumeLayout(false); this.ResumeLayout(false); } #endregion [STAThread] static void Main() { Application.Run(new fmPayrollCalc()); } private void btCalcular_Click(object sender, System.EventArgs e) { lbFuncionarios.Items.Clear(); controller.Calcular(); Funcionario[] funcionarios = controller.Funcionarios; for (int i = 0; i < funcionarios.Length; i++) lbFuncionarios.Items.Add(funcionarios[i].Nome); btExportBanco.Enabled = true; btExportFGTS.Enabled = true; btExportGrafica.Enabled = true; } 176 private void btExportFGTS_Click(object sender, System.EventArgs e) { if (saveDialog.ShowDialog() == DialogResult.OK) controller.ExportarFGTS(saveDialog.FileName); } private void btExportGrafica_Click(object sender, System.EventArgs e) { if (saveDialog.ShowDialog() == DialogResult.OK) controller.ExportarGrafica(saveDialog.FileName); } private void btExportBanco_Click(object sender, System.EventArgs e) { if (saveDialog.ShowDialog() == DialogResult.OK) controller.ExportarBanco(saveDialog.FileName); } private void lbFuncionarios_SelectedIndexChanged(object sender, System.EventArgs e) { txtDetalhamento.Text = ""; DetalhamentoFolha detalhamento = controller.GetDetalhamentoPara(lbFuncionarios.SelectedIndex); for (int i = 0; i < detalhamento.Count; i++) txtDetalhamento.Text += detalhamento[i] + "\r\n"; txtSalLiquido.Text = "R$" + controller.GetSalarioLiquidoPara(lbFuncionarios.SelectedIndex).ToStrin g("#,##0.00"); } } } 7.6 PROJETO PAYRULE (LOP) CalculoConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payrule { [ValidationState(ValidationState.Enabled)] public partial class Calculo { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] 177 private void OperacaoEnumeration(ValidationContext context) { if ((this.Operacao != "Soma") && (this.Operacao != "Subtração") && (this.Operacao != "Multiplicação") && (this.Operacao != "Divisão")) { context.LogError( "A operação só pode ser definida como: 'Soma', 'Subtração', 'Multiplicação' ou 'Divisão'.", "CALC_OP_INVALIDA", this); } } } } EscolhaConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payrule { [ValidationState(ValidationState.Enabled)] public partial class Escolha { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void IntervaloValido(ValidationContext context) { if ((this.Ate < this.De) && (this.Ate != 0)) { context.LogError( "A propriedade Ate deve ser maior do que a propriedade De, ou 0.", "ESC_INT_INVALIDO", this); } } } } QtdDiasEstadoConstraints.cs using System; 178 using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payrule { [ValidationState(ValidationState.Enabled)] public partial class QtdDiasEstado { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void EstadoEnumeration(ValidationContext context) { if ((this.Estado != "Presença") && (this.Estado != "Falta") && (this.Estado != "Afastamento") && (this.Estado != "Dia Não Útil")) { context.LogError( "O estado só pode ser definido como: 'Presença', 'Falta', 'Afastamento' ou 'Dia Não Útil'.", "QTD_DIAS_EST_INVALIDO", this); } } } } SalarioBrutoConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payrule { [ValidationState(ValidationState.Enabled)] public partial class SalarioBruto { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void GranuralidadeEnumeration(ValidationContext context) { if ((this.Granuralidade != "Ano") && (this.Granuralidade != "Mês") && 179 (this.Granuralidade != "Dia") && (this.Granuralidade != "Hora") && (this.Granuralidade != "Minuto")) { context.LogError( "A granuralidade só pode ser definida como: 'Ano', 'Mês', 'Dia', 'Hora' ou 'Minuto'.", "SAL_GRAN_INVALIDA", this); } } } } 180 DslDefinition.dsl 181 Payrule.ttimport <#@ import namespace="System.Collections" #> <# ArrayList funcoes = new ArrayList(); #> <# ArrayList detalhamentos = new ArrayList(); #> using System; using PayrollCalc; namespace PayrollCalc.Regras { public partial class <#= this.Regra.Nome #>: RegraFolha { public double CalculaERetornaSaldo(Funcionario funcionario, DetalhamentoFolha detalhamento) { <# String entradaSaldo = GetValorEntrada(this.Regra.Saldo.Entrada, funcoes, detalhamentos); foreach(Detalhe detalhe in this.Regra.Detalhes) { foreach(Entrada entrada in detalhe.Entradas) { if ((entrada.VaiParaSaldo == null) && (entrada.VaiParaCalculo == null) && (entrada.VaiParaCalculosComoSegunda.Count == 0) && (entrada.VaiParaEscolhas.Count == 0) && (entrada.VaiParaEscolhasComoValor.Count == 0)) { funcoes.Add(" public double AdicionaDetalhamento" + funcoes.Count.ToString() + "(double valor, DetalhamentoFolha detalhamento)\n" + " {\n" + " detalhamento.AddItem(\"" + GetDetalheValor(detalhe.Valor) + "\");\n" + " return valor;\n" + " }"); detalhamentos.Add(detalhe); #> AdicionaDetalhamento<#=(funcoes.Count - 1).ToString() + "(" + GetValorEntrada(entrada, funcoes, detalhamentos) + ", detalhamento);"#> <# } } } #> return <#=entradaSaldo#>; }<# foreach (String funcao in funcoes) {#> <#="\n" + funcao#><# }#> } } <#+ private String GetValorEntrada(Entrada entrada, ArrayList funcoes, ArrayList detalhamentos) { String result = ""; if (entrada is Valor) result = (entrada as Valor).Numero.ToString().Replace(',', '.'); 182 else if (entrada is MesAtual) result = "funcionario.FichaPonto[0].EntradaTurnoRealizado.Month"; else if (entrada is SalarioBruto) { if ((entrada as SalarioBruto).Granuralidade == "Ano") result = "funcionario.Salario * 12"; else if ((entrada as SalarioBruto).Granuralidade == "Mês") result = "funcionario.Salario"; else if ((entrada as SalarioBruto).Granuralidade == "Dia") result = "funcionario.SalarioDia"; else if ((entrada as SalarioBruto).Granuralidade == "Hora") result = "funcionario.SalarioHora"; else result = "funcionario.SalarioHora / 60"; } else if (entrada is QtdHorasExtrasPeriodo) result = "funcionario.FichaPonto.GetQtdHENoPeriodo(Convert.ToDateTime(\"" + (entrada as QtdHorasExtrasPeriodo).De + "\"), Convert.ToDateTime(\"" + (entrada as QtdHorasExtrasPeriodo).Ate + "\"))"; else if (entrada is QtdDiasEstado) { result = "Convert.ToDouble("; if ((entrada as QtdDiasEstado).Estado == "Presença") result += "funcionario.FichaPonto.ContaDiasEmEstado(Ponto.EstadoPonto.Presenca)" ; else if ((entrada as QtdDiasEstado).Estado == "Dia Não Útil") result += "funcionario.FichaPonto.ContaDiasEmEstado(Ponto.EstadoPonto.DiaNaoUtil )"; else result += "funcionario.FichaPonto.ContaDiasEmEstado(Ponto.EstadoPonto." + (entrada as QtdDiasEstado).Estado + ")"; result += ")"; } else if (entrada is QtdMesesTrabalhados) result = "funcionario.GetQtdMesesTrabalhadosSemAno(funcionario.FichaPonto[0].En tradaTurnoRealizado.Month)"; else if (entrada is QtdDiasFeriasMes) result = "funcionario.DiasDeFerias"; else if (entrada is QtdFilhos) result = "funcionario.NroDependentes"; else if (entrada is QtdFaltas) result = "funcionario.QtdFaltas"; else if (entrada is Calculo) result = "(" + GetValorEntrada((entrada as Calculo).Entrada, funcoes, detalhamentos) + " " + GetCalculoOperator((entrada as Calculo).Operacao) + " " + GetValorEntrada((entrada as Calculo).SegundaEntrada, funcoes, detalhamentos) + ")"; else if (entrada is ResultadoEscolha) result = GetCodigoParaResultadoEscolha(entrada as ResultadoEscolha, funcoes, detalhamentos); else result = ""; foreach(Detalhe detalhe in entrada.Detalhes) { if (detalhamentos.IndexOf(detalhe) < 0) { 183 funcoes.Add(" public double AdicionaDetalhamento" + funcoes.Count.ToString() + "(double valor, DetalhamentoFolha detalhamento)\n" + " {\n" + " detalhamento.AddItem(\"" + GetDetalheValor(detalhe.Valor) + "\");\n" + " return valor;\n" + " }"); detalhamentos.Add(detalhe); result = "AdicionaDetalhamento" + (funcoes.Count - 1).ToString() + "(" + result + ", detalhamento)"; } } return result; } private String GetCodigoParaResultadoEscolha(ResultadoEscolha resultado, ArrayList funcoes, ArrayList detalhamentos) { String corpoFuncao = ""; String elseWord = ""; String limiteSuperior; ArrayList variaveisEntrada = new ArrayList(); int indiceVariavel; foreach(Escolha escolha in resultado.Escolhas) { limiteSuperior = ""; indiceVariavel = variaveisEntrada.IndexOf(escolha.Entrada); if (indiceVariavel < 0) { variaveisEntrada.Add(escolha.Entrada); indiceVariavel = variaveisEntrada.Count - 1; } if (escolha.Ate != 0) limiteSuperior = " && (var" + indiceVariavel.ToString() + " <= " + escolha.Ate.ToString().Replace(',', '.') + ")"; corpoFuncao += " " + elseWord + "if ((var" + indiceVariavel.ToString() + " >= " + escolha.De.ToString().Replace(',', '.') + ")" + limiteSuperior + ")\n"; corpoFuncao += " {\n"; if (variaveisEntrada.IndexOf(escolha.EntradaValor) > -1) corpoFuncao += " return var" + variaveisEntrada.IndexOf(escolha.EntradaValor).ToString() + ";\n"; else corpoFuncao += " return " + GetValorEntrada(escolha.EntradaValor, funcoes, detalhamentos) + ";\n"; corpoFuncao += " }\n"; elseWord = "else "; } corpoFuncao corpoFuncao corpoFuncao corpoFuncao += += += += " " " " " + elseWord + "\n"; {\n"; return 0;\n"; }\n"; funcoes.Add(corpoFuncao + " }"); int indiceFuncao = funcoes.Count - 1; 184 corpoFuncao = "\n" + corpoFuncao; for(int i = 0; i < variaveisEntrada.Count; i++) { corpoFuncao = " double var" + i.ToString() + " = " + GetValorEntrada(variaveisEntrada[i] as Entrada, funcoes, detalhamentos) + ";\n" + corpoFuncao; } corpoFuncao = " public double VerificaEscolha" + indiceFuncao.ToString() + "(Funcionario funcionario, DetalhamentoFolha detalhamento)\n {\n" + corpoFuncao; funcoes[indiceFuncao] = corpoFuncao + " }"; return "VerificaEscolha" + indiceFuncao.ToString() + "(funcionario, detalhamento)"; } private char GetCalculoOperator(string operacao) { if (operacao == "Soma") return '+'; else if (operacao == "Subtração") return '-'; else if (operacao == "Multiplicação") return '*'; else return '/'; } private String GetDetalheValor(String mensagem) { return mensagem.Replace("$VALOR$", "R$\" + valor.ToString(\"#,##0.00\") + \""); } #> RegraDecimoTerceiro.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraDecimoTerceiro.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> RegraEstados.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraEstados.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> 185 RegraFerias.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraFerias.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> RegraFGTS.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraFGTS.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> namespace PayrollCalc.Regras { public partial class RegraFGTS { public double RetornaValorFGTS(double salario) { return salario * 0.08; } } } RegraHE.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraHE.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> RegraINSS.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraINSS.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> 186 RegraINSS.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraIRF.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> RegraSalarioFamilia.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraSalarioFamilia.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #> RegraFolhaArrayFactory.tt using System; namespace PayrollCalc.Regras { public class RegraFolhaArrayFactory { public static RegraFolha[] GetRegras() { //Devolve um array com todas as regras que incidem sobre o cálculo da folha return new RegraFolha[] { new RegraFGTS(), new RegraHE(), new RegraINSS(), new RegraSalarioFamilia(), new RegraIRF(), new RegraFerias(), new RegraDecimoTerceiro(), new RegraEstados(), new RegraSindicato() }; } } } 187 RegraDecimoTerceiro.pr 188 RegraEstados.pr 189 RegraFerias.pr 190 RegraFGTS.pr 191 RegraHE.pr 192 RegraINSS.pr 193 RegraIRF.pr 194 RegraSalarioFamilia.pr 7.7 PROJETO PAYXPORT (LOP) ArquivoConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payxport { [ValidationState(ValidationState.Enabled)] public partial class Arquivo { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void OrdemDasLinhasFuncionario(ValidationContext context) { try { 195 this.GetLinhasOrdenadas(GetLinhas("Funcionário")); } catch (Exception) { context.LogError( "A ordem das linhas do tipo Funcionário deve começar em '1' e ser contínua. Não podem haver números repetidos.", "REGRA_ORDEM_LINHA", this); } } [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void OrdemDasLinhasDetalhe(ValidationContext context) { try { this.GetLinhasOrdenadas(GetLinhas("Detalhe")); } catch (Exception) { context.LogError( "A ordem das linhas do tipo Detalhe deve começar em '1' e ser contínua. Não podem haver números repetidos.", "REGRA_ORDEM_LINHA", this); } } [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void TamanhoDasLinhas(ValidationContext context) { Int16 tamanho = -1; foreach (Linha linha in this.Linhas) { if (tamanho == -1) { tamanho = linha.Tamanho; } else if (linha.Tamanho != tamanho) { context.LogWarning( "Nem todas as linhas têm o mesmo tamanho.", "REGRA_TAM_LINHAS", this); break; } } } 196 } } CampoConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payxport { [ValidationState(ValidationState.Enabled)] public partial class Campo { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void TamanhoDoCampo(ValidationContext context) { if (this.Tamanho < 1) context.LogError( "O tamanho do campo não pode ser menor do que 1.", "CAMPO_TAMANHO", this); } } } FuncionarioConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payxport { [ValidationState(ValidationState.Enabled)] public partial class Funcionario { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void PropriedadeEnumeration(ValidationContext context) 197 { if ((this.Propriedade != "Matrícula") && (this.Propriedade != "Nome") && (this.Propriedade != "CPF") && (this.Propriedade != "Salário")) context.LogError( "A Propriedade do Funcionario só pode ser definida como 'Matrícula', 'Nome, 'CPF' ou 'Salário'.", "FUNC_PROP", this); } } } LinhaConstraints.cs using using using using System; Microsoft.VisualStudio.Modeling; System.Collections.ObjectModel; Microsoft.VisualStudio.Modeling.Validation; namespace PayrollCalc.Payxport { [ValidationState(ValidationState.Enabled)] public partial class Linha { [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void OrdemDosCampos(ValidationContext context) { try { this.GetCamposOrdenados(); } catch (Exception) { context.LogError( "A ordem dos campos deve começar em '1' e ser contínua. Não podem haver números repetidos.", "LINHA_ORDEM_CAMPO", this); } } [ValidationMethod ( // These values select which events invoke the method. ValidationCategories.Open | ValidationCategories.Save | ValidationCategories.Menu ) ] private void TipoEnumeration(ValidationContext context) 198 { if (this.Tipo == "Funcionário") { foreach (Campo campo in this.Campos) { if (campo is Detalhe) { context.LogError( "Não podem haver campos Detalhe em linhas do tipo Funcionário", "LINHA_CAMPO_EM_LINHA_ERRADA", this, campo); } } } else if (this.Tipo == "Detalhe") { foreach (Campo campo in this.Campos) { if ((campo is Funcionario) || (campo is SalarioLiquido)) { context.LogError( "Não podem haver campos Funcionario e SalarioLiquido em linhas do tipo Detalhe", "LINHA_CAMPO_EM_LINHA_ERRADA", this, campo); } } } else { context.LogError( "O tipo de linha só pode ser igual a 'Funcionario' ou 'Detalhe'", "LINHA_TIPO", this); } } } } ArquivoProperties.cs using System; using System.Collections; using System.Text; namespace PayrollCalc.Payxport { public partial class Arquivo { public ArrayList GetLinhas(String tipo) { ArrayList result = new ArrayList(); foreach (Linha linha in this.Linhas) 199 { if (linha.Tipo == tipo) { result.Add(linha); } } return result; } public ArrayList GetLinhasOrdenadas(String tipo) { return GetLinhasOrdenadas(GetLinhas(tipo)); } public ArrayList GetLinhasOrdenadas(ArrayList linhas) { ArrayList result = new ArrayList(); for (int i = 1; i <= linhas.Count; i++) { bool found = false; foreach (Linha linha in linhas) { if (linha.Ordem == i) { result.Add(linha); found = true; break; } } if (!found) { throw new Exception("Ordem das linhas do tipo inválida."); } } return result; } } } CampoCalculation.cs using System; using System.Collections; using System.Text; namespace PayrollCalc.Payxport { public partial class Campo { private Int16 GetDeValue() { 200 Int16 count; try { ArrayList campos = this.Linha.GetCamposOrdenados(); count = 0; foreach (Campo campo in campos) { if (campo == this) break; count += campo.Tamanho; } count += Convert.ToInt16(1); } catch (Exception) { count = 0; } return count; } private Int16 GetAteValue() { return Convert.ToInt16(GetDeValue() + this.Tamanho - 1); } } } LinhaCalculation.cs using System; using System.Collections; using System.Text; namespace PayrollCalc.Payxport { public partial class Linha { private Int16 GetTamanhoValue() { Int16 tamanho = 0; foreach (Campo campo in this.Campos) { tamanho += campo.Tamanho; } return tamanho; } } } 201 LinhaProperties.cs using System; using System.Collections; using System.Text; namespace PayrollCalc.Payxport { public partial class Linha { public ArrayList GetCamposOrdenados() { ArrayList result = new ArrayList(); for (int i = 1; i <= this.Campos.Count; i++) { bool found = false; foreach (Campo campo in this.Campos) { if (campo.Ordem == i) { result.Add(campo); found = true; break; } } if (!found) { throw new Exception("Ordem dos campos inválida."); } } return result; } } } 202 DslDefinition.dsl Payxport.ttimport <#@ import namespace="System.Collections" #> <#@ include file="..\CustomCode\CustomValues.ttcustom" #> using System; using System.Collections; namespace PayrollCalc.Exportacao { public class <#=this.Arquivo.Nome#>: Exportador { public string[] Exporta(Folha folha) { 203 ArrayList resultado = new ArrayList(); FolhaItem item; string linha; <# string tipoFunc = "Funcion" + ((char) 225) + "rio"; ArrayList linhasFunc = this.Arquivo.GetLinhasOrdenadas(tipoFunc); ArrayList linhasDet = this.Arquivo.GetLinhasOrdenadas("Detalhe"); #> for (int i = 0; i < folha.Count; i++) { item = folha[i]; <# foreach (Linha linha in linhasFunc) { EscreveCodigoLinha(linha, ""); }#> <# if (linhasDet.Count > 0) { #> for (int j = 0; j < item.Detalhamento.Count; j++) { <# foreach (Linha linha in linhasDet) { EscreveCodigoLinha(linha, " "); } #> } <# } #> } return (string[]) resultado.ToArray(typeof(string)); } } } <#+ public void EscreveCodigoLinha(Linha linha, String ident) { WriteLine(ident + " linha = \"\";"); foreach (Campo campo in linha.GetCamposOrdenados()) { WriteLine(ident + " linha += " + GetValorCampo(campo) + ";"); } WriteLine(ident + " resultado.Add(linha);\n"); } public String GetValorCampo(Campo campo) { string result = "ExportacaoUtils.CortaString(\n bool campoFloat = false; "; if (campo is Texto) { result += '"' + (campo as Texto).Valor + '"'; } else if (campo is Funcionario) { result += "item.Funcionario."; if ((campo as Funcionario).Propriedade == "Matr" + ((char) 237) + "cula") { result += "Matricula.ToString()"; 204 } else if ((campo as Funcionario).Propriedade == "Nome") { result += "Nome"; } else if ((campo as Funcionario).Propriedade == "CPF") { result += "Cpf"; } else if ((campo as Funcionario).Propriedade == "Sal" + ((char) 225) + "rio") { result += "Salario"; campoFloat = true; } } else if (campo is Detalhe) { result += "item.Detalhamento[j].ToString()"; } else if (campo is SalarioLiquido) { result += "item.SalarioLiquido"; campoFloat = true; } else if (campo is Custom) { result += GetCustomValue(campo, ref campoFloat); } if (campoFloat) { result += ".ToString(\"0.00\")"; } result += ".PadLeft(" + campo.Tamanho.ToString(); if (campoFloat) { result += " , '0')"; } else { result += " , ' ')"; } if (campoFloat) { result += ".Replace(',', '.')"; } result += ", " + campo.Tamanho.ToString() + ")"; return result; } #> ExportadorBanco.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payxport processor="PayxportDirectiveProcessor" requires="fileName='ExportadorBanco.px'" #> <#@ include file="..\Base\Payxport.ttimport" #> 205 ExportadorFGTS.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payxport processor="PayxportDirectiveProcessor" requires="fileName='ExportadorFGTS.px'" #> <#@ include file="..\Base\Payxport.ttimport" #> ExportadorGrafica.tt <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTra nsformation" debug="true"#> <#@ Payxport processor="PayxportDirectiveProcessor" requires="fileName='ExportadorGrafica.px'" #> <#@ include file="..\Base\Payxport.ttimport" #> ExportacaoUtils.tt using System; namespace PayrollCalc.Exportacao { public class ExportacaoUtils { public static string CortaString(string source, int max) { if (source.Length > max) return source.Remove(0, source.Length - max); else return source; } } } CustomValues.ttcustom <#+ public String GetCustomValue(Campo campo, ref bool isCampoFloat) { isCampoFloat = false; if ((campo as Custom).Nome == "FGTS") { isCampoFloat = true; 206 return "(new PayrollCalc.Regras.RegraFGTS()).RetornaValorFGTS(item.Funcionario.Sala rio)"; } else { return ""; } } #> ExportadorBanco.px ExportadorFGTS.px ExportadorGrafica.px Programação Orientada a Linguagens Guilherme Pires, Gustavo Royer Chaurais Universidade Federal de Santa Catarina (UFSC) – Florianópolis, SC – Brasil [email protected], [email protected] Abstract. With the objective to make the computer understands our intention instead of showing it a sequence of instructions to be followed by the program, studious had proposed the Language Oriented Programming. This paper aims at to analyze the effectiveness and the efficiency of such methodology in relation to the older ones. For this a project is written using of the proposed techniques and positives and negatives points are raised. Resumo. Com o objetivo de fazer com que o computador entenda a nossa intenção em vez de termos que mostrar a ele uma seqüência de instruções a serem seguidas pelo programa, estudiosos propuseram a Programação Orientada a Linguagens. Este artigo visa analisar a real eficácia e a eficiência de tal metodologia em relação às outras mais antigas. Para isto um projeto é escrito utilizando-se das técnicas propostas e pontos positivos e negativos são levantados. 1. Introdução Com o passar do tempo podemos perceber que o número de programadores vem aumentando consideravelmente. Isso se deve a cada vez que surge uma nova linguagem ou novo estilo de programação torna-se mais fácil o aprendizado e utilização dos mesmos. Porém existe ainda uma barreira que impede um maior crescimento, e transpô-la tem sido o objetivo de diversas pessoas da área tecnológica. Esta barreira é fazer com que o computador entenda exatamente o que estamos pensando, ao invés de nós termos que escrever o que desejamos de uma maneira, diferente, a qual o computador entenda (DMITRIEV, 2005). Com isto diminuiríamos o tempo de programação já que não teríamos que transcrever o que desejamos em algoritmos, e também muito mais pessoas poderiam programar. Com os paradigmas declarativos já se procurava resolver este problema, representados, principalmente, pelo PROLOG. Ao invés de utilizarmos variáveis, repetições e condições, passaríamos a utilizar fatos e regras lógicas que representam o domínio relacional do problema que desejamos resolver (APPLEBY,1991). Porém, muito provavelmente, devido a sua complexidade, ao conhecimento prévio em lógica necessário e à escassez de ferramentas as linguagens declarativas tenham sido restringidas ao ambiente acadêmico e não se tornaram um padrão para o desenvolvimento de software. Porém a busca pela simplificação do desenvolvimento e softwares não parou e diversos estilos e tendências de programação foram surgindo, entre eles: Intentional Programming, Software Factories, Generative Programming e Model Drivem Architecture. Além de trazer de volta conceitos antigos as Domain Specific Languages (DSLs) (DMITRIEV, 2005). Com base nestes conceitos e estilos, Sergey Dmitriev (2005) propõe a criação de um novo paradigma para substituir a Orientação a Objetos. Ele propõe a Programação Orientada a Linguagens (Language Oriented Programming), porém diferentemente dos paradigmas declarativos ela visa a facilidade de compreensão do código fonte, fazendo com que mais pessoas tenham condições de escrever os seus programas sem ter que transcrevê-los para algoritmos. A partir do artigo citado e de estudos nas tendências da programação, realizaremos uma validação da proposta de Dmitriev (2005). Para isto desenvolveremos um projeto utilizando o paradigma orientado a objetos e posteriormente desenvolveremos o mesmo sistema utilizando a orientação a linguagens. Finalmente, realizaremos uma análise comparativa entre os dois paradigmas, buscando encontrar os pontos positivos que comprovem a eficiência do paradigma orientado a linguagens. Contando com a motivação de podermos contribuir com algo bastante novo para a área do desenvolvimento de software, além de termos grande interesse no assunto, nosso objetivo geral resume-se a analisar o paradigma proposto, verificando assim a possibilidade de ser utilizado em projetos reais. Finalmente, nosso objetivo específico será a sua comparação com o paradigma atualmente mais utilizado no mercado, a Orientação a Objetos. 2. Fundamentos Teóricos 2.1 Intentional Programming Nascida na década de 1990, nos laboratórios da Microsoft Research, a Intentional Programming quebra alguns conceitos básicos da programação tradicional, como, por exemplo, o da utilização de arquivos texto para a representação de códigos fonte. O primeiro artigo escrito sobre o tema foi do pesquisador da empresa, Charles Simonyi (1995). Segundo Simonyi (1995), as linguagens de programação atuais não nos permitem construir diversas coisas que, muitas vezes, nem nos damos conta por estarmos tão acostumados. Um programa é criado com base em Intentions, e este é o porquê do nome “Intentional Programming” (SIMONYI, 1995). Outra definição bastante importante na Intentional Programming são as Identities. O conceito de identidade na programação intencional vai além de um simples nome, seria realmente um identificador. Deste modo, poderíamos ter “traduções” diferentes para uma mesma entidade: uma em inglês, outra em português e assim por diante. Estaríamos sempre nos referindo ao mesmo conceito sem problemas de diferenças de nomes (SIMONYI, 1995). Uma das grandes diferenças entre a programação tradicional e a Intentional Programming é a de que, na última, todas as declarações de conceitos ficariam em um mesmo lugar. Estas seriam posteriormente referenciadas, e não redeclaradas. 2.2 Generative Programming Generative Programming é um novo paradigma de programação que mescla técnicas de orientação a objetos e engenharia de domínio (CZARNECKI, EISENECKER, & STEYAERT, 1997). Os objetivos da Generative Programming, basicamente, consistem em aumentar a eficiência do desenvolvimento de softwares, aumentar a reusabilidade e a adaptabilidade do código, melhorar o controle da complexidade do sistema e gerenciar um grande número de variáveis (CZARNECKI, EISENECKER, & STEYAERT, 1997). Para alcançar os seus objetivos a Generative Programming se baseia em alguns fundamentos, entre eles: a separação dos domínios do sistema, implementação de modelo aberta para que se possa ter acesso à estratégia de implementação de cada componente, propagação de aspectos para reduzir a redundância, entre outros. 2.3 Software Factories Mais um conceito amplamente estudado nos laboratórios da Microsoft Research, as Software Factories reduzem drasticamente o tempo do desenvolvimento do projeto, baseando-se no seguinte princípio: automatizar tudo o que é possível (GREENFIELD, 2004). Para podermos personalizar somente o necessário e as tarefas “braçais” serem executadas de forma automática, a primeira solução a se pensar é componentização. Vários módulos de software trabalhando conjuntamente que, personalizados, produziriam o resultado desejado para cada situação. Outro conceito muito importante que também está ligado a software factories são os templates. Através destes conseguimos construir rapidamente nossas aplicações, pois contamos com artefatos prontos e personalizáveis(GREENFIELD, 2004). Uma arquitetura que está intimamente relacionada ao conceito em questão é a Model-Driven Architecture, na qual, criamos o modelo e o próprio sistema gera o código correspondente e faz o mapeamento dos objetos para a persistência em um local de armazenamento de dados. 3. DSLs – Domain Specific Languages Domain Specific Languages representam pequenas linguagens relacionadas a um domínio específico de um problema. São contrárias às General Pourpose Languages largamente utilizadas atualmente (FOWLER, 2006). Vamos imaginar, por exemplo, a leitura de um arquivo de remessa bancária pelo sistema que estamos projetando. Neste caso, teríamos caracteres unidos uns aos outros, sem qualquer tipo de divisor, da seguinte maneira: 00010371803200717370000000000000000000000000000000000000 10112345 LUIZ INÁCIO DA SILVA31032007000019450000 90317370000000000000000000000000000000000000000000000000 Ao pensar neste problema, seguindo os princípios da Orientação a Objetos, começaríamos a modelar nossas classes de acordo com os conceitos que bem conhecemos. Além disso, em alguma etapa do projeto, pensaríamos no algoritmo que faria a leitura dos dados da remessa. Em algum lugar de nosso código, deveríamos instruir o computador a separar os campos, lendo do arquivo somente o que necessitamos e poderíamos passar depois, perfeitamente, para um programador que ele entenderia o que está sendo feito. Se pararmos para pensar, podemos analisar que, tal entendimento só nos é familiar porque já estamos acostumados a instruir o computador ao que ele deve fazer, utilizando a sua maneira de pensar. No entanto, para os não-programadores, pensar “fingindo ser uma máquina” não é uma tarefa fácil (FOWLER, 2006). Para o segundo grupo, poderíamos dizer a mesma coisa da seguinte maneira: LER LINHA Se linha for HEADER NroBanco inicia em 2, contando 3 caracteres, DataGeracao inicia em 8, contando 8 caracteres, HoraGeracao inicia em 17, contando 4 caracteres. Se linha for REGISTRO NSR inicia em 4, contando 5 caracteres, NomeCliente inicia em 9, contando 28 caracteres, DataVencto inicia em 37, contando 8 caracteres, ValorAPagar inicia em 45, contando 8 caracteres. Neste caso, mesmo uma pessoa que não tenha contato com desenvolvimento de software entenderia o que estamos dizendo. Temos agora uma linguagem específica de domínio que acabamos de criar. Com isso, em vez de nos forçarmos a pensar da maneira que o computador executaria nosso código, faríamos o inverso, ele passaria a entender o que passa em nossa mente (FOWLER, 2006). 4. Programação Orientada a Linguagens Podemos perceber que as tendências para o futuro do desenvolvimento de software são bastante parecidas e envolvem basicamente os mesmos princípios: a automação de processos repetitivos e a facilidade na tradução da linguagem natural e do pensamento para algo inteligível pelo computador. Segundo Dmitriev (2005), linguagens de propósito geral (largamente utilizadas, atualmente) são um tanto improdutivas, pois, para utilizá-las, precisamos transcrever o que estamos pensando em algoritmos. Quanto mais especializada for a linguagem utilizada, mais rápida será a tradução e mais fácil será o entendimento. Dentre os principais itens para o desenvolvimento de software (DMITRIEV, 2005), destacam-se: • • Tempo para implementar idéias: podemos pegar, por exemplo, a confecção de diagramas da Orientação a Objetos. Esses diagramas são fundamentais para que entendamos o funcionamento interno do programa, no entanto, não expressam a realidade como ela é. Precisamos de algum tempo para entender e compreender as relações para imaginar o funcionamento do programa. Entendimento e manutenibilidade de código existente: entender código já existente, escrito por nós mesmos ou por outra pessoa não é uma tarefa • simples. Precisamos traduzir mentalmente o quê aquele algoritmo está tentando fazer para o contexto de alto nível representado pelo problema. Curva de aprendizado do problema: uma das poucas maneiras de se estender uma linguagem orientada a objetos é utilizando bibliotecas (class libraries). No entanto, essas bibliotecas geralmente são expressas em um nível bastante baixo e bem distante do conceitual. Isso introduz problemas como a curva de aprendizado para entender a comunicação e o desenvolvimento utilizando-se das bibliotecas inseridas. Fazendo frente a todos estes problemas e reunindo as tendências já apresentadas, Dmitriev (2005) sugere a criação de um novo paradigma, a programação orientada a linguagens (LOP – Language Oriented Programming). Nesse novo jeito de se desenvolver software, utilizaríamos várias Domain Specific Languages em conjunto. Uma para cada problema a ser solucionado. Caso essa linguagem ainda não exista, criamos uma e utilizamos no projeto. A intenção é promover a reutilização de linguagens de programação de uso específico. Uma linguagem, no paradigma Language Oriented Programming é dividida em 3 partes principais (DMITRIEV, 2005): • • • Estrutura o Sintaxe abstrata da linguagem o Conceitos suportados o Como esses conceitos podem ser organizados. Editor o Sintaxe concreta o Edição/Representação (em forma de texto, árvore, desenhos, estrutura de marcação). Semântica o Como deve ser interpretada o Como deve gerar o executável. Desta maneira, teríamos linguagens específicas para cada domínio de problema e conseguiríamos usufruir dos resultados almejados até o momento. 5. Estudo de Caso Com a finalidade de exemplificar a utilização da Language Oriented Programming na prática e analisar seus prós e contras, faremos uma comparação entre um projeto construído seguindo-se os princípios da Orientação a Objetos, e outro, baseado no paradigma proposto. Para que possamos realizar a comparação optamos por desenvolver um sistema para o cálculo da folha de pagamento de uma empresa. Esta aplicação possuirá uma quantidade razoável de regras de negócio, o que facilitará a compreensão do exemplo. Utilizaremos de diversos conceitos utilizados no mercado em geral. O fluxo principal consistirá em analisar cada funcionário, percorrendo uma lista com os seus dias trabalhados, calculando, assim, seu salário mais os seus descontos e acréscimos. Para a especificação do projeto não utilizaremos nenhuma notação formal, pois segundo Dmitriev (2005), ao utilizarmos notações formais, como a UML (Unified Modeling Language), estamos tendo o trabalho de abstrair o que queremos em modelos orientados a objetos, o que pode não representar legivelmente a solução para o problema. Por isso faremos como se estivéssemos explicando o sistema para o programador que irá desenvolvê-lo. 5.1 Escopo do Projeto Como nosso projeto visa o cálculo da folha de pagamento dos funcionários de uma empresa, o mesmo será alimentado por uma lista de funcionários e suas respectivas fichas de registro de freqüência, onde as informações contidas serão: dados pessoais do funcionário e dados dos dias trabalhados no período em questão. O sistema também implementará diversas regras para realizar o cálculo dos descontos e acréscimos ao salário do funcionário. Por exemplo, quando o funcionário possuir uma falta não justificada devidamente será descontado do mesmo o salário do dia que faltou. Entra as regras, além do desconto das faltas, estão: cálculo de férias, desconto do IRF (Imposto de Renda sobre a Fonte), desconto do INSS, por exemplo. Além de realizar o cálculo do salário com base nas regras citadas anteriormente, o sistema também realizará a exportação de arquivos contendo as informações referentes a folha salarial do funcionário. Serão exportados três tipos de arquivo: um para a gráfica para geração do holerite, um para o banco com o valor a ser depositado na conta do funcionário e um do FGTS a ser enviado à Caixa Econômica Federal. 5.2 Solução Orientada a Objetos Para a implementação do sistema orientado a objetos optamos por utilizar a metodologia Test-Driven Development. Esta metodologia consiste em escrever os testes primeiro para depois desenvolver as classes propriamente ditas. Nenhuma classe será implementada sem que antes exista um teste para ela. Isto garante que o código estará funcionando, já que sempre que houver uma alteração os testes acusaram se ela causou um erro ou não. Optamos por ela por ser uma metodologia mais ágil e flexível que uma metodologia baseada em documentação e que, apesar disso, nos garante a qualidade do que estamos implementando. Quanto à documentação, após termos as classes funcionando corretamente, utilizaremos o Borland Developer Studio 2006 para realizarmos a engenharia reversa para gerar a documentação necessária. Para a arquitetura escolhemos o padrão Model View Controller, onde teremos que construir elementos de software específicos para tratar dados e informações provenientes de uma fonte qualquer; a interface do programa com o usuário/elemento externo; e os algoritmos que irão reger a iteração entre eles. Ainda na arquitetura, gostaríamos de uma solução bastante flexível quanto à interface com o usuário. Nosso sistema para o Cálculo de Folha de pagamento deverá ser independente a ponto de ser inserido em outro projeto como um módulo a parte, utilizando janelas e formulários totalmente redefinidos. A linguagem escolhida para o desenvolvimento foi o Visual C#, da empresa Microsoft Corporation. A escolhemos por se adaptar à arquitetura e metodologia escolhidas, por utilizar o .NET Framework o qual provê diversas facilidades para o desenvolvimento, além de levarmos em conta a nossa experiência com esta linguagem. A IDE escolhida foi o Microsoft Visual Studio 2003, que possui um compilador C#, e que também ajudará bastante no desenvolvimento da interface gráfica. Finalmente, para o desenvolvimento dos testes utilizaremos o NUnit 2.2, escolhido por ser destinado ao .NET Framework, e mais uma vez considerando a nossa experiência. Após toda análise realizada chegamos a três projetos principais: um de testes (PayrollCalcTests), um para a camada das regras de negócio (PayrollCalc) e, por fim, um projeto de interface (PayrollInterface) que é independente do projeto da regra de negócios. Como falado anteriormente, começaremos a desenvolver pelo nosso primeiro teste. Para ter a flexibilidade desejada criaremos uma única classe (ProcessadorDeFolha) para controlar todas as outras. No nosso primeiro teste instanciaremos um novo ProcessadorDeFolha, chamaremos o seu método Roda (o qual retornará um objeto do tipo Folha), e verificaremos a quantidade de itens retornados nessa folha (deve ser igual a 0). Deste modo, sabemos que os próximos passos serão: criar tais classes; declarar e implementar o método; e ver nosso primeiro teste passar. Continuamos seguindo os passos do Test-Driven Development criando os elementos à medida que são necessários e, rapidamente, chegamos à seguinte estrutura de classes: • • • • • • • • ProcessadorDeFolha – Contém um único método Roda, que recebe como parâmetro uma lista de funcionários e um conjunto de regras a serem executadas sobre cada um deles. Funcionario – Representa um funcionário com seus atributos. Ficha Ponto – Cada Funcionario terá a sua ficha ponto. Refere-se ao fechamento corrente e representa uma coleção de Pontos. Ponto – Os detalhes da FichaPonto. Cada dia é representado por um ponto e contém informações sobre o turno esperado, o turno realizado, o estado do ponto e horas extras realizadas. Folha – Resultante do método Roda do ProcessadorDeFolha, representa a folha de pagamento já calculada. Contém uma coleção de FolhaItems. FolhaItem – Os detalhes da folha. Cada item de folha possui como atributo um Funcionario e um DetalhamentoFolha. DetalhamentoFolha – Contem informações mais descritivas sobre o cálculo que fora realizado. É implementado como uma coleção de strings, nas quais temos informações sobre o resultado de cada regra. RegraFolha – Na verdade não é uma classe, mas uma Interface. Toda regra será uma classe que implementará essa interface e possuirá um único método, CalculaERetornaSaldo, que receberá um objeto funcionário e um detalhamento de folha. O saldo retornado por cada regra será somado ao salário bruto de cada Funcionario. Temos assim o salário líquido de cada um deles. O algoritmo para o cálculo da folha seria basicamente: Percorrer a lista de Funcionarios Percorrer a lista de Regras Rodar cada regra para cada Funcionario gerando um FolhaItem Retornar a Folha contendo todos os FolhaItems E para que o conjunto com as regras não tenha de ser criado manualmente a cada cálculo da folha, iremos utilizar uma Fábrica de Regras (Factory). Esta seria basicamente uma classe contendo um único método, GetRegras, que devolverá um Array de RegraFolhas. Este vetor será o conjunto padrão para os cálculos, porém, nada impede um cálculo de ser feito com uma coleção diferente de regras. Para a implementação da exportação dos arquivos continuamos seguindo o padrão de independência que aplicamos às regras de cálculo, além de continuar respeitando a metodologia adotada. Para isso adicionamos um método à classe ProcessadorDeFolha que irá devolver as linhas referentes ao conteúdo do arquivo, o qual necessitará de um Exportador, que se referirá a uma interface que possuirá apenas um método Exporta que receberá uma Folha como parâmetro e retornará um array de strings. Todas as classes referentes à exportação implementarão esta interface. Tratando a manipulação dos dados dispondo os strings nas suas posições corretas e devolvendo um array de strings correspondente ao conteúdo do arquivo. Após todas as classes implementadas, rodamos novamente os nossos testes para garantir que tudo que foi implementado está funcionando conforme esperávamos. Para o desenvolvimento do projeto de interface precisávamos apenas decidir como seria feita a entrada de dados, tendo em vista que para o restante necessitaríamos apenas chamar os métodos desenvolvidos anteriormente e exibir os seus retornos. Optamos por realizar a entrada de dados através de um arquivo XML, pois é um arquivo bastante flexível e portável, além de não termos que instalar nenhum programa na máquina do cliente. Internamente, temos para nosso projeto de Interface Gráfica uma classe representa o formulário e possui como atributo um objeto do PayrollInterfaceController. Este, por sua vez, possui uma instância ProcessadorDeFolha, chamando seus métodos quando necessário, além de responsável por carregar e salvar os arquivos de entrada e saída. que tipo de ser 5.3 Solução Orientada a Linguagens A ferramenta escolhida para a geração das DSLs foi o Microsoft DSL Tools por o mesmo ser totalmente integrado com o Visual Studio 2005, por podermos customizar o código utilizando o Visual C#, e como não tínhamos experiência optamos por ser compatível com o ambiente o qual estávamos realizando o desenvolvimento. Para que pudéssemos continuar o desenvolvimento tivemos que migrar o projeto do Visual Studio 2003 para o 2005, além de instalar a versão mais nova do NUnit, já que o Microsoft Visual Studio 2005 utiliza o .NET Framework 2.0. Após planejar bastante nosso projeto, tomando como base alguns exemplos da ferramenta e nosso conhecimento obtido até agora, decidimos por criar duas DomainSpecific Languages: 1. Payrule – Criação de regras para o cálculo da folha 2. Payxport – Criação de layouts para os arquivos de exportação DSLs vão sempre gerar código para um domínio específico. Seja para uma linguagem de programação específica (C#, Java, Deplhi...), para o próprio sistema operacional (através de um aplicativo executável), para um relatório (HTML, PDF, TXT) ou para um framework já construído. No nosso caso, nosso framework será o projeto PayrollCalc. Alteramos o projeto PayrollCalc com a finalidade de atender ao novo paradigma, separamo-lo na seguinte estrutura: • • • PayrollCalc – Classes comuns, framework para rodar as regras PayrollCalc.Regras – Projeto com as regras e a fábrica de regras PayrollCalc.Exportacao – Projeto com os exportadores para arquivos Deste modo, as linguagens Payrule e Payxport resultarão em Assemblies .NET. Estes, substituirão PayrollCalc.Regras e PayrollCalc.Exportacao, respectivamente (gerados utilizando-se o paradigma Orientado a Objetos), e poderão ser carregados pelo projeto PayrollInterface normalmente, sem diferença alguma. A parte mais difícil foi definir como seriam feitas as entradas no diagrama. Não poderíamos ter apenas uma Domain Class Funcionario com vários atributos, como havíamos pensado primeiramente, pois, no Microsoft DSL Tools, faz-se necessário relacionar um elemento ao outro, e não um elemento a uma propriedade de outro. Portanto, decidimos criar um elemento (Domain Class) para cada uma das entradas do diagrama. Os seguintes foram criados: • Salário Bruto – Será traduzido para o salário bruto do funcionário em questão. Através de uma propriedade nessa Domain Class, o usuário da linguagem vai definir a granularidade a ser utilizada: Ano, Mês, Dia, Hora ou Minuto. • Qtd. Horas Extras no Período – Retorna a quantidade de horas extras realizadas no fechamento em um determinado período horário. • Qtd. Dias em Estado – Quantidade dos dias nos estados Presença, Falta, Afastamento ou Dia Não Útil, durante o fechamento. • Qtd. Meses Trabalhados – Quantidade de meses trabalhados no ano atual. • Qtd. Dias de Férias no Mês – Quantidade de dias de férias no fechamento. • Qtd. Filhos – Quantidade de filhos. • Qtd. Faltas – Quantidade de faltas durante o ano (a ser utilizada na regra de cálculo de férias) • Mês Atual – Valor numérico representando o mês atual (1-12). • Valor – Um número especificado por uma propriedade. Nosso diagrama terá entradas e, após o cálculo, o resultado deverá ser enviado para um elemento final. Portanto, criamos outra Domain Class, cujo nome foi configurado como Saldo. Além disso, uma regra não é feita apenas de uma entrada sendo enviada para o elemento final. Percebemos que, nas regras que já havíamos implementado, havia duas ações a serem tomadas: efetuar um cálculo simples (Soma, Subtração, Multiplicação e Divisão) ou submeter a entrada a uma escolha, através de faixas de valores. Portanto, criamos mais três Domain Classes: • Cálculo – Tem como parâmetros uma entrada primária e uma entrada secundária (representando os operandos do cálculo). Além do tipo de cálculo a ser feito (operador). • Escolha – Uma entrada poderá ser ligada a vários elementos de escolha. Cada um destes terá as propriedades De e Até, que definirão uma faixa de valores. Caso Até seja configurada como 0 (zero), a condição não terá um limite superior. A Domain Class Escolha possui, além de uma entrada, um Valor relacionado. • Resultado Escolha – Um conjunto de Escolhas resulta em um Resultado Escolha com o Valor da Escolha resultante da operação. Levando em conta o fato do método principal de uma RegraFolha ser o CalculaERetornaSaldo, existe mais um detalhe que não deve ser deixado de lado: há um objeto Detalhamento, passado como parâmetro, e este deve ser preenchido com os valores corretos. Contudo, essa não foi uma tarefa complicada de se resolver. Inserimos mais uma Domain Class denominada Detalhe que possui uma propriedade Valor. Além disso, ela deve receber uma Entrada qualquer. A propriedade pode ser configurada com qualquer mensagem (String), e terá a seqüência $VALOR$ substituída pelo valor de sua Entrada. No Microsoft DSL Tools, precisamos definir também os conectores que interrelacionarão as Domain Classes. Tentamos, porém, criá-los com funções bem definidas. Seriam: • Resulta – Será utilizado para ligar uma Entrada qualquer a um componente de ação ou a um Saldo. • Entra – Será utilizado como valor para um objeto Escolha ou como entrada secundária de um Cálculo. • Detalha – Será utilizado para ligar uma Entrada a um objeto Detalhe. Neste ponto, notamos a necessidade da utilização de Herança em nossa DSL. Fazer a ligação de todos os elementos que podem representar Entradas aos seus próximos seria uma tarefa muito trabalhosa e deixaria difícil a compreensão de nosso diagrama. Deste modo, a Domain Class SalarioBruto herdará de Entrada e, com isso terá todas as suas propriedades e relações. Concluídas as Domain Classes, seus relacionamentos e propriedades, iniciamos a construção dos Shapes (formas) que as representarão no diagrama. Bem como a dos conectores. Cada Shape tem os seus Decorators, os quais mostram o texto nas figuras. Estamos finalizando a construção de nossa DSL. Continuamos adicionando diversas validações às nossas Domain Classes, tais validações serão executadas sempre que um arquivo de nossa linguagem for aberto, salvo, ou sempre que o usuário selecionar a opção Validate no menu popup do diagrama. Estas necessitam ser escritas em forma de código e requerem um atributo especial (recurso do .NET Framework para modificar o comportamento de métodos e classes) indicando que o método será uma validação. Criamos então para algumas Domain Classes uma Partial Class (outro recurso do .NET Framework que permite a extensão de classes sem a utilização de herança), conforme a documentação do Microsoft DSL Tools. Abaixo um exemplo de utilização da linguagem. Tabela 1 – Exemplo de utilização da nossa primeira DSL Exemplos Cálculo Multiplicação entre o Salário Bruto Mensal por 75% É chegada a etapa final da construção da primeira Domain-Specific Language: a Semântica. Precisamos construir TextTemplates que irão ler nosso diagrama, gerando texto no formato de código fonte em C# e, em seguida, compilados em um Assembly .NET chamado PayrollCalc.Regras. A construção dos TextTemplates é feita em formato texto, basicamente adicionando-se Tags especiais ao código a ser gerado. A tarefa da tradução semântica foi, nesse caso, a mais complicada e demorada, pois não sabíamos o que nos esperava ao construirmos nosso diagrama. Mesmo assim, pensamos da seguinte forma: 1. Partindo do elemento Saldo da Regra, chamamos um método para pegar o valor de sua Entrada. a. Se sua Entrada for i. Calculo – chamaremos a mesma função recursivamente para os dois operandos e concatenaremos o sinal da operação entre eles. ii. Escolha – criaremos um método na classe final e no corpo do mesmo, colocaremos uma estrutura de vários IFs, comparando as Entradas e retornando um Valor. Para as Entradas e para o Valor, chamaremos o mesmo método utilizado para retornar o valor de uma Entrada, recursivamente. iii. Outra – faremos a tradução simples, de acordo com as propriedades e requisitos de cada elemento. b. Para cada Detalhe, geraremos um método que chame detalhamento.AddItem, traduzindo a palavra $VALOR$ e retornando o mesmo valor de entrada do método como resultado da função. 2. Procuraremos todos os Detalhes, cujas Entradas não levam ao Saldo e geraremos código e método para eles conforme descrito acima. Depois de um tempo considerável, chegamos a uma primeira versão estável de nosso TextTemplate principal. No entanto, sabemos que o mesmo gerador de código será utilizado para todas as regras. Por isso, chamamos seu arquivo de Payrule.ttimport e decidimos que, para cada regra nova, o usuário terá de criar: • Um diagrama novo (com a extensão .pr); • Um TextTemplate novo (com a extensão .tt). Este apenas importará nosso gerador de código genérico. Além disso, criamos mais um TextTemplate para a classe RegraFolhaArrayFactory. Entretanto, como o código final será sempre o mesmo, o TextTemplate ficou igual ao código gerado. Feito isso, nossa primeira Domain-Specific Language está pronta e podemos utilizá-la para começar a construção das regras. Ao final, apenas adicionaremos o nome de cada uma na classe RegraFolhaArrayFactory e teremos nosso Assembly .NET gerado e pronto para ser usado. Apesar de alguns problemas encontrados devido a erros na construção da linguagem ocasionados por nossa falta de experiência, o desenvolvimento de todas as regras levou pouco mais de uma hora pra ficar pronto. E isso foi certamente mais rápido do que ter de escrever código para todas elas. Além disso, agora está muito mais simples fazer a manutenção das regras, pois estamos lidando apenas com conceitos próprios do domínio Cálculo de Folha de Pagamento. Muito provavelmente, um leigo em matéria de desenvolvimento de software poderia modificar qualquer uma das regras escritas sem grandes esforços, além de criar suas próprias outras. Após finalizarmos todas as regras submetemos o Assembly aos testes. Conforme esperávamos, nem todos os testes passaram na primeira tentativa. Cometemos alguns enganos na tradução (como esquecer das aspas para os valores) e no desenho das regras (multiplicar por um valor errado, ou especificar uma operação diferente da esperada). Todavia, consertados os erros mais simples, em pouco tempo, estávamos com nosso novo Assembly totalmente funcional, rodando perfeitamente sobre o projeto de interfaces e sobre o projeto de testes. Com a experiência obtida no desenvolvimento da primeira linguagem, foi muito mais rápido desenvolvermos a segunda para os exportadores. Decidimos utilizar, desta vez, outra maneira de referência entre as linhas e os campos: as linhas serão figuras as quais irão conter os campos, em vez de referenciá-los simplesmente. Ou seja, não necessitaremos de um conector nesse caso. As figuras de campo serão posicionadas dentro das de linha. Decidimos também que teremos dois tipos de linha: Funcionário e Detalhe. No entanto, não teremos duas Domain Classes para isto. Teremos apenas uma e seu tipo será especificado por uma propriedade. Além disso, as linhas serão colocadas soltas no diagrama, mas possuirão uma propriedade especificando a sua ordem de aparição no arquivo. Em alguns casos, não teremos linhas de Detalhe, porém, ao menos uma linha de qualquer tipo deve ser adicionada ao arquivo. Para os campos, também teremos uma propriedade representando sua ordem. Contudo, o usuário terá de especificar também seu tamanho e, desta vez, faremos uso de outra funcionalidade muito interessante do Microsoft DSL Tools: as propriedades calculadas. Aos campos, serão adicionadas duas destas: De e Ate. Ou seja, sempre que houver uma modificação nas propriedades Ordem e Tamanho de um campo, suas propriedades De e Ate serão recalculadas para mostrar as posições inicial e final atualizadas na linha corrente. Ainda no que se refere aos campos, seu valor dependerá da Domain Class utilizada e também utilizaremos herança em sua definição. Ficaremos com os seguintes tipos de campo, que herdarão de Campo: • Texto – Segue o mesmo princípio do elemento Valor, da linguagem anterior. O usuário poderá especificar qualquer texto como valor para este campo. • Funcionario – Retorna o valor de uma propriedade do funcionário em questão. • Detalhe – A mensagem de detalhe em questão. • SalarioLiquido – Retorna o Salário Líquido calculado. No sistema baseado no paradigma Orientação a Objetos, o valor do FGTS era apenas exportado e não influenciava o Salário Líquido, por isso utilizamos a Regra de FGTS na classe do Exportador. Portanto, não achamos consistente, incluir uma Domain Class para isto. Seria extremamente específico e fugiria ao contexto. Vamos criar, em vez disso, mais um campo, o Custom, que terá apenas uma propriedade Nome e, posteriormente, o usuário especificará o código necessário para o cálculo deste valor. A etapa da semântica, desta vez, foi muito mais rápida do que a anterior. O planejamento prévio nos ajudou na hora de escrever o código. Além disso, o problema atual é muito mais simples de se resolver. O pensamento para a tradução foi baseado em: 1. Para cada linha de Funcionário a. Percorrer a lista de campos, retornando seu valor; b. Adicionar código para adequar o valor ao seu tamanho correto. 2. Para cada linha de Detalhe a. Fazer o mesmo que na linha do tipo Funcionário. Ao esbarrar na geração de código para o campo Custom, criamos mais um arquivo chamado CustomValues.ttcustom, o qual é referenciado pelo Payxport.ttimport (análogo ao Payrule.ttimport da linguagem anterior). E nesse arquivo deixamos um método parcialmente implementado, para que o usuário pudesse preencher com o código de cada Campo Custom adicionado ao diagrama. Também deve ser retornado se o valor especificado é ou não de ponto flutuante, para que se possamos tomar as atitudes necessárias ao tamanho do campo na geração do arquivo. Finalmente, chegamos ao fim da construção da segunda linguagem. Iniciemos a utilização da mesma para a construção dos Exportadores, que foi muito rápida devido a flexibilidade obtida e à experiência com a outra linguagem criada. E desta vez todos os testes passaram de primeira. 5.4 Análise Comparativa A grande questão da comparação entre os dois paradigmas se encontra na produtividade do segundo em relação ao primeiro. Conforme já comentado, de acordo com a evolução das linguagens e paradigmas de programação, pode-se observar um constante crescimento de pessoas capazes de construir seu próprio software. Quanto a este ponto, não temos dúvida de que qualquer pessoa possuidora de conhecimento sobre o domínio poderia editar ou criar elementos de software em Domain-Specific Languages. A curva de aprendizado seria incomparavelmente menor. No entanto, essa facilidade não se estende à criação das linguagens propriamente dita, pois temos uma fase que ainda é muito complicada: a parte semântica e geração de código. Para criar validações e gerar código corretamente, ainda necessitaremos de programadores com mais experiência. Tanto no domínio, quanto no ambiente de destino. Quanto ao tempo gasto para se construir os dois projetos, nossas expectativas não foram alcançadas. A utilização do paradigma proposto foi consideravelmente mais demorada. Logicamente, isso se deve a nossa pouca experiência com o paradigma em questão, a pouca documentação disponível e a termos que construir todas as DSLs. Pensamos só ter a ganhar utilizando algo que irá possibilitar a mais pessoas construir e alterar seu próprio software além de automatizar processos muitas vezes mecânicos. 6. Conclusão A evolução do desenvolvimento de software no mundo segue para um mesmo ponto: tornar-se cada vez mais fácil, a ponto de permitir que mais pessoas possam criar seus próprios programas de maneira bastante simples. A Language Oriented Programming está fortemente ligada ao ato da geração automática de código. No entanto, esta não é uma tarefa fácil de se resolver. Para a escrita de Domain-Specific Languages ainda precisaremos de programadores com muito conhecimento na plataforma de destino do código gerado. Por outro lado, pessoas conhecedoras apenas do domínio em questão são capazes de utilizar uma DSL para construir/alterar um sistema inteiro. Através de uma análise comparativa, esperávamos traçar pontos positivos e negativos entre os dois paradigmas em questão (a atual Orientação a Objetos e a nova Orientação a Linguagens), estabelecendo assim uma espécie de validação da proposta de Dmitriev (2005). E através da construção de um projeto para o cálculo de folhas de pagamentos em ambos os paradigmas, pudemos avaliar positivamente a Language Oriented Programming, chegando a conclusão de só termos a ganhar fazendo uso da mesma. No entanto, sabemos também que ainda deverá haver um certo período de maturação para que este se torne um padrão para o desenvolvimento de software. Sabemos que o mundo hoje caminha para a automação. E isso para o desenvolvimento de software. O próximo paradigma certamente inclusão de muitas pessoas nesse contexto. Mesmo não aparecendo Programação Orientada a Linguagens, certamente, vai conter muitos aqui estudados. também serve possibilitará a com o nome dos conceitos 7. Referências APPLEBY, D. (1991). PROGRAMMING LANGUAGES: Paradigm and Practice. Singapore: McGraw-Hill, Inc. BROWN, A. (17 de Fevereiro de 2004). An introduction to Model Driven Architecture. Acesso em 18 de Julho de 2007, disponível em IBM: http://www.ibm.com/developerworks/rational/library/3100.html CZARNECKI, K. (28 de Novembro de 2002). Generative Core Concepts. Acesso em 18 de Julho de 2007, disponível em Generative Core Concepts: http://www.programtransformation.org/Transform/GenerativeCoreConcepts CZARNECKI, K., EISENECKER, U. W., & STEYAERT, P. (1997). Beyond Objects: Generative Programming. Acesso em 9 de Março de 2007, disponível em http://www-ia.tu-ilmenau.de/~czarn/aop97.html DMITRIEV, S. (2005). Language Oriented Programming. The Next Programming Paradigm. Acesso em 10 de Março de 2007, disponível em http://www.onboard.jetbrains.com/is1/articles/04/10/lop/ FOWLER, M. (2006). Introduction to Domain Specific Languages. Acesso em 13 de Março de 2007, disponível em http://www.infoq.com/presentations/domain-specificlanguages FOWLER, M. (2005). Language Workbenches: The Killer-App for Domain Specific Languages? Acesso em 11 de Março de 2007, disponível em http://martinfowler.com/articles/languageWorkbench.html GREENFIELD, J. (Novembro de 2004). Software Factories: Assembling Applications with Patterns, Models, Frameworks, and Tools. Acesso em 19 de Julho de 2007, disponível em Microsoft: http://msdn2.microsoft.com/en-us/library/ms954811.aspx OMG. (s.d.). Model Driven Architecture. Acesso em 18 de Julho de 2007, disponível em OMG: http://www.omg.org/mda/ PROLOG. (Agosto de 2001). Acesso em 19 de Julho de 2007, disponível em Wikipedia: http://en.wikipedia.org/wiki/Prolog SELLS, C. (Dezembro de 2001). Generative Programming: Modern Techniques to Automate Repetitive Programming Tasks. Acesso em 18 de Julho de 2007, disponível em Generative Programming: Modern Techniques to Automate Repetitive Programming Tasks: http://msdn.microsoft.com/msdnmag/issues/01/12/GenProg/ SIMONYI, C. (1995). The Death Of Computer Languages, the Birth of Intentional Programming. Acesso em 13 de Março de 2007, disponível em http://research.microsoft.com/research/pubs/view.aspx?msr_tr_id=MSR-TR-95-52