Página 8 de 16

O TDD está morto?

Test Driven Development (Desenvolvimento Orientado a Testes) é uma metodologia de desenvolvimento de software bastante popular, que é centrada (adivinhe) em testes.

No entanto, apesar de muito propagada, há várias críticas a essa metodologia por profissionais e pensadores de destaque.

Por esses dias rolou uma conversa bastante interessante sobre o assunto entre, nada mais nada menos, que Martin Fowler (que elaborou a ideia de Injeção de Dependência), Kent Beck (idealizador do TDD e do framework JUnit) e David Heinemeier Hansson (criador do Ruby on Rails).

Vamos analisar os pontos altos da conversa nos próximos tópicos. Mas antes, uma breve revisão sobre TDD…

Como o TDD funciona

Ao contrário do processo “tradicional” de desenvolvimento que envolve Especificação, Implementação e Testes, o TDD começa pelos testes em cima das interfaces do sistema.

A princípio todos os testes irão falhar. O objetivo do desenvolvedor é fazer com que eles sejam executados com sucesso. Em tese, isso garante o foco no que realmente deve ser feito e fornece uma medida mais real do progresso.

Os testes utilizados no TDD geralmente são Testes Unitários. Ambos os conceitos estão muito relacionados, mas não são sinônimos.

Testes Unitários

Testes Unitários são aqueles onde uma única função ou método do sistema é testada de cada vez. Em geral esses testes são automatizados.

Isso pode parecer simples, mas implica em que a rotina em questão não pode depender de outras rotinas, caso contrário o teste é sujeito a falhas de terceiros.

Dessa maneira, a fim de testar as rotinas que naturalmente possuem dependências de outras classes e métodos, de recursos como bancos de dados ou arquivos e outros, os desenvolvedores criam simulações desses elementos para que a rotina a ser testada funcione, porém não dependa diretamente desses outros elementos durante o teste.

Este é o conceito de mock, isto é, algo que imita o elemento original.

Por exemplo, um método que normalmente retorna um valor do banco de dados poderia ser substituído por um método que retorna um valor fixo num teste em particular. Cada linguagem, framework e ferramenta deve prover formas adequadas para se fazer isso, sendo inclusive uma forma de medir a qualidade da mesma.

Benefícios desta abordagem

Escrever os testes antecipadamente e então desenvolver com base neles traz várias vantagens:

  • Melhora o entendimento dos requisitos, já que para escrever o teste é necessário que o desenvolver entenda logo de início o que o sistema deve fazer.
  • Provê um objetivo claro a ser alcançado durante o desenvolvimento, isto é, fazer os testes executarem sem erros.
  • Aumenta a visibilidade do que está ou não pronto, já que os testes servem como indicadores mais confiáveis do que algo que está implementado e “só falta testar”.
  • A automação dos testes possibilita a Integração Contínua com certa garantia do funcionamento do sistema, já que todos os testes unitários podem ser reexecutados como testes de regressão sempre que necessário.

Desvantagens

Como regra geral, tudo que traz vantagens tem seus pontos negativos. Sempre temos que tomar cuidado com certos “evangelistas” que irão fazer uma tecnologia ou metodologia parecer não ter um lado negativo ou impacto zero.

Isso se aplica também ao TDD. Vejamos algumas considerações sobre ele:

Aumento do esforço

Será necessário gastar muito tempo para criar os testes e todos os mocks, além da infraestrutura de Integração Contínua. É claro que isso pode ser visto como um investimento em qualidade ao invés de uma pena a ser paga, mas na prática nem todos podem se dar a esse luxo.

Aumento da complexidade

A arquitetura acaba sempre mais complexa, usando muita Injeção de Dependências e outras técnicas de desacoplamento.

Mudanças nos requisitos são muito mais custosas, já que além do código do sistema em si, todos os testes e mocks envolvidos deverão ser reescritos.

Se o arquiteto de software não souber o que está fazendo, a arquitetura do sistema pode acabar cheia de gambiarras.

Intrusividade no código principal

Em muitas situações, será necessário programar de uma forma específica para que um certo código possa ser testado.

A arquitetura da aplicação muitas vezes acaba sendo então afetada por elementos e fatores que, de outra forma, não fariam parte de sua natureza.

Enfim, o design da aplicação acaba sendo influenciado pelos testes, o que nem sempre é desejável, já que é mais uma coisa a ser levada em conta e pode desviar o foco do arquiteto.

Testar tudo é quase impossível

Muitas tecnologias são difíceis de testar com testes unitários ou mesmo com testes de integração.

Pense na interface do usuário, por exemplo. Quantas delas possibilitam a execução da interface de forma “desacoplada”? Existem ferramentas como o Selenium para sistemas web, mas com elas também vêm diversas outras limitações e dependências.

O TDD está morto?

Vimos que todo benefício traz um custo associado. É este todo o ponto da discussão sobre o TDD estar morto. O custo de testar tudo e criar os mocks necessários para todos os testes vale a pena? Vejamos alguns comentários a seguir.

David Hansson inicia a argumentação dizendo que muitas pessoas não conseguem trabalhar com TDD porque não faz muito sentido para determinados tipos de trabalho. Ele toca no ponto da necessidade de se ter muitos mocks e que isso acaba tornando a codificação difícil.

Kent Beck aproveita o gancho e relata de casos onde a equipe não conseguia refatorar o código porque os testes estavam tão acoplados com a implementação, com dois ou três níveis de mocks, que qualquer alteração afetava inúmeros deles. Ele também afirma que há cenários onde o TDD se aplica melhor, por exemplo, quando os requisitos do software a ser desenvolvido são claros o suficiente para escrevê-los em testes diretamente. Por outro lado, há situações onde simplesmente o desenvolvedor vai descobrindo aos poucos aquilo que deve fazer. Isso é recorrente na criação de frameworks ou algoritmos que precisam analisar dados não estruturados.

Fowler argumenta ainda que a maior vantagem do TDD é um código que consegue testar a si mesmo. Só isso poderia também ser alcançado de outras formas. Ele também comenta sobre aqueles que dizem que “você não está fazendo teste unitário de verdade” porque existem algumas dependências durante o teste. Segundo ele, o importante não é a pureza de uma definição, mas que o teste funcione e seja útil, até porque há variações da definição de testes unitários.

Conclusões

É importante entender que TDD não é a “bala de prata” do desenvolvimento de software. Ele traz consigo vários desafios e problemas em potencial, principalmente se não há pessoas suficientemente experientes na equipe.

Testar é essencial, mas para cada projeto podem haver diferentes formas de testes, cujo busto-benefício deve ser avaliado para cada caso.

O teste não deve ser um objetivo em si mesmo, ainda que adotemos o TDD. Ele apenas tem o objetivo de garantir que estamos entregando o que o cliente pediu.

O tempo e o esforço que são investidos em testes também devem ser avaliado. Isso afeta diretamente a qualidade, mas a qualidade tem o seu preço. Quem irá pagar o preço da qualidade?

Quanto a testes unitários, não devemos buscar toda a pureza possível. O importante é que ele teste, ou seja, funcione para aquela situação específica.

Concluindo, o conselho em geral é: use TDD com moderação.


Para quem consegue entender Inglês, aqui vai o link do Hangout com essas grandes personalizados:

Is TDD dead?

Análise e Design Orientados a Objetos – uma visão prática

UML_logo Estudantes e praticantes de Engenharia de Software geralmente tem dúvidas sobre como modelar um sistema de forma efetiva.

Atualmente, a abordagem mais recomendada é a Orientada a Objetos, preferencialmente usando UML, já que essas técnicas são amplamente reconhecidas e propagadas.

O problema é que muita confusão ocorre por falta de conhecimento sobre os objetivos da modelagem de sistemas, as fases de um projeto de desenvolvimento de software e os diferentes níveis de modelagem possíveis. Por exemplo: quais classes devem ser incluídas num Diagrama de Classes? Devemos colocar as classes do framework? Como identificar as classes necessárias em um sistema?

O ponto de partida é não misturar a Análise do problema com o Design (projeto) da solução ou com a Implementação tecnológica.

Em resumo, vamos analisar nos tópicos abaixo como aplicar a modelagem orientada a objetos em diferentes fases de um projeto, tendo em mente alcançar objetivos concretos com a modelagem, além das diferentes aplicações dos modelos utilizados.

Análise

O analista é o profissional responsável por identificar um problema a ser resolvido ou necessidade a ser atendida e elicitar os requisitos para a criação de uma solução.

O conjunto de requisitos define o que o sistema deve fazer para atender às necessidades identificadas.

Baseando-se nos requisitos, o analista continua o processo de Análise identificando em alto nível de quais funcionalidades o sistema deverá possuir para atender aos requisitos.

Uma solução comum para mapear cada funcionalidade é através de Casos de Uso (não confundir com o Diagrama da UML). Um Caso de Uso é uma espécie de passo-a-passo da interação entre usuário e sistema, embora esse conceito possa variar um pouco. Além disso, geralmente ele descreve as pré-condições necessárias para a correta execução e as pós-condições, que são os resultados da ação realizada.

Note que ainda estamos em alto nível e nada aqui tem a ver com a solução tecnológica.

Continuando, o analista treinado em Orientação a Objetos e UML irá modelar o conhecimento sobre o domínio e o problema utilizando os diagramas adequados, que geralmente são: Diagrama de Caso de Uso, Diagrama de Atividades, Diagrama de Classes e Diagrama de Estados.

O Diagrama de Caso de Uso é uma representação visual simples das interações do sistema com o mundo externo. Os atores que interagem com o sistema são representações de usuários, outros sistemas ou qualquer entidade externa ao sistema que se comunique com o mesmo. Este diagrama não exclui a necessidade de mapear em detalhes os casos de uso conforme mencionado anteriormente.

O Diagrama de Atividades representa os passos do caso de uso numa espécie de fluxograma, incluindo bifurcações de cenários alternativos, cenários de erro, etc. Nem todos os cenários precisam ser representados no mesmo diagrama.

O Diagrama de Classes, neste estágio de um projeto, deve incluir apenas as classes de domínio, sem qualquer referência à tecnologia que será usada na implementação. Poderíamos chamar este diagrama de Diagrama de Classes de Domínio. A função do diagrama é representar as entidades necessárias e o relacionamento entre elas. Em suma, é a forma moderna e orientada a objetos do Diagrama de Entidade-Relacionamento (DER), embora ambos possam ser usados concomitantemente. No entanto, o DER geralmente é associado à modelagem estruturada.

O Diagrama de Estados é usado para as entidades do sistema que seguem um fluxo de estados. Por exemplo, uma parcela pode estar “em aberto”, “liquidada”, “em atraso”, “em prejuízo”. Este diagrama representa os estados e como ocorrem as transições entre eles.

Com tudo isso, o analista pode validar a solução verificando se as classes e os casos de uso atendem aos requisitos iniciais. Por exemplo, se houver um requisito de que “o gerente poderá extrair um relatório com o total de produtos vendidos no mês”, o analista deve olhar se a classe Produto possui um relacionamento “navegável” com Venda e ItemVenda e se é possível extrair a informação de totalização das vendas de forma lógica. Ele também pode adicionar os métodos e atributos importantes às classes de modo a atender aos requisitos.

Design (Projeto)

Com base em todas essas informações, entram em ação os arquitetos e desenvolvedores para propor uma solução tecnológica para o problema. Isso não necessariamente vem em sequência, muito pode ocorrer em paralelo.

Os projetistas técnicos poderão criar vários outros diagramas para representar o que será implementado. O Design pode ser feito de forma agnóstica, isto é, sem considerar quais tecnologias, frameworks e bibliotecas serão usadas. No entanto, creio que é mais produtivo modelar a divisão de componentes e classes já pensando um pouco na implementação, de forma a não gerar mais um gap de informação.

Os diagramas mais relevantes e geralmente usados são:

  • Diagrama de Componentes: representa a divisão em alto nível dos componentes principais do sistema. A divisão não representa a estrutura de pastas dos arquivos do projetos, mas é uma divisão lógica de responsabilidades.
  • Diagrama de Deployment: uma representação do ambiente onde o sistema será executado, incluindo servidores, bancos de dados, replicação, proxies, etc.
  • Diagrama de Sequência: para uma determinada ação no sistema, este diagrama representa a interação entre diversos objetos através das chamadas executadas e do retorno, permitindo visualizar a sequência de chamadas no decorrer tempo.

Note que cada um dos diagramas citados podem ser produzidos para vários casos diferentes. Quando falamos em Diagrama de Classes ou Diagrama de Componentes, não há necessariamente um único diagrama que representa o sistema como um todo. A representação pode ser feita em níveis diferentes, por exemplo, um mostrando os componentes gerais do sistema e outros diagramas mostrando a estrutura interna de cada componente individualmente. A representação também pode ser feita em contextos diferentes, por exemplo, vários diagramas para representar apenas o necessário para uma funcionalidade importante, ignorando classes e pacotes não relevantes naquele contexto.

Implementação

A implementação deve seguir o que foi definido no Design, porém não significa que cada método, classe, pacote e componente deve ser mapeado um-para-um no projeto “físico”, em seus arquivos e estrutura de diretórios.

O programador deve ter a liberdade de encontrar a melhor solução para atender ao que foi solicitado com a estrutura que ele desejar. Seria péssimo, do ponto de vista de boas práticas, impor cada detalhe do que deve ser implementado. Se isso fosse possível, não precisaríamos de programadores, mas de um gerador de código.

Construindo a ponte

Ao estudar com atenção as “fases” (entre aspas porque não são uma sequência linear) de um projeto de desenvolvimento de software, é possível notar que existe um grande salto (gap) entre cada uma delas. Uma analogia comumente usada nos livros de Engenharia de Software consiste em construir uma ponte entre o que o cliente precisa e a solução tecnológica.

Ainda hoje, a Engenharia de Software é uma disciplina um tanto imatura. Não temos uma forma padronizada de construção como a Civil ou Elétrica para nos apoiar. A validade de um Modelo de Análise, um Modelo de Design ou da solução implementada depende quase exclusivamente de fatores humanos, como a capacidade de comunicação e entendimento dos analistas, além da capacidade técnica dos desenvolvedores.

Não existem regras definitivas sobre como e em qual nível modelar, assim como não há regras sobre como traduzir uma necessidade em um requisito, um requisito em um modelo e um modelo numa implementação.

A UML foi um grande avanço, mas os diversos diagramas sempre variam em nível de detalhe, abrangência e muitos outros fatores de projeto para projeto, de equipe para equipe e de indivíduo para indivíduo.

Princípios de modelagem

Mesmo com as últimas afirmações acima, não quero ser pessimista. Embora não haja uma resposta definitiva para a modelagem de sistemas, existem alguns princípios que podem nos guiar:

  • Coloque a comunicação em primeiro lugar. O objetivo de um diagrama é comunicar informação e não simplesmente ser um espelho do código. Se um diagrama não comunica algo útil, não perca tempo com ele. Considere sua equipe e o seu projeto e faça os diagramas que forem relevantes com os detalhes relevantes para que as pessoas saibam o que estão fazendo. O seu time consegue se reunir numa mesa e discutir um diagrama, rabiscando-o e usando-o como base para a conversa?
  • Não faça diagramas de tecnologias específicas. Se alguém quiser saber como Servlets, Rails ou Django funcionam, é melhor comprar um livro. Você só vai confundir as pessoas. Já vi muitos diagramas por aí que nada mais são do que o modelo MVC com nomes diferentes.
  • Verifique se o diagrama atende os requisitos. O seu diagrama deve ser útil não só para entender o que deve ser feito, mas também para validar se a sua solução atende ao que o cliente precisa. Faça testes mentais lógicos, olhando para as classes, métodos e relacionamentos, verificando se elas tem motivo de estarem ali, se para um certo cenários você consegue extrair os dados necessários, etc.

Como mencionei algumas vezes, a Análise, o Design e a implementação provavelmente serão feitas muitas vezes durante o ciclo de desenvolvimento. Não espere ter tudo certo no começo. Investir tempo demais em detalhamento é ruim, vários autores já alertaram.

Identificando as classes necessárias

Existe um método para identificação de classes de domínio em potencial que consiste na análise de um texto à procura de substantivos. Esta técnica pode ser útil quando você não tem ideia do que está fazendo e quer algumas ideias iniciais. No entanto, é péssima porque em muitos lugares é ensinada como uma forma “burra” de extrair informação.

Volta e meia ouço alguém com a velha ideia de criar um interpretador mágico, um tipo de Inteligência Artificial, capaz de gerar um sistema com base num texto descrevendo as necessidades do usuário. Devaneios à parte, é melhor nos concentrarmos no que é real hoje.

Aliás, é comum usarmos a palavra “extrair” ou “levantar” indevidamente. Quando criamos um sistema, não extraímos ou levantamos os requisitos e as classes necessárias como se elas já existissem ali, ocultas de alguma forma.

Quanto aos requisitos, o termo “elicitar” é mais adequado, com o sentido de descobrir e descrever. Quanto às classes, nós simplesmente decidimos de forma espúria quais delas o sistema deverá conter de modo a atender às necessidades. Se verificarmos que elas não atendem aos requisitos, nós as modificamos para que o façam.

Tudo isso trata-se de um processo criativo e não de um processo de extração como se faz com matéria prima. Por “criativo”, não pense em arte pós-moderna, mas num processo criativo metódico e até científico.

Minha recomendação é identificar, através dos requisitos, quais dados são necessários para que o sistema funcione, assim como o relacionamento entre elos. Já dizem os DBAs: os dados são o coração do sistema. Assim conseguimos as classes que representam as entidades necessárias.

Depois, com base nas funcionalidades que o sistema deve ter, pode-se definir classes que serão responsáveis por tratar essas funcionalidades.

Considerações finais

A Engenharia de Software ainda possui muitos desafios pela frente para se tornar realmente uma engenharia no sentido completo da palavra.

Muitos hoje (inclusive eu) consideram-se mais artesãos tecnológicos do que engenheiros propriamente ditos. Isso é bom em certo sentido, mas também abre muitas brechas para as “artes abstratas”. 😉

Enfim, não há uma resposta definitiva para o sucesso na modelagem de um sistema, mas espero ter exemplificado bem um caminho que aprendi ao longo de alguns anos estudando e refletindo sobre como desenvolver software adequadamente, não apenas do ponto de vista técnico.


Este artigo foi baseado na minha resposta no StackOverflow em Português!

Distributed transactions and concurrent processing with Atomikos

distributed Atomikos is a piece of software written in Java that, among other things, implements JTA (Java Transaction API) e XA (eXtended Architecture, supports distributed transaction processing).

In general, a transaction is associated to the current thread, so the methods invoked during a request in a JEE server can share the same transaction.

However, an interesting question on StackOverflow raised the possibility of dividing an atomic operation composed by different tasks between threads with a single global transaction.

Well, in order to bypass the default architecture we’ll need invoke Atomikos XA API directly to include the various Data Sources from each thread in the main transaction.

I did a simple example implementing that. The project is available on my GitHub account.

Implementation

Firstly, we have the DataSource and TransactionManager initialization using Atomikos API in class AtomikosDataSource. Here is the relevant excerpt:

// Atomikos implementations
private static UserTransactionManager utm;
private static AtomikosDataSourceBean adsb;

// initialize resources
public static void init() {
    utm = new UserTransactionManager();
    try {
        utm.init();
        adsb = new AtomikosDataSourceBean();
        adsb.setMaxPoolSize(20);
        adsb.setUniqueResourceName("postgres");
        adsb.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
        Properties p = new Properties();
        p.setProperty("user", "postgres");
        p.setProperty("password", "0");
        p.setProperty("serverName", "localhost");
        p.setProperty("portNumber", "5432");
        p.setProperty("databaseName", "postgres");
        adsb.setXaProperties(p);
    } catch (SystemException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

After that, I implemented the thread named Processamento (Processing) that receives the main transaction instance (Transaction). The interface Callable defines this thread as some kind of task that returns an Integer value. Here is the code:

private static class Processamento implements Callable<Integer> {

    private int id;
    private boolean falhar; //fail or not?
    private Transaction transaction;

    public Processamento(int id, boolean falhar, Transaction transaction) {
        this.falhar = falhar;
        this.transaction = transaction;
        this.id = id;
    }

    public Integer call() throws Exception {
        if (falhar) {
            //fail unexpectedly
            throw new RuntimeException("Falhou inesperadamente!"); 
        }

        //enlist xa connection
        XAConnection xac = AtomikosDataSource.getDS().getXaDataSource().getXAConnection();
        synchronized (transaction) {
            transaction.enlistResource(xac.getXAResource());
        }

        //normal execution, update row with OK
        Connection c = xac.getConnection();
        Statement s = c.createStatement();
        s.executeUpdate("update teste set processado = 'ok' where id = " + id);
        s.close();
        c.close();

        //delist xa connection
        synchronized (transaction) {
            transaction.delistResource(xac.getXAResource(), XAResource.TMSUCCESS);
        }
        return id;
    }

}

Notice, instead of using JTA, I’m using Atomikos XA API directly.

The call to AtomikosDataSource.getDS().getXaDataSource().getXAConnection() gets a XA connection, which is added to the main transaction with the command transaction.enlistResource(xac.getXAResource()). This operation is called enlistment. At the end of the process, there is a delistment.

I synchronized a few commands because I got random NullPointerException errors during tests. I didn’t investigate if it’s a bug or by design, that is, Transaction object is not synchronized by default.

Finally, I implemented a method to create five instances of the thread above and then get the results. If one of them fail, the global transaction is rolled back. This is the code:

public static int processar(boolean falhar) {
    int ok = 0;
    Transaction transaction = null;
    try {

        //start transaction
        AtomikosDataSource.getTM().begin();
        transaction = AtomikosDataSource.getTM().getTransaction();

        //create thread pool
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Callable<Integer>> processos = new ArrayList<Callable<Integer>>();

        //create 5 threads, passing the main transaction as argument
        for (int i = 0; i < 5; i++) {
            //if falhar == true, fail the fifth thread
            processos.add(new Processamento(i + 1, i == 4 && falhar, transaction));
        }

        //execute threads and wait
        List<Future<Integer>> futures = executor.invokeAll(processos);

        //count the result; get() will fail if thread threw an exception
        Throwable ex = null;
        for (Future<Integer> future : futures) {
            try {
                int threadId = future.get();
                System.out.println("Thread " + threadId + " sucesso!");
                ok++; 
            } catch (Throwable e) {
                ex = e;
            }
        }

        if (ex != null) {
            throw ex;
        }

        //finish transaction normally
        transaction.commit();

    } catch (Throwable e) {

        e.printStackTrace();
        try {
            //try to rollback
            if (transaction != null) {
                AtomikosDataSource.getTM().rollback();
            }
        } catch (IllegalStateException e1) {
            e1.printStackTrace();
        } catch (SecurityException e1) {
            e1.printStackTrace();
        } catch (SystemException e1) {
            e1.printStackTrace();
        }

    }
    return ok;
}

Notice that some methods have a parameter named falha (fail). It’ll be used to create a scenario where one of the threads will generate an error and force a rollback of all changes made by other threads.

The processar() (process) method returns the number of “successes”, i.e, threads executed without errors, independently if the transaction was committed or rolled back. It’ll be also used in tests.

Tests

I did tests for both success and error scenarios in order to validate the solution.

In the success scenario, each one of the five threads updates a row from TESTE table with the value ok, then the transaction is committed.

In the error scenario, the last thread always throw an exception, forcing the rollback of all operations. Notice that the last thread created it’s not necessarily the last one executed.

The test code is very simple. Look:

public class AtomikosTest {

    @BeforeClass
    public static void init() {
        //create atomikos transaction manager and data source
        AtomikosDataSource.init();

    }
    @Before
    public void reset() {
        //recreate data of TEST table
        AtomikosDAO.resetTable();
    }

    @AfterClass
    public static void shutdown() {
        //close atomikos resources
        AtomikosDataSource.shutdown();
    }

    @Test
    public void sucesso() {
        //process 5 rows in 5 threads
        int okParcial = AtomikosDAO.processar(false);
        //should return 5 successes
        Assert.assertEquals(5, okParcial);
        //confirms in table, count 5 ok's
        Assert.assertEquals(5, AtomikosDAO.countOk());
    }

    @Test
    public void fail() {
        //process 5 rows in 5 threads, one should fail
        int okParcial = AtomikosDAO.processar(true);
        //should return 4 successes
        Assert.assertEquals(4, okParcial);
        //confirms in table, count zero ok's due to rollback
        Assert.assertEquals(0, AtomikosDAO.countOk());
    }

}

Notes about configuration

In this project, I chose PostgreSQL to be the resource to be modified in the distributed transaction.

It was necessary enable the configuration called max_prepared_transactions in the configuration file postgresql.conf with a value greater than the number of participants in the distributed transaction. Otherwise, PostgreSQL won’t be able of participating in distributed transactions.

Final thoughts

Even though there’s a growing interest about NoSQL and even NewSQL, ACID transactions, as available on traditional RDBMS, are very important in many situations. It’s so true, that there are tutorials on how to simulate a transaction with two-phase commit in non-transactional databases like MongoDB.

Furthermore, it’s important to say that each participant of a distributed transaction must support XA protocol. Unfortunately, some database drivers and other data source implementations aren’t. So do your homework and research before start coding.


Este artigo foi baseado na minha resposta no StackOverflow em Português!

Transações distribuídas e processamento paralelo com Atomikos

distributed Atomikos é um software Java que, entre outras coisas, implementa os padrões JTA (Java Transaction API) e XA (eXtended Architecture, que suporta processamento de transações distribuídas).

Em geral, cada transação é associada à thread atual, de modo que os diversos métodos que atendem uma solicitação num servidor JEE podem compartilhá-la.

Entretanto, uma questão interessante do StackOverflow levantou a possibilidade de uma aplicação dividir uma operação atômica em tarefas delegadas a várias threads, porém compartilhando uma única transação global.

Bem, para fazer esse “desvio” da arquitetura original, a solução foi usar diretamente a API XA do Atomikos para incluir os DataSources das diferentes threads na transação principal.

Fiz um exemplo simples que implementa isso. O projeto está disponível no meu GitHub.

Implementação

Antes de mais nada, temos a inicialização do DataSource e do TransactionManager usando a API do Atomikos realizado na class AtomikosDataSource. Eis o trecho relevante:

// Atomikos implementations
private static UserTransactionManager utm;
private static AtomikosDataSourceBean adsb;

// initialize resources
public static void init() {
    utm = new UserTransactionManager();
    try {
        utm.init();
        adsb = new AtomikosDataSourceBean();
        adsb.setMaxPoolSize(20);
        adsb.setUniqueResourceName("postgres");
        adsb.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
        Properties p = new Properties();
        p.setProperty("user", "postgres");
        p.setProperty("password", "0");
        p.setProperty("serverName", "localhost");
        p.setProperty("portNumber", "5432");
        p.setProperty("databaseName", "postgres");
        adsb.setXaProperties(p);
    } catch (SystemException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

Depois, implementei uma thread chamada Processamento que recebe a instância da transação (Transaction) principal. A interface Callable define que a thread é um tipo de tarefa que retorna um valor Integer. Eis o código:

private static class Processamento implements Callable<Integer> {

    private int id;
    private boolean falhar;
    private Transaction transaction;

    public Processamento(int id, boolean falhar, Transaction transaction) {
        this.falhar = falhar;
        this.transaction = transaction;
        this.id = id;
    }

    public Integer call() throws Exception {
        if (falhar) {
            throw new RuntimeException("Falhou inesperadamente!");
        }

        //enlist xa connection
        XAConnection xac = AtomikosDataSource.getDS().getXaDataSource().getXAConnection();
        synchronized (transaction) {
            transaction.enlistResource(xac.getXAResource());
        }

        //normal execution, update row with OK
        Connection c = xac.getConnection();
        Statement s = c.createStatement();
        s.executeUpdate("update teste set processado = 'ok' where id = " + id);
        s.close();
        c.close();

        //delist xa connection
        synchronized (transaction) {
            transaction.delistResource(xac.getXAResource(), XAResource.TMSUCCESS);
        }
        return id;
    }

}

Note que, ao invés de usar o JTA, estou usando diretamente a API do XA implementada pelo Atomikos.

A chamada AtomikosDataSource.getDS().getXaDataSource().getXAConnection() recupera uma conexão do XA, a qual é adicionada à transação principal com o comando transaction.enlistResource(xac.getXAResource()). Esta operação é chamada de alistamento (enlistment). Ao final do processamento da thread, o alistamento é desfeito.

Sincronizei alguns trechos pois obtive aleatoriamente alguns NullPointerException nos testes. Não cheguei a averiguar se é um bug do Atomikos ou se é by design, isto é, o objeto Transaction não é thread-safe.

Finalmente, implementei um método que inicia cinco instâncias da thread de processamento listada acima e posteriormente colhe os resultados. Se uma delas falhar, a transação global é desfeita (rollback). Veja o código abaixo:

public static int processar(boolean falhar) {
    int ok = 0;
    Transaction transaction = null;
    try {

        //start transaction
        AtomikosDataSource.getTM().begin();
        transaction = AtomikosDataSource.getTM().getTransaction();

        //create thread pool
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Callable<Integer>> processos = new ArrayList<Callable<Integer>>();

        //create 5 threads, passing the main transaction as argument
        for (int i = 0; i < 5; i++) {
            processos.add(new Processamento(i + 1, i == 4 && falhar, transaction));
        }

        //execute threads and wait
        List<Future<Integer>> futures = executor.invokeAll(processos);

        //count the result; get() will fail if thread threw an exception
        Throwable ex = null;
        for (Future<Integer> future : futures) {
            try {
                int threadId = future.get();
                System.out.println("Thread " + threadId + " sucesso!");
                ok++; 
            } catch (Throwable e) {
                ex = e;
            }
        }

        if (ex != null) {
            throw ex;
        }

        //finish transaction normally
        transaction.commit();

    } catch (Throwable e) {

        e.printStackTrace();
        try {
            //try to rollback
            if (transaction != null) {
                AtomikosDataSource.getTM().rollback();
            }
        } catch (IllegalStateException e1) {
            e1.printStackTrace();
        } catch (SecurityException e1) {
            e1.printStackTrace();
        } catch (SystemException e1) {
            e1.printStackTrace();
        }

    }
    return ok;
}

Note que vários métodos possuem um parâmetro chamado falha. Ele será usado para gerar um cenário onde uma das threads irá gerar um erro e forçar o rollback das alterações das demais threads.

O método processar() retorna a quantidade de “sucessos”, isto é, threads que executaram sem erro, independentemente se a transação foi efetivada ou desfeita. Isso também será usado nos testes.

Testes

Fiz testes tanto de um cenário de sucesso quanto de falha para validar a solução.

No cenário de sucesso, cada uma das cinco threads atualiza uma linha da tabela TESTE com o valor ok e no final o método principal faz o commit da transação.

No cenário de falha, a última thread sempre lança uma exceção, forçando o rollback das demais. Note que a última thread criada não é necessariamente a última a ser executada.

O código de teste ficou muito simples. Veja:

public class AtomikosTest {

    @BeforeClass
    public static void init() {
        //create atomikos transaction manager and data source
        AtomikosDataSource.init();

    }
    @Before
    public void reset() {
        //recreate data of TEST table
        AtomikosDAO.resetTable();
    }

    @AfterClass
    public static void shutdown() {
        //close atomikos resources
        AtomikosDataSource.shutdown();
    }

    @Test
    public void sucesso() {
        //process 5 rows in 5 threads
        int okParcial = AtomikosDAO.processar(false);
        //should return 5 successes
        Assert.assertEquals(5, okParcial);
        //confirms in table, count 5 ok's
        Assert.assertEquals(5, AtomikosDAO.countOk());
    }

    @Test
    public void fail() {
        //process 5 rows in 5 threads, one should fail
        int okParcial = AtomikosDAO.processar(true);
        //should return 4 successes
        Assert.assertEquals(4, okParcial);
        //confirms in table, count zero ok's due to rollback
        Assert.assertEquals(0, AtomikosDAO.countOk());
    }

}

Notas sobre a configuração

Neste projeto, usei o servidor de banco de dados PostgreSQL como o recurso a participar da transação distribuída.

Foi necessário habilitar a configuração max_prepared_transactions no arquivo de configuração postgresql.conf com um valor maior que o número de participantes na transação distribuída. Sem isso, o PostgreSQL não será capaz de participar de transações desta natureza.

Considerações finais

Embora haja um crescente interesse sobre NoSQL e até NewSQL, transações ACID, como disponíveis nos SGBDRs tradicionais, são importantes em muitos cenários. Até por isso existem tutoriais sobre como simular uma transação com o conceito de two-phase commit em bancos de dados não transacionais como MongoDB.

Além disso, é importante ressaltar que cada participante de uma transação distribuída deve ser compatível com o protocolo XA. Infelizmente, alguns drivers de bancos de dados ou outras fontes de dados podem não ser compatíveis. Então, faça sua lição de casa e pesquise antes de sair implementando.


Este artigo foi baseado na minha resposta no StackOverflow em Português!

Vagas na GFT em Sorocaba e Alphaville

Não trabalho mais na GFT, porém o artigo permanece no site para referência.

Até onde eu sei, a GFT está contratando continuamente, então ainda dá para aproveitar as dicas aqui e aplicar para uma vaga lá, mas por favor não envie currículos para mim.

A GFT, empresa em que trabalho, está buscando profissionais para trabalhar com experiência Java e .NET, além de Gerentes de Projetos. A maioria das vagas é para a cidade de Sorocaba, mas há algumas de Java para Alphaville.

Por quê Sorocaba?

Para quem não sabe, eu decidi morar no interior. Em meu artigo "Morando no interior, trabalhando na capital" explico a dura rotina de morar no interior e trabalhar em São Paulo, além de listar algumas motivações que me levaram a fugir do caos urbano.

Você também não aguenta mais ficar de 2 a 5 horas no trânsito por dia? Não adianta esperar a construção de mais linhas de trens e metrô, novas linhas de ônibus e duplicação de avenidas. São Paulo não tem solução. Venha para o interior!

Além disso, Sorocaba possui várias universidades e faculdades, como UFSCAR, UNESP e FATEC. Ela fica a menos de 100 quilômetros São Paulo, sendo possível, por exemplo, fazer cursos na capital. Obviamente, não podemos comparar o mercado de trabalho com capitais e cidades metropolitanas maiores, mas Sorocaba é bem desenvolvida para uma cidade do seu porte.

Por quê a GFT?

Todos sabemos que existem poucas empresas boas para trabalhar fora das capitais brasileiras. A maioria não paga bem nem possui um plano de carreira decente. Em outras palavras: não há perspectiva de crescimento. Infelizmente, o que mais vejo é pessoas boas se formando no interior e migrando para inchar ainda mais a capital. Até quando São Paulo vai suportar isso?

Não querendo fazer propaganda, mas já fazendo, algumas empresas como a GFT nadaram um pouco contra a maré e decidiram investir no potencial dos profissionais do interior, ainda que mantendo uma unidade comercial mais próxima de São Paulo. Em minha opinião, empresas que investem nesse modelo merecem um reconhecimento.

A GFT é uma multinacional alemã e possui filiais espalhadas pela Europa e Estados Unidos. Aqui há vários projetos internacionais, então se você tiver com o Inglês afiado poderá não só praticar como também “precisar” viajar em situações específicas. Se o seu Inglês não é tão bom, junto com todos os benefícios há um curso de Inglês gratuito ministrado por uma das melhores escolas do país. Como toda boa empresa, o horário de trabalho é flexível e o ambiente não é estressante. Todas as quartas-feiras temos frutas grátis para incentivar a boa saúde.

Notas Pessoais

Posso dizer que não me arrependo da decisão de ter deixado a capital para morar em Sorocaba. Também estou gostando de trabalhar na GFT.

Há alguns dias conversei com um colega que também entrou na GFT há poucos meses. Ele veio da CI&T de Campinas. Ele disse que, comparando com o projeto em que ele estava alocado, a GFT está sendo um tanto melhor. Não quero com isso comparar as empresas, apenas dar uma noção de que a GFT não perde em nada para empresas conhecidas por serem boas para se trabalhar e que investem pesado em propaganda para divulgar isso.

Para mim tem sido um bom lugar para trabalhar. A única ressalva é que não é um ambiente ágil como uma startup ou uma empresa pequena. Como a maioria dos projetos é para o setor financeiro, há a rotineira burocracia. Nada que não seja comum quando trabalhamos com grandes instituições.

Finalmente, as vagas

Você pode conferir a lista de vagas neste link. Porém, não vá logo enviando seu currículo pelo site!

Antes de mais nada, veja o resumo das vagas*:

  • Desenvolvedor Java Pleno e Sênior: o candidato deve ter experiência com Java. Conhecer Spring bem e JPA são diferenciais.
  • Arquiteto Java: profissional com bastante experiência e Inglês fluente.
  • Engenheiro .NET: o candidato deve ter experiência na plataforma .NET e Inglês avançado.
  • Gerente de Projetos: profissional experiente para atuar em múltiplos projetos internacionais, incluindo reuniões com pessoas de vários países, com Inglês fluente.

* Leia com atenção a descrição das vagas clicando nos respectivos links pois este é um resumo que fiz por minha conta e risco.

Note que o Inglês é um grande diferencial em todas as vagas. Quanto maior o seu nível, melhor para você.

Além disso, há vagas para diferentes níveis de conhecimento. Se a empresa considerar que o seu perfil é adequado, ela vai encaixar você numa posição equivalente a sua experiência.

Quer uma ajudinha?

Todos sabem que enviar e-mail para o RH não surte tanto efeito quando uma indicação, certo?

Pois bem, se você quer entrar na GFT, estando em Sorocaba ou querendo vir para cá, entre em contato comigo pelo formulário do site ou pelo LinkedIn.

Eu mesmo irei analisar seu perfil e, se estiver dentro do que a empresa precisa, farei uma indicação ao RH e darei pessoalmente um retorno sobre o processo.

Caso tenha alguma dúvida, pode entrar em contato comigo pelos mesmos meios citados acima.

For my right to no loger use Mozilla Firefox

Alert: this article contains political issues and is based on personal opinions. If you are squeamish, please stop right now.


Thursday, April 3th, 2014.

Mozilla Foundation announces the step down of his CEO, Brendan Eich, less than two weeks of his appointment.

Brendan is, no more and no less, the creator of Javascript language and co-founder of Mozilla project and Mozilla Foundation. Event though his impressive professional history and contributions to open source community were not enough to hold his position as CEO.

As Fox News reported, there was a big pressure by some employees and many comments on Twitter because in 2008 Brendan donated one thousand dollars to support Proposition 8, a state constitutional amendment to provide that “only marriage between a man and a woman is valid or recognized in California”.

No, you don’t read it wrong. I’ll translate: a few gay right militants could not live with the idea of working to someone who thinks differently and six years ago used his own money in something they reprove.

And things are getting worse. Bumbling articles, trying justify all that, claimed that “Eich’s stance was unacceptable in Silicon Valley, a region of the business world where social liberalism is close to an universal ideology”.

Translating again: you are unacceptable to such duty unless you are a liberal and, as a consequence, support same-sex marriage.

Personal comments

I wrote this article because this is just one of many cases of intolerance inverted around the world. Alleged victims persecute and attack the “intolerant”. So, if most of the workers of a company are conservatives, can they fire their boss because he is a liberal?

I’m a Christian, on right politically, against socialism and communism, advocate of homeschooling and moderate children physical discipline and so on. Besides that, I don’t judge the professional capability, intellectual faculties or the character of my colleagues by their personal positions that certainly are different and diverse, neither I use ploys against them.

I have no problems in lead or be leaded by people that think differently. It’s part of someone’s maturity and ethics be careful in speaking about personal convictions in order to not offend others. The problem is, in today’s world, it’s not a two-way street. Soon I will renounce because someone will search out the internet to something “controversial” I published in order to demoralize me.

We live in an upside down world. Kids try to subject their parents, students their teachers, criminals the officers and minorities the majorities. Anyone who feels like a victim, offended by any reason, think he has the right to attack everyone. Grownup people are short.

I am for freedom, but not only of liberals. I am in favor that we could be free, both you and me.

Practicing my right to no longer use Mozilla Firefox anymore

For now, I can only use my right to no longer use Firefox and democratically talk about my position and invite you to do the same.

Do not support an institution that discriminates their employees by their personal convictions.

Do note participate in liberal fascist groups or discussions, that do not tolerate nothing unless their own ideas.

Uninstalling FF in 3… 2… 1…

Pelo meu direito de não mais usar o Mozilla Firefox

Atenção: este artigo contém teor político e opinativo. Se você tem estômago fraco, pare de ler agora.


Quinta-feira, 3 de abril de 2014.

A Fundação Mozilla anuncia a renúncia de seu CEO, Brendan Eich, menos de suas semanas após assumir o cargo.

Brendan é, nada mais, nada menos que o criador da linguagem Javascript e cofundador do projeto Mozilla e da Fundação Mozilla. Mas, apesar de seu impressionante histórico profissional e várias contribuições para a comunidade de código livre, isso não foi suficiente para suportar sua permanência como CEO.

Como noticiou o Fox News, houve uma grande pressão por parte de alguns funcionários e muitos comentários no Twitter porque em 2008 Brendan doou mil dólares em apoio à Proposition 8, uma proposta de emenda constitucional para defender que “apenas casamentos entre um homem e uma mulher sejam válidos ou reconhecidos na Califórnia”.

Não, você não leu errado. Vou traduzir: alguns ativistas gays não suportaram a ideia de trabalhar para alguém que pensa diferente e há 6 anos fez com seu dinheiro pessoal algo eles não aprovam.

E a coisa fica pior. Artigos desastrados, tentando justificar o ocorrido, chegam a afirmar que “a posição de Eich era inaceitável no Vale do Silício, uma região do mundo dos negócios onde o liberalismo social está próximo de uma ideologia universal”.

Traduzo novamente: você é inaceitável para exercer um cargo a menos que seja um liberal e, consequentemente, defenda o casamento entre pessoas do mesmo sexo.

Comentários pessoais

Escrevi este artigo porque este é apenas um dos muitos casos de intolerância invertida ocorrendo no mundo. As supostas vítimas perseguem e atacam o “intolerante”. Então se uma empresa tem a maioria de funcionários conservadores ela tem o direito de demitir o chefe porque ele é um liberal?

Eu sou cristão, politicamente de direita, contra o socialismo e o assistencialismo, defensor do ensino em casa (homeschooling), defensor da criação de filhos com palmadas e por aí vai. Nem por isso julgo a capacidade profissional, o intelecto ou o caráter de meus colegas por suas convicções pessoais que certamente são diferentes e diversificadas, nem uso de estratagemas para prejudicá-los.

Pessoalmente, não tenho problemas em conviver como líder ou liderado por pessoas que pensam diferente. Faz parte da maturidade e da ética de cada ser humano ter cuidado em como declarar suas convicções pessoais para não ofender aos demais. O problema é que, no mundo em que vivemos, a recíproca parece não ser verdadeira. Logo serei eu a renunciar porque alguém vai vasculhar a internet atrás de algum comentário “polêmico” que eu tenha feito para me desmoralizar.

Vivemos num mundo de cabeça para baixo, onde filhos tentam dominar os pais, alunos aos professores, bandidos aos policiais e as minorias às maiorias. A sociedade está indo para um caminho perigoso. Qualquer que se sente vítima, ofendido por qualquer motivo, acha-se no direito de atacar quem quer que seja. Gente grande que é bom tá em falta.

Eu sou a favor da liberdade, mas não apenas dos liberais. Sou a favor de que sejamos livres, você e eu também.

Exercendo o meu direito de não mais usar o Mozilla Firefox

Por enquanto, posso apenas exercer meu direito e não usar mais o Firefox, além de democraticamente emitir a minha opinião e convidar você a fazer o mesmo.

Não apoie uma instituição que discrimina seus funcionários por suas convicções pessoais.

Não participe de grupos e discursos do fascismo liberal, que não suporta nada a não ser suas próprias ideias.

Desinstalando FF em 3… 2… 1…

News about this blog!

New high: 10,000 views

This blog has grown.

In the first months, it had only about 10 daily views. The average increased continuously and today it goes beyond a hundred, with peaks of 160 views. A few days ago, this site reached ten thousand views milestone, summed up since last year June.

views-blog

New horizons: English articles

The goal of this blog is contribute to IT community. Nothing better to reach more people than using the world’s IT “official” language.

It’s with great pleasure that I announce today the launch of the internationalized version of this blog! Notice the flag on the right.

I will continue to write primarily in Portuguese. However, I’ll start to translate the current content. Portuguese speakers won’t lost anything if they just ignore the other language.

At the present, only a small part of the accesses comes from another countries. My intent is to raise the international visibility of the Brazilian community of developers through this blog, even though with such a small contribution.

But, I have to confess, there is a double intent in all that: practice my English skills. I have already written some articles on how to improve the overall English comprehension reading books. Well, it’s time to do the same in writing.

Exactly because of that I left a big yellow alert saying My English is beta. That’s right. Not only Google and other companies that adopt agile principles can put unpolished “products” in “production”. 😉

What you should to expect

Making my work and thoughts public is not always easy. As I have written, I am exposed to critique and criticism. Something published online is like a tattoo: once done, you’ll never erase it completely again. I’ll do my best to maintain the quality in a high standard.

Moreover, the issues remain divided in four big categories.

The first one contains technical tips of programming. They can be simple or complicated, but I’ll try to post those who can save the life of a poor programmer in despair. Most of this tips come from such situations that happens on sites like StackOverflow.

The second contains reflections about Software Engineering. For instance, when I write about software development problems or difficulties on estimation.

The third refers to Software Architecture. I plan to write a series of articles introducing various technologies to serve as a reference for developers. Furthermore, articles on how to choose and use specific technologies will be part of this category.

Finally, I’ll continue to share thoughts about career and professional development. I regard this as essential. My intent is to administer it continuously in order to lift some professionals from lethargy, where one day I was.

I believe the four aforementioned topics are fundamental for good Software Engineers and will produce good results if administered with right proportions.

Thank you, dear reader

Writing to nobody can be a stress reliever. But it’s much better to know you’re coming, reading, sharing and commenting.

So, thank you!

Please, keep liking, sharing and commenting.

It’s for you.

Novidades sobre o blog!

Marco: 10.000 acessos

O blog tem crescido.

Nos primeiros meses, contava com uma média de 10 visitas diárias. Esses números vêm crescendo continuamente e hoje vai além de uma centena, com picos de 160 visualizações. Há alguns dias, o site atingiu o marco de dez mil visualizações somadas desde junho do ano passado.

views-blog

Novos horizontes: postagens em Inglês

O objetivo deste blog é contribuir para a comunidade de TI de forma geral. E nada melhor para atingir mais pessoas do que usar o idioma “oficial” da TI no mundo, o Inglês.

É com grande felicidade que inauguro hoje a versão “internacionalizada” do blog! Note a bandeirinha à direita.

Calma, as postagens em Português vão continuar e serão prioridade. Entretanto, vou iniciar a tradução dos artigos já publicados. Os visitantes de fala portuguesa não perderão nenhum conteúdo se ignorarem o outro idioma.

Atualmente, apenas uma pequena parcela dos acessos tem vindo de outros países. Pretendo aumentar a visibilidade internacional da comunidade de desenvolvedores brasileiros através deste blog, ainda que com uma contribuição bem pequena.

Mas, preciso confessar, tenho um objetivo duplo nisso tudo: praticar meu Inglês. Já escrevi alguns artigos sobre como melhorar o entendimento através da leitura. Bem, chegou a hora de fazer o mesmo com a escrita.

Justamente por isso coloquei um aviso grande e amarelo de que meu Inglês está em versão beta. Isso mesmo. Não é só o Google e outras empresas que adotam práticas ágeis que podem colocar “produtos” ainda inacabados em “produção”. 😉

O que você deve esperar

Tornar meu trabalho e minhas reflexões públicas não é sempre fácil. Como já escrevi, sou suscetível à crítica. Algo que é publicado na Internet é como uma marca de tatuagem: uma vez feita, você nunca mais consegue apagá-la totalmente. Então vou fazer o possível para manter o nível de qualidade das postagens elevado.

Além disso, os temas continuam divididos basicamente em quatro grandes categorias.

A primeira contém dicas técnicas de programação. Elas podem ser simples ou complicadas, mas tentarei publicar apenas as mais úteis, que podem salvar a vida de algum pobre programador num momento de desespero. A maioria vêm de contextos como este ocorridos em sites como StackOverflow e GUJ, dos quais tenho participado.

A segunda contém reflexões sobre Engenharia de Software. Por exemplo, quando escrevo sobre problemas no desenvolvimento de software ou dificuldades da estimação.

A terceira envolve Arquitetura de Software. Pretendo escrever uma série de artigos introdutórios teóricos e práticos sobre diversas tecnologias para servir uma referência para quem está aprendendo. Além disso, artigos sobre como definir e aplicar tecnologias específicas também podem entrar nesta categoria.

Por último, continuarei a publicar reflexões sobre carreira e desenvolvimento profissional. Considero isso essencial. Pretendo ministrá-los continuamente para tirar alguns profissionais da letargia, onde eu já estive um dia.

Creio que os quatro temas principais acima são importantes para todo bom Engenheiro de Software e produzem um resultado muito bom se ministrados na proporção certa.

Obrigado, caro leitor

Escrever para ninguém ler pode até ser útil como desabafo. Mas é muito bom saber que você tem acessado, lido, compartilhado e comentado.

Então, obrigado!

E continue curtindo, compartilhando e comentando sobre o conteúdo deste blog.

Ele é feito para você.

Relative, absolute paths and other file methods in Java

The Java File class encapsulates the filesystem in a simplified manner. Its constructor accepts absolute and relative (to the working directory of the program) paths. For instance:

new File(".") //--> program's current directory

But using relative paths can cause trouble in some situations, since the working directory could be modified. Besides that, if there are input fields for file paths you should avoid relative paths unless in special cases like if the selected file is part of your program.

Checking if a path is relative

The File.isAbsolute() method gives us a hand in this task, telling if the path is absolute. Example:

File f1 = new File("..");
System.out.println(f1.isAbsolute()); //prints false

File f2 = new File("c:\\temp");
System.out.println(f2.isAbsolute()); //prints true

How to get the absolute path

Another useful method is getAbsolutePath(). It returns the absolute path to an instance of File.

Another example:

File f1 = new File("\\directory\\file.xml");
System.out.println(f1.getAbsolutePath()); //prints C:\directory\file.xml

File f2 = new File("c:\\directory\\file.xml");
System.out.println(f2.getAbsolutePath()); //prints c:\directory\file.xml

Other nice features of File

File contains various interesting features for specific use cases, such as:

  • getParentFile: returns a File pointing to the directory that contains the current file or directory.
  • getAbsoluteFile: returns another instance of File with an absolute path.
  • toURI: returns a URI (Universal Resource Identifier) that begins with file:. It’s useful to network operations.
  • isFile e idDirectory: tells if File points to a file or directory, respectively.
  • exists: tells if the file or directory really exists in filesystem.
  • canRead e canWrite: tells if you can read or write to the file, respectively.
  • createNewFile: creates a new blank file.
  • delete: removes the file or directory (if empty).
  • length: retuns the file size in bytes.
  • list e listFiles: lists files and directories, if File is a directory.
  • mkdir e mkdirs: creates a new directory, if File is a directory. The latter also creates parent directories if needed.
  • getFreeSpace: returns the available space in the device to where File is pointing to.
  • createTempFile: static method that returns an unique temporary file to be used by the application. The method deleteOnExit will delete the file at the termination of the program.

The class File also contains some important static attributes useful to read and write files in different platforms:

  • File.separator: path-name separator character. On Unix and Linux it is /, while on Windows it is \.
  • File.pathSeparator: path-separator character, in order to create a path list with various directories, like PATH system variable. On Unix and Linux it is :, while on Windows it is ;.

This article was based on my answer on StackOverflow in Portuguese!

Página 8 de 16

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.