223
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

Language Oriented Programming Análise do Conceito … · 4.4 Análise Comparativa ... Tabela 1 - Comparação entre os paradigmas ... Voltando um pouco à teoria dos paradigmas de

Embed Size (px)

Citation preview

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: Language Oriented Programming, Domain-Specific

Languages, Payroll Calculation, Programming Paradigms, Software

Development, Object Oriented Programming

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 Model-

Driven 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 re-

declaradas.

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.

3 LA

dese

os m

tradu

comp

softw

e ao

Java

mesm

seja

que n

utiliza

preci

espe

será

LANGUA

Pode

senvolvimen

mesmos pri

dução da li

mputador.

Tam

ftware, hoje,

ao IDE utiliza

va, sendo e

sma maneir

ja tão perce

e nos é forne

Segu

lizadas, atu

ecisamos tra

pecializada f

rá o entendim

AGE OR

odemos pe

ento de softw

princípios: a

linguagem

mbém não

je, ainda est

lizado para s

editada em

eira, posso u

ceptível para

rnecida (DMI

egundo Dmit

tualmente)

transcrever o

a for a lingua

dimento. Pod

RIENTED

perceber q

ftware são b

a automaçã

m natural e

ão nos é d

stá muito pr

a se program

m uma ferr

o utilizar o D

ara alguns, a

MITRIEV, 20

itriev (2005

) são um

r o que esta

uagem utiliz

oderíamos i

Figura 1 - A

ED PROG

que as

o bastante p

ção de proc

e do pensa

difícil perc

preso à ling

amar. Por ex

erramenta co

Delphi ou o

, ainda esta

2005).

05), linguage

tanto imp

stamos pens

ilizada, mais

s imaginar e

Análise de Pr

GRAMMI

tendências

parecidas e

ocessos rep

nsamento pa

rceber que

nguagem de

exemplo, po

como o Ecl

o C#. Entre

stamos muito

gens de pro

mprodutivas,

nsando em a

is rápida ser

r então o seg

Produtividade

MING

ias para

s e envolvem

epetitivos e a

para algo i

e o desenv

de programa

posso utiliza

clipse, ou o

tretanto, por

uito amarrad

ropósito ger

as, pois, pa

algoritmos

rá a traduç

eguinte gráfi

o futuro

em basicam

e a facilidad

inteligível

envolvimento

mação escol

izar a lingua

u o JBuilder

or mais que

ados a estru

eral (largam

para utilizá

os. Quanto m

ução e mais

áfico:

26

ro do

mente

ade na

l pelo

nto de

colhida

uagem

er. Da

ue não

trutura

mente

izá-las,

o mais

is fácil

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.

Lista de Funcionários

Uma lista simples com os campos:

matrícula, salário bruto mensal, nome,

CPF, 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.

Ficha de Registro de Freqüência

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)

• Turno esperado

o Horário de entrada

esperado

o Horário de saída

esperado

• Turno realizado

o Horário de entrada

realizado

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

Social

Será descontado um percentual do

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

Tempo de Serviço

Corresponde a 8% do valor bruto da

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%

• Acima de R$2.115,01 – 27,5%

Salário Família 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 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

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á permitido o

parcelamento das férias. 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$2.512,08 –

R$502,58

Listagem 2 – Regras para o cálculo de cada funcionário

38

• Ficha do Funcionário

Data Estado Turno

Esp.

Turno

Real.

Intervalo

Esp.

Intervalo

Real.

Hora

Extra

1/1/2007 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 - - - - -

10/1/2007 Presença 08:00-18:00 08:00-18:00 12:00-14:00 12:00-14:00 -

• 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 e valores:

1-20 = Nome do Funcionário

21-31 = 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 e valores:

1-4 = Agência (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 “1”)

2-4 = Matrícula do Funcionário

5-24 = Nome do Funcionário

25-35 = CPF do Funcionário

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

Entrada -

VaiParaSaldo: Saldo (0..1)

Toda entrada poderá ou não ir para

um, e somente um, objeto do tipo

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.

SalarioBruto Entrada

Granularidade: String

Poderá ser definida como Ano, Mês,

Dia, Hora ou Minuto.

QtdHorasExtrasPeriodo Entrada

De: String

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.

QtdDiasEstado Entrada

Estado: String

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.

Calculo Entrada

Entrada: Entada (1..1)

Todo cálculo necessita

obrigatoriamente de um, e somente

um, objeto do tipo Entrada através

de um conector Resulta. Esta

entrada representará o 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á o segundo

operando.

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]

Escolha -

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

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]

ResultadoEscolha Entrada

Escolhas: Escolha[] (1..*)

Todo ResultadoEscolha necessita

obrigatoriamente de um ou mais

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.

Saldo -

Entrada: Entrada (1..1)

Todo Saldo necessita

obrigatoriamente de um, e somente

um, objeto do tipo Entrada. Este

valor será retornado no método

CalculaERetornaSaldo.

Detalhe -

Entrada: Entrada (1..1)

Todo Detalhe necessita

obrigatoriamente de um, e somente

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

Nome Shape

SalarioBruto

QtdHorasExtrasPeriod

o

QtdDiasEstado

QtdMesesTrabalhados

QtdFeriasMes

QtdFilhos

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

Calculo

OperacaoEnumeration

Uma operação só poderá ser definida

como: Soma, Subtração, Multiplicação

ou Divisão.

78

Escolha

IntervaloValido

A propriedade De deve ser menor do

que a propriedade Ate, a menos que a

segunda contenha o valor 0 (zero).

QtdDiasEstado

EstadoValido

O estado somente poderá ser definida

como Presença, Falta, Afastamento

ou Dia Não Útil.

SalarioBruto

GranularidadeEnumeration

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 a mesma função

recursivamente para os dois operandos e

concatenaremos o sinal da operação entre eles.

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 Domain-

Specific 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

Linha -

Campos: Campo[] (1..*)

Toda linha terá obrigatoriamente um ou

mais objetos do tipo Campo.

Ordem: Inteiro

Representa a ordem da linha no

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 Campo

Funcionario: String

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

Arquivo

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.

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.

Campo

TamanhoDoCampo

O tamanho do campo não pode ser

menor do que o valor 1.

Funcionario

PropriedadeEnumeration

A propriedade Propriedade só pode

receber Matrícula, Nome, CPF ou

Salário.

Linha

OrdemDosCampos

Os campos devem estar ordenados e

devem começar por 1.

TipoEnumeration

O tipo da linha só pode ser

Funcionário ou Detalhe.

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 “auto-

documentá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

Orientada a Objetos

Language Oriented

Programming

(DSLs/Sistema)

Tempo de

Desenvolvimento

Médio Alto/Baixo

Compreensão Difícil Muito Difícil/Muito Fácil

Curva de Aprendizado Grande Grande/Muito Pequena

Quantidade de

Profissionais Capazes

Baixa Muito Baixa/Alta

97

de Programar com

Proficiência

Manutenção Posterior Média Difícil/Muito Fácil

Documentação Necessita de Notação

Específica

Necessita de Notação

Específica/Auto-

Documentá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 Test-

Driven 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.program-

transformation.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/pt-

br/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 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

SUN MICROSISTEMS. (2002). Model-View-Controller. Retrieved Julho 18,

2007, from Java BluePrints: http://java.sun.com/blueprints/patterns/MVC-

detailed.html

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.TimeOfDay).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 EstadoPonto estado; private DateTime entradaTurnoEsperado; private DateTime saidaTurnoEsperado; private DateTime entradaTurnoRealizado; private DateTime saidaTurnoRealizado;

120

private DateTime entradaIntervaloEsperado; private DateTime saidaIntervaloEsperado; private DateTime entradaIntervaloRealizado; private DateTime saidaIntervaloRealizado; private DateTime entradaHoraExtra; private DateTime 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, detalhamento.GetItem(0)); Assert.AreEqual(item1, detalhamento[0]); Assert.AreEqual(item2, detalhamento.GetItem(1)); Assert.AreEqual(item2, detalhamento[1]); Assert.AreEqual(item3, detalhamento.GetItem(2)); Assert.AreEqual(item3, 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 void Adiciona3EVerificaPontos() Ponto ponto1 = CriaPontoNovo(); Ponto ponto2 = CriaPontoNovo(); Ponto ponto3 = CriaPontoNovo(); ficha.AddPonto(ponto1); ficha.AddPonto(ponto2); ficha.AddPonto(ponto3); Assert.AreSame(ponto1, ficha.GetPonto(0)); Assert.AreSame(ponto1, ficha[0]); Assert.AreSame(ponto2, ficha.GetPonto(1)); Assert.AreSame(ponto2, ficha[1]); Assert.AreSame(ponto3, ficha.GetPonto(2)); Assert.AreSame(ponto3, ficha[2]); [Test] public void Adiciona3Remove2EVerificaPonto() Ponto ponto1 = CriaPontoNovo(); Ponto ponto2 = CriaPontoNovo(); Ponto 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, folha[0]); Assert.AreSame(item2, folha.GetItem(1)); Assert.AreSame(item2, folha[1]); Assert.AreSame(item3, folha.GetItem(2)); Assert.AreSame(item3, 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 System; using System.Collections; using NUnit.Framework; using PayrollCalc.Tests.Mocks; using PayrollCalc.Regras; using 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(" fulano da silva123456789010008896.89", resultado[0]); Assert.AreEqual(" beltrano souza999999999990000009.60", resultado[1]); Assert.AreEqual(" ciclano pereira111111111110000000.26", resultado[2]); 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 System; using System.Collections; using NUnit.Framework; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 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, 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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 System; using NUnit.Framework; using PayrollCalc.Regras; using 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, 0, 0, 00.00, 00.00); RodaTeste(200.00, 0, 30, 0, 0, 0, 00.00, 00.00); RodaTeste(500.00, 0, 30, 0, 0, 0, 00.00, 00.00); RodaTeste(800.00, 0, 30, 0, 0, 0, 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 System; using System.Collections; using System.IO; using System.Xml; using PayrollCalc; using PayrollCalc.Regras; using 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 System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using 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.AnchorStyles.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).ToString("#,##0.00");

7.6 PROJETO PAYRULE (LOP)

CalculoConstraints.cs

using System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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].EntradaTurnoRealizado.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 += " " + elseWord + "\n"; corpoFuncao += " \n"; corpoFuncao += " return 0;\n"; corpoFuncao += " \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.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraDecimoTerceiro.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

RegraEstados.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraEstados.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

185

RegraFerias.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraFerias.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

RegraFGTS.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" 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.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraHE.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

RegraINSS.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraINSS.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

186

RegraINSS.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#> <#@ Payrule processor="PayruleDirectiveProcessor" requires="fileName='RegraIRF.pr'" #> <#@ include file="..\Base\Payrule.ttimport" #>

RegraSalarioFamilia.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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 System; using Microsoft.VisualStudio.Modeling; using System.Collections.ObjectModel; using 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.ModelingTextTransformation" debug="true"#> <#@ Payxport processor="PayxportDirectiveProcessor" requires="fileName='ExportadorBanco.px'" #> <#@ include file="..\Base\Payxport.ttimport" #>

205

ExportadorFGTS.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" debug="true"#> <#@ Payxport processor="PayxportDirectiveProcessor" requires="fileName='ExportadorFGTS.px'" #> <#@ include file="..\Base\Payxport.ttimport" #>

ExportadorGrafica.tt

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" 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.Salario)"; 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 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.

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

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

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.program-transformation.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. (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