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

Language Oriented Programming Análise do Conceito e