Free E-Books!

Category : Diversos

No site E-Books Directory você vai encontrar um diretório de links para e-books gratuitos, desde graphic novels a livros sobre ciência da computação. Tem algumas coisas muito boas lá. Pra ir direto a seção sobre linguagens de programação é só clicar aqui.

Os livros estão todos em lingua inglesa.

Código Ruim

Category : Refatoração

Um código bom é como uma prosa bem redigida, exprime com clareza sua intenção e exerce boa influência nas alterações futuras. Passamos a maior parte do tempo de programação lendo e aprendendo códigos dos outros e quanto mais agradável esta experiência, melhores são nossos resultados.

Infelizmente a realidade é um pouco diferente…

Na Semana passada um novato da equipe de desenvolvimento Delphi pediu para que eu verificasse um trecho de código no módulo de folha de pagamentos, pois achou engraçado a lógica condicional que fazia a mesma coisa independendente do resultado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('PROCESSADO').AsString = 'P' then
    LogVer.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
               '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
               '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
               '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
               '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
else
    if cdsEventos.FieldByName('PROCESSADO').AsString = 'A' then
        LogAlte.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', )+'|')
    else
        begin
        LogCanc.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
        end;
end;

Ao passar o olho rapidamente enxerguei a mesma coisa que ele, as condicionais não serviam para nada. Prestando um pouco mais de atenção percebemos que de acordo com a o valor do campo ‘PROCESSADO’ a informação seria escrita num arquivo de log diferente (LogVer, LogAlte ou LogCanc).

A duplicação do código de escrita no log é tão gritante que oculta tudo mais que está em volta, escondendo sua intenção. Para piorar este trecho de código estava num procedimento que não tinha nada a ver com log de informações e fazia mais uma dúzia de coisas diferentes.

Refatorando

Apenas eliminando a duplicação a legibilidade melhorou drasticamente, e pudemos enxergar a intenção do código sem distrações. Com três refatorações simples melhoramos muito aquele trecho de código:

  • Movemos o código duplicado para uma variável que descreve exatamente o que aquele emaranhado de cocatenação de strings significa.
  • Renomeamos as variáveis que representam nossos objetos de log para algo mais descritivo.
  • Substituímos as strings mágicas ‘P’ e ‘A’ por constantes que expressam o significado oculto nestas strings.

Eis o resultado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TForm1.ProcessarLog();
begin
NovaEntrada := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
               '|' + Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
               '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
               '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
               '|' + Alinhar(DIREITA, 18, ' ', '') + '|');

if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntrada)
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntrada)
else
    LogCancelados.Add(NovaEntrada);
end;

A identação do código também foi corrigida.

Com isso ganhamos clareza de intenção e o próximo novato que passar por este código irá entender o que está acontecendo ali sem distrações. Refatorações posteriores melhorarão ainda sua estrutura, pois ainda temos aquela dúzia de coisas misturadas no procedimento.

Mais melhorias

O código de formatação da entrada de log deve ser extraído para sua própria função. Com isso temos menos um desvio mental durante a leitura, já que dificilmente estariamos interessados em saber como a linha do log está sendo formatada aqui.

Se um dia a formatação fosse alterada teríamos que descobrir onde este código está e encontrá-lo em ‘ProcRegistrosFuncionarios()’, com proximadamente 200 linhas não seria nada intuitivo.

Extraímos o código de formatação para sua própria função:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntradaLogFormatada())
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntradaLogFormatada())
else
    LogCancelados.Add(NovaEntradaLogFormatada());
end;

function TForm1.NovaEntradaLogFormatada(): String
begin
    Result := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
              '|' + Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
              '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
              '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00', cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
              '|' + Alinhar(DIREITA, 18, ' ', '') + '|');
end;

Plantando uma semente

O código existente influencia a maneira como os novos códigos são escritos. É como plantar uma semente, bom código irá gerar códigos cada vez melhores. Código ruim é como uma erva daninha que irá se multiplicar e acabar com seu sistema.

A refatoração nos deu mais clareza e disponibilizou uma função única para formatação da entrada no log. Imagina se fosse necessário incluir a opção para escrever o log em XML. Com a primeira versão do código provavelmente um programador inexperiente iria aumentar as duplicações e piorar mais o código que já estava ruim, criando uma aberração como:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
procedure TForm1.ProcessarLog();
begin
if FormatoXML then
begin
    if CDSGrid.FieldByName('PROCESSADO').AsString = 'P' then
        LogVer.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
else if CDSGrid.FieldByName('PROCESSADO').AsString = 'A' then
    LogAlte.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
else
    LogCanc.Add('<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />')
end
else
begin
if cdsEventos.FieldByName('PROCESSADO').AsString = 'P' then
    LogVer.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
               '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
               '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
               '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
               '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
else
    if cdsEventos.FieldByName('PROCESSADO').AsString = 'A' then
        LogAlte.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', )+'|')
    else
        begin
        LogCanc.Add('|'+Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString)+
                    '|'+Alinhar(DIREITA,  6, '0', cdsEventos.FieldByName('EVENTO').AsString)+
                    '|'+Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString)+
                    '|'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',cdsEventos.FieldByName('QUANTIDADE').AsFloat))+
                    '|'+Alinhar(DIREITA, 18, ' ', '')+'|')
        end;
end;
end;

Enquanto que com nossa nova versão refatorada, com a metade de linhas o resultado da alteração seria:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
procedure TForm1.ProcessarLog();
begin
if cdsEventos.FieldByName('Processado').AsString = PROCESSADO then
    LogProcessados.Add(NovaEntradaLogFormatada())
else if cdsEventos.FieldByName('Processado').AsString = ALTERADO then
    LogAlterados.Add(NovaEntradaLogFormatada())
else
    LogCancelados.Add(NovaEntradaLogFormatada());
end;

function TForm1.NovaEntradaLogFormatada(): String
begin
if FormatoXML then
    Result := '<funcionario="'+Alinhar(DIREITA, 11, '0', CDSGrid.FieldByName('FUNCIONARIO').AsString)+'"'
              ' evento="'+Alinhar(DIREITA,  6, '0', CDSGrid.FieldByName('EVENTO').AsString)+'"'
              ' ano_mes="'+Alinhar(DIREITA, 10, ' ', CDSGrid.FieldByName('ANO_MES').AsString)+'"'
              ' quantidade="'+Alinhar(DIREITA, 10, '0', FormatFloat('#0.00',CDSGrid.FieldByName('QUANTIDADE').AsFloat))+'" />'
else
    Result := '|' + Alinhar(DIREITA, 11, '0', cdsEventos.FieldByName('FUNCIONARIO').AsString) +
              '|' + Alinhar(DIREITA, 6, '0', cdsEventos.FieldByName('EVENTO').AsString) +
              '|' + Alinhar(DIREITA, 10, ' ', cdsEventos.FieldByName('ANO_MES').AsString) +
              '|' + Alinhar(DIREITA, 10, '0', FormatFloat('#0.00', cdsEventos.FieldByName('QUANTIDADE').AsFloat)) +
              '|' + Alinhar(DIREITA, 18, ' ', '') + '|');
end;

Ainda não está perfeito, mas podemos ir refatorando cada vez mais, como extrair as responsabilidades de log para outra classe, e de lá aplicar padrões de projeto como o Strategy, aumentando ainda mais a reusabilidade do código. Eu parei por aqui porque precisamos entregar logo a correção efetuada no projeto.

Conclusão

Código legado largado é difícil de entender e alterar. Sua manutenção se torna penosa para o programador e consequentemente encarece o software, já que fatores como tempo e qualidade entram em jogo.

Escreva seu código para que seus leitores o entenda sem precisar chamar o colega ao lado e influencie-os a dar bons passos nas alterações futuras. É sua responsabilidade como programador.

Como disse Martin Fowler em seu livro Refactoring: Improving the Design of Existing Code:

“Qualquer tolo consegue escrever código que um computador entenda. Somente bons programadores escrevem código que humanos possam entender.”

Aprenda a expressar a intenção do seu código, não seja um tolo.

Livros recomendados

Clean Code: A Handbook of Agile Software Craftsmanship [Robert C. Martin]
Refactoring: Improving the Design of Existing Code [Martin Fowler]

Desenvolvimento Orientado por Testes

Category : TDD

Como comentei no meu primeiro artigo aqui no Blog, durante meus estudos para a SCJP conheci muitas coisas bacanas. Foi meu primeiro contato com algumas das metodologias do Agile. Entre elas a que mais procurei praticar, mesmo enquanto criava simples códigos de aprendizagem era o Test Driven Development (TDD).

A premissa do TDD é que o desenvolvedor deve escrever o código de testes antes de escrever o código de produção. Minha primeira reação ao ler isso era de achar maluquice: “como posso testar algo antes mesmo de existir?”. A resposta para isso encontrei pesquisando mais sobre o assunto e tudo passou a fazer muito sentido depois que entendi seus conceitos. Meu pensamento então passou a ser: “como um programador pode viver sem isso?”.

As três leis básicas

Para começar você deve seguir três regras básicas:

  • Escreva um teste que falhe.
  • Não escreva mais de um teste que falhe, sendo que não compilar é falhar.
  • Escreva o código de produção necessário para passar neste teste.

Um exemplo simples

Você começa a escrever um código de teste que propositalmente falhe, já que neste ponto não deverá haver nenhum código de produção. No JUnit 4 o caso de teste seria algo simples como:

1
2
3
4
5
6
7
8
9
10
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class HelloWorldTest {
    @Test
    public void aSimpleHelloTest() {
        HelloWorld hello = new HelloWorld();
        assertEquals("Hello world!", hello.sayHello());
    }
}

Execute o teste. O mesmo deverá falhar neste ponto.

Em seguida implemente na classe alvo o código de produção de maneira que passe no teste. Por exemplo:

1
2
3
4
5
public class HelloWorld {
    public String sayHello() {
        return "Hello world";
    }
}

Execute o teste novamente e faça com que o código passe no teste. Repita o processo.

“No Eclipse você pode facilmente criar uma unidade de testes indo em New : Other : Junit Test Case. Certifique-se de utilizar o JUnit 4 para seus testes. Para executá-lo bas ir em Run : Run As : JUnit Test

Benefícios

Gosto de classificar cinco benefícios quando me perguntam sobre TDD.

1. Entendimento do domínio:

Escrevendo testes antes do código o desenvolvedor é levado a refletir sobre o que está sendo implementado, o que leva a uma maior clareza sobre o domínio do problema, e até mesmo a levantar fatos que poderiam passar despercebido na especificação de requisitos.

2. Segurança:

Os testes são um selo de garantia que tudo está funcionando corretamente. Se for necessário alterar algo no futuro, o desenvolvedor terá certeza que a alteração não quebrou nada.

3. Ganho de produtividade:

A codificação de um caso teste é simples e ocupa uma fração insignificante do tempo de desenvolvimento. A unidade de testes se transforma numa documentação sempre atualizada do código. Com isso em mãos o desenvolvedor fará a manutenção do código mais rápidamente.

4. Melhoria na qualidade:

Os testes de unidade garantem que o que foi implementado funcione sempre da maneira esperada. Muitos dos bugs que poderiam passar despercebidos em um código sem teste não passarão aqui. O desenvolvedor que reflete sobre o domínio sendo implementado cria códigos menos propensos a bugs.

5. Melhoria estrutual do código:

Desenvolver orientado por testes nos força a criar códigos com menor acoplamento, orientado a objetos. Os testes também são essenciais para a refatoração do código. Como eu disse outro dia no Twitter: “Refatorar sem testes é como paraquedismo sem para-quedas!”

Links relacionados:

http://www.junit.org/
http://en.wikipedia.org/wiki/Test_driven_development
http://en.wikipedia.org/wiki/Agile_software_development

Livros relacionados

Clean Code: A Handbook of Agile Software Craftsmanship [Robert C. Martin]
Test-driven development: by example [Kent Beck]

Cadê o Meu Gato? Ou, Cadê o Bug?

Category : Boas Práticas

Kitty BugFaz tempo estava querendo escrever este post, mas um fato interessante me fez mudar o título e o texto.

Nosso novo bichano de estimação na primeira semana desapareceu do apartamento. Minha esposa me ligou desesperada dizendo que ele havia fugido enquanto eu saia pra trabalhar. Esta idéia já estava formada na cabeça dela, e não adiantou eu argumentar que sempre tomo cuidado ao sair e que a gata ainda filhote sempre fica insegura quando a porta é aberta e prefere se esconder a sair.

Depois de procura-la por todo o prédio, casas e lotes vizinhos minha esposa resolveu desistir e colocar cartazes de “procura-se” por aí. Eu tinha certeza que ela estava em casa, mas ela teimou que já havia revirado todos os locais e nenhum sinal do bichinho.

Você pode rir, mas ao chegar a noite em casa não demorei mais de cinco minutos para encontrar a danadinha. Fui no local mais óbvio, onde todo gato gosta de entrar quando não estamos olhando, o guarda-roupas.

Mas o que isso tem a ver com software?

Este fato me levou a lembrar um erro que cometi poucas semanas antes na implementação de uma classe responsável pela manipulação dos dados de produtos no sistema.

Eu conhecia a classe de cor e só precisava adicionar uma nova propriedade. Basicamente um par de getter/setter. O design da classe é um pouco mais complicado que isso, e não vem ao caso, colocar uma nova propriedade implica sempre em mais algumas gambiarras tarefas por uma decisão de design.

Mas eu estava confiante, já conhecia o código e estava mais preocupado em terminar outra tarefa de maior importância. Basicamente usei a clássica combinação rápida que destrói qualquer código: Ctrl+C, Ctrl+V e renomear. Agora era só voltar para a tarefa de maior importância e commitar aquele bug que eu acabara de criar.

Não existe menos ou mais atenção!

Ou você está prestando atenção no que está fazendo ou não. Pequenas alterações, por mais insignificantes que sejam precisam de testes. Testar apenas as partes mais complexas de um software é se submeter a perder horas procurando pela vizinhança por algo que está no seu guarda-roupas quando ocorrer um bug.

Leve a sério e pratique TDD e BDD ou você nunca vai ter certeza se seu gato fugiu ou está tirando uma soneca dentro de uma gaveta!

Alterando a Rota Padrão do Squid com Iptables

Category : Linux, Roteamento

Hoje resolvi sujar as mãos um pouquinho e resolver um problema pertinente lá na empresa: fazer com que o Squid utilize uma rota alternativa a rota padrão do gateway. Temos dois links de internet conectados ao gateway e queriamos definir as rotas de acordo com o tráfego. O problema era o tráfego gerado pelo próprio gateway, como o Proxy, DNS e Atualização do DNS dinâmico com ddclient.

Vasculhando um pouco pelo google achei uma alternativa interessante, que seria usar o módulo owner do iptables para marcar o tráfego vindo do usuário proxy (usamos Debian no gateway) para uma tabela de roteamento alternativa.

Diagrama do Gateway

Diagrama do Gateway

O script requer o pacote iproute2 instalado, bem como o Squid já configurado.

A tabela de Roteamento

Nosso gateway padrão atualmente é 192.168.15.254, na subrede 192.168.15.0/24. Com a função set_route() é criada uma nova tabela de roteamento com identificação numérica armazenada na variável $TABELA.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
declare LAN="192.168.1.0/24"
declare WAN="10.0.0.0/24"
declare IFLAN="eth0"
declare IFWAN="eth2"
declare GATEWAY="10.0.0.254"
declare TABELA="100"

declare IPROUTE="/sbin/ip route"

#Cria a tabela de roteamento alternativa com o iproute
function set_route() {
    $IPROUTE add $LAN dev $IFLAN table $TABELA
    $IPROUTE add $WAN dev $IFWAN table $TABELA
    $IPROUTE add default via $GATEWAY dev $IFWAN table $TABELA
    $IPROUTE flush cache
}

As Regras de Roteamento

A função set_rule() primeiro marca os pacotes oriundos do usuário proxy usando os matchers MARK e CONMARK. Em seguida é criada uma regra de roteamento para capturar os pacotes marcados e roteá-los através da nova tabela.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
declare IPTABLES="/sbin/iptables"
declare IPRULE="/sbin/ip rule"

#Cria as regras para o trafego que utilizara a tabela de roteamento alternativa
function set_rule() {
    $IPTABLES -t mangle -A PREROUTING -j CONNMARK --restore-mark   

    #A regra que marca o trafego gerado pelo usuario do proxy
    $IPTABLES -t mangle -A OUTPUT -m owner --uid-owner proxy -j MARK --set-mark $TABELA

    $IPTABLES -t mangle -A POSTROUTING -o $IFWAN -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -A POSTROUTING -j CONNMARK --save-mark

    #A regra que captura os pacotes marcados e os insere na nova tabela
    $IPRULE add fwmark $TABELA table $TABELA
}

Você pode ampliar as regras como, por exemplo, fazer com que todo tráfego pop3 e smtp sejam roteados através da nova tabela.

1
2
    $IPTABLES -t mangle -A PREROUTING -p TCP --dport smtp -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -A PREROUTING -p TCP --dport pop3 -j MARK --set-mark $TABELA

Juntando tudo

Se você já possui mais de uma tabela de roteamento configurada com o iproute basta adaptar o script conforme a sua necessidade.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/bin/bash
# Desctiption: Script de roteamendo avancado
# Author: Fabiano Sobreira

#Variaveis
declare LAN="192.168.1.0/24"
declare WAN="10.0.0.0/24"
declare IFLAN="eth0"
declare IFWAN="eth2"
declare GATEWAY="10.0.0.254"
declare TABELA="100"

declare IPTABLES="/sbin/iptables"
declare IPROUTE="/sbin/ip route"
declare IPRULE="/sbin/ip rule"

#Cria a tabela de roteamento alternativa com o iproute
function set_route() {
    $IPROUTE add $LAN dev $IFLAN table $TABELA
    $IPROUTE add $WAN dev $IFWAN table $TABELA
    $IPROUTE add default via $GATEWAY dev $IFWAN table $TABELA
    $IPROUTE flush cache
}

function unset_route() {
    $IPROUTE flush table $TABELA
}

#Cria as regras para o trafego que utilizara a tabela de roteamento alternativa
function set_rule() {
    $IPTABLES -t mangle -A PREROUTING -j CONNMARK --restore-mark   

    #A regra que marca o trafego gerado pelo usuario do proxy
    $IPTABLES -t mangle -A OUTPUT -m owner --uid-owner proxy -j MARK --set-mark $TABELA

    #Regras que marcam o trafego smtp e pop3
    $IPTABLES -t mangle -A PREROUTING -p TCP --dport smtp -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -A PREROUTING -p TCP --dport pop3 -j MARK --set-mark $TABELA

    $IPTABLES -t mangle -A POSTROUTING -o $IFWAN -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -A POSTROUTING -j CONNMARK --save-mark

    #A regra que captura os pacotes marcados e os insere na nova tabela
    $IPRULE add fwmark $TABELA table $TABELA
}

function unset_rule() {
    $IPTABLES -t mangle -D PREROUTING -j CONNMARK --restore-mark
    $IPTABLES -t mangle -D OUTPUT -m owner --uid-owner proxy -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -D PREROUTING -p TCP --dport smtp -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -D PREROUTING -p TCP --dport pop3 -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -D PREROUTING -p TCP --dport imap -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -D POSTROUTING -o $IFWAN -j MARK --set-mark $TABELA
    $IPTABLES -t mangle -D POSTROUTING -j CONNMARK --save-mark
    $IPRULE del fwmark $TABELA table $TABELA
}

function do_start() {
    set_route
    set_rule
}

function do_stop() {
    unset_rule
    unset_route
}

case $1 in
    start)
        printf "%s\n" "Iniciando o roteamento"
        do_start
        exit
    ;;
    stop)
        printf "%s\n" "Parando o roteamento"
        do_stop
        exit
    ;;
    restart)
        printf "%s\n" "Reiniciando o roteamento"
        do_stop
        do_start
        exit
    ;;
    *)
        printf "%s\n" "Uso: ${SCRIPT} (start|stop|restart)"
        exit
    ;;
esac

Links Sobre o Assunto

Linux Advanced Routing & Traffic Control
Linux Packet Filtering and Iptables

Regex no Java. Usando Pattern e Matcher

Category : Java, Regex

No artigo anterior demonstrei a utilização dos quantificadores regex para buscar um padrão simples de uma tag HTML. Continuarei com o exemplo demonstrando a utilização das expressões regulares na plataforma Java.

Para começar crie uma classe chamada Conexao no pacote ex.regex. Esta classe encapsula um BufferedReader para leitura do stream de conexão com uma URL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package ex.regex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

class Conexao {

    private BufferedReader reader;

    Conexao(URL url) throws IOException {
        reader = new BufferedReader(new InputStreamReader(url.openStream()));
    }

    String ler() throws IOException {
        return reader.readLine();
    }

    void fechar() throws IOException {
        reader.close();
    }

    @Override
    protected void finalize() throws Throwable {
        reader.close();
    }
}

Esta classe recebe no construtor um objeto URL e injeta o InputStreamReader criado pelo método openStream() deste objeto no BufferedReader para posterior leitura. Os métodos ler() e fechar() são bem sugestivos.

Agora é hora de criar a classe ExemploRegex no pacote ex.regex. Incialmente apenas com três membros privados. O primeiro será uma String constante que reprensenta nosso regex. O segundo um objeto Pattern e o terceiro um objeto Matcher. As classes Pattern e Matcher fazem parte do pacote java.util.regex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ex.regex;

import java.io.IOException;
import java.io.Console;
import java.net.URL;  
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ex.regex.Conexao;

public class ExemploRegex {
    private final String REGEX = "<img src=['\"]([^'\"]+)";
    private final Pattern pattern = Pattern.compile(REGEX);
    private final Matcher matcher = pattern.matcher("");
}

“Em resumo um objeto Pattern é uma expressão regular compilada que pode ser aplicada a qualquer numero de strings, o objeto Matcher é uma instância individual desta expressão regular sendo aplicada a uma string específica.”(Mastering Regular Expressions, FRIEDL, JEFFREY E. F.)

Através do método matcher(), criamos um novo objeto Matcher. Este método é interessante, pois recebe um CharSequence (String, StringBuffer, etc) como parâmetro. O objeto matcher trabalha como o motor de buscas que confronta a sequência de caracteres com nosso Pattern. A classe Matcher oferece uma rica API para buscar e retornar resultados de buscas com regex.

Até agora tudo o que temos é nossa regex de captura de URL de imagens e uma sequência de caracteres vazia. Implementaremos na classe ExemploRegex o método privado processarURL().

1
2
3
4
5
6
7
8
9
10
11
12
    private void processarURL(String url) throws IOException {
        String s;
        Conexao cn = new Conexao(new URL(url));

        while ((s = cn.ler()) != null) {
            matcher.reset(s);
            while (matcher.find()) {
                System.out.println(matcher.group(1));
            }
        }
        cn.fechar();
    }

O método processarURL() cria um objeto Conexao para a URL desejada e percorre linha a linha o Reader.

A chamada do método reset() da classe Matcher descarta todas as informações de estado do nosso Matcher informando um novo CharSequence como parâmetro para reiniciarmos nossa pesquisa. O novo CharSequence é a próxima linha obtida do stream.

O método find() tenta encontrar na sequência um padrão equivalente ao Pattern usado para construir este Matcher. Quando encontrado, o primeiro grupo da expressão é impresso, através da chamada a group().

Capturando Grupos

Em regex, os grupos de captura são definidos por parênteses e numerados sequencialmente da esquerda para direita, sendo que o grupo zero representa o Pattern inteiro.

Grupos podem causar um pouco de sobrecarga nas expressões regulares mais complexas pois criam uma backreference para a expressão dentro do grupo, o que pode prejudicar o desempenho das buscas. Sempre sinalize os grupos que não deseja capturar da seguinte maneira com (?:).

Por exemplo: num Matcher criado por um Pattern com expressão regular “(?:Guarda)-(roupas|chuva)”, a chamada do método group(1) retornaria a palavra roupas ou chuva. Grupos marcados com (?:) são ignorados (não são armazenados e não criam backreferences).

Voltando a classe ExemploRegex, agora precisamos de um método main que nos permita entrar com uma URL para ser processada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static void main(String[] args) throws IOException {

        Console console = System.console();
        if (console == null) {
            System.err.println("O console não pôde ser encontrado.");
            System.exit(1);
        }

        ExemploRegex exr = new ExemploRegex();
        String url = "";

        while (!url.equalsIgnoreCase("sair")) {
            url = console.readLine("Entre com a URL do documento (CTRL+C ou 'sair' para terminar): ");
            exr.processarURL(url);
        }
    }

Verificamos o console e em seguida iniciamos uma iteração que aguarda a entrada da URL do documento para processar. Execute a classe e experimente entrar com a URL de um site como http://www.gamespot.com/pc e veja o resultado.

Considerações Finais

A utilização de expressões regulares combinando as classes Pattern e Matcher do pacote java.util.regex abre um novo leque para o desenvolvedor que precisa encontrar padrões numa sequência de texto com formato não padronizado.

Com elas pode-se encontrar padrões específicos de maneira elegante e simples. Veja quantos operadores condicionais foram necessários para encontrar o que queríamos.

O exemplo apresentado é bem simples e você pode expandí-lo para uso próprio.

Links recomendados:

http://aurelio.net/er/
http://www.regular-expressions.info/brackets.html
http://java.sun.com/javase/6/docs/api/java/util/regex/package-summary.html
http://java.sun.com/docs/books/tutorial/essential/regex/index.html

Livros Recomendados

Mastering Regular Expressions
FRIEDL, JEFFREY E. F.
OREILLY & ASSOC

Real World Regular Expressions With Java 1.4
HABIBI, MEHRAN
APRESS

Regex: Cuidado Com os Quantificadores Gananciosos!

Category : Java, Regex

Para um desenvolvedor ou mesmo um adminstrador de sistemas o uso das expressões regulares é uma excelente ferramenta em diversas situações, como:

  • Localizar sequências de texto condicionalmente;
  • filtrar a saida de logs e comandos do sistema;
  • fazer parsing simplificado em arquivos e sequências de texto.

Não pretendo explicar o básico sobre regex aqui, pois existem excelentes tutorias sobre o assunto na Internet, basta dar uma pesquisada no Google. Entretanto vou mostrar uma pequena armadilha que os quantificadores nos fazem cair.

Buscando a URL de uma imagem numa tag HTML.

Suponhamos que você esteja procurando pelo endereço de uma imagem dentro de uma tag IMG do HTML na seguinte sequência de texto:

  • <img src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem’/>

Você poderia utilizar uma regex simples para capturar a URL, como:

  • <img src=’.+’

Mas se surpreenderia ao ver o resultado:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem

A nossa busca retornou uma sequência muito além da nossa url

O que aconteceu de fato?

Os quantificadores regex ?, + e * sozinhos são chamados de quantificadores gananciosos (greedy), e repetem a sentença antecessora quantas vezes possível. No caso da tag, inicialmente encontramos o literal src=’, em seguida o motor regex é instruído a procurar qualquer caractere nenhuma ou muitas vezes quantas vezes possível .*, o que nos leva até o fim da nossa sequência de texto. O resultado até aqui seria:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem’>

Mas ainda falta um token, a aspas de fechamento. A busca seria considerada como falha neste ponto, porém o motor regex a partir de agora volta procurando por nossa aspas de fechamento. No primeiro passo ele encontra o sinal > e o descarta, no segundo ele encontra uma aspas e dá a busca como bem sucedida com o seguinte resultado:

  • src=’http://blog.sobreira.eti.br/image.jpg’ alt=’Imagem

Um pouco diferente do que imaginamos a princípio.

Como resolver este problema?

A resposta para este problema é a utilização dos quantificadores relutantes (lazy). Estes basicamente são a representação do operados ganancioso junto de uma interrogação: ??, ?+ e ?*. Diferente dos quantificadores gananciosos, os operadores relutantes repetem a pesquisa o mínimo de vezes possível, no mínimo uma vez. Primeiro encontra o h, em seguida verifica se o próximo caractere é ‘. O que falha, pois o caractere seguinte é t. O motor então continua expandindo a busca por ‘ enquanto a pesquisa falhar.

Então o resultado para:

  • <img src=’.+?’

é:

  • <img src=’http://blog.sobreira.eti.br/image.jpg

Bem próximo do que desejamos.

Não exagere no uso dos operadoes relutantes!

Os operadores relutantes têm baixo desempenho pois ficam indo e voltando (backtracking) a cada iteração. “Não encontrei aspas, então volto e verifico se o próximo caractere é um símbolo qualquer”. É como dar um passo pra frente, um passo pra trás e dois passos pra frente. Uma solução simples e comumente utilizada nestes casos é utilizar a negação junto com o quantificado ganancioso. Por exemplo:

  • <img src=’[^']+

Isto permite o motor seguir em frente enquanto o segundo caractere de apas não é encontrado. Quando encontrado a busca termina sem retornos. A operação falha retornando nossa URL completa. Este tipo de busca oferece excelente desempenho. Mais informações aqui.

Utilizando grupos.

Para isolar as partes da expressão encontradas por nossa expressão regular, podemos utilizar grupos de captura, identificados pelo delimitador ().

  • <img src=’([^.*]+)

A expressão regular anterior isola nossa url num grupo. Em Java os grupos são acessados pelo método group() da classe Matcher (pacote java.util.regex). Os grupos são numerados da esquerda para direita, sendo 0 (zero) a sentença como o todo. Na nossa regex anterior group(1) retornaria a URL integralmente.

No próximo artigo mostrarei como fazer buscas regex em sequência de caracteres (CharSequence) no Java com as classes Pattern e Matcher do pacote java.util.regex. Até lá!

Operadores utilizados nos exemplos:

  • Repete o item anterior zero ou muitas vezes (possessivo): *
  • Repete o ítem anterior uma ou muitas vezes (possessivo): +
  • Marca o ítem anterior como opcional (possessivo): ?
  • Repete o item anterior zero ou muitas vezes (relutante): *?
  • Repete o ítem anterior uma ou muitas vezes (relutante): +?
  • Nega os caracteres dentro da classe: [^]

Referência:

http://www.regular-expressions.info/
http://java.sun.com/javase/6/docs/api/java/util/regex/package-summary.html

SCJP, Eu passei!

Category : Pessoal

Hoje conquistei a minha certificação SCJP6. O Score foi 85%, eu pretendia conseguir no mínimo 90% das questões mas não foi dessa vez. Segue o Score detalhado:

  • Declarations, Initialization and Scoping: 100%
  • Flow Control: 81%
  • API Contents: 71%
  • Concurrency: 100%
  • OO Concepts: 70%
  • Collections  Generics: 100%
  • Fundamentals: 77%

Obviamente me preocupei tanto com as temerosas threads e os mínimos detalhes sobre conjuntos e genéricos, que relaxei nos outros tópicos.

Já vi várias pessoas criticando estas certificações, talvez porque ainda exista a cultura de que o profissional certificado tem mais valor que o não-certificado em algumas empresas. Eu não concordo de maneira alguma com isso. Bons profissionais são bons profissionais. Profissionais certificados, são apenas profissionais certificados. Certificações provam que você foi capaz de aprender um assunto.

Uma certificação de software é uma boa meta de estudo. Eu sempre quis aprender Java e seus fundamentos de forma sólida, e que o resto fosse apenas usar API’s, independente de IDE’s. A SCJP me obrigou a não sair do foco, aprender os fundamentos e não cometer erros estúpidos no uso da linguagem. Além disso pude trocar informações com outros profissionais, o que me levou a estudar não só o Java, mas os fundamentos da orientação a objetos, aplicação de padrões de projeto e técnicas de melhoria de código.