26 de abr. de 2010

three-valued logic (3VL)

Salve,

Já que tenho um tempinho livre, resolvi falar sobre o a lógica ternária. Provavelmente todos que estão lendo este tópico, entendem lógica boleana, e o tipo boleano (boolean), e é sobre ele que a discussão paira.

O tratamento da lógica ternária é um desafio para solução de implementação e abaixo mostrarei algumas idéias mais simples.


Tipo boleano

O tipo boleano, cuja premissa foi cunhada por Boole (http://en.wikipedia.org/wiki/Boole), identifica uma informação com dois, apenas dois, estados, como o BIT. Para programadores de linguagens de alto nível, tal como Java, entendemos como simplmesmente:

verdadeiro ou falso

Quem passou por uma cadeira de lógica e álgebra booleana, deve conhecer a famosa tabela, esta:

TRUE   AND  TRUE   =  TRUE
TRUE   AND  FALSE  =  FALSE
TRUE   OR   FALSE  =  TRUE
FALSE  OR   FALSE  =  FALSE

Bem simples não?


Three-valued logic

A lógica ternária, inclui um terceiro estado, indeterminado, ou formalmente unknown (desconhecido). Não é novidade, ele é utilizado no SQL, conhecido como o valor NULL.

Imagine um campo fumante em uma tabela qualquer de uma base de dados relacional (SQL), e que seja do tipo boolean. Este campo pode ter os seguintes estados:

+---------+------------+
| Nome    |  Fumante   |
+---------+------------+
| João    |  true      |
| Maria   |  null      |
| José    |  false     |
+---------+------------+

Em teoria, entende-se que João é fumante, José não, e não sabemos se Maria fuma ou não, correto?

Esse é o enfadonho "Boolean de três estados". Existe uma tabela, que inclui o valor desconhecido, como pode ser visto abaixo:

ABA OR BA AND BNOT A
TrueTrueTrueTrueFalse
TrueUnknownTrueUnknownFalse
TrueFalseTrueFalseFalse
UnknownTrueTrueUnknownUnknown
UnknownUnknownUnknownUnknownUnknown
UnknownFalseUnknownFalseUnknown
FalseTrueTrueFalseTrue
FalseUnknownUnknownFalseTrue
FalseFalseFalseFalseTrue
fonte: http://en.wikipedia.org/wiki/Ternary_logic


"Maldição do Boolean de Três Estados"

Quando se está programando, podemos cair nesta questão, neste detalhe.

Por exemplo, utlizando um método no Java para separar em uma lista somente os fumantes.

// uma classe de domínio

public class Funcionario {
    // bla bla bla

    public Boolean isFumante() {
        return isFumante;
    }
}

// em uma classe qualquer

private List getFumantes(List todos) {
    List fumantes = new ArrayList();

    for (Funcionario funcionario : todos) {
        if (funcionario.isFumante()) {
            fumantes.add(funcionario);
        }
    }
    return fumantes;
}

Pergunto, há algum problema com essa solução de implementação?

tic, tac, tic, tac, tic, tac, ....

CLARO.

Esta código está propenso a:

Exception in thread "main" java.lang.NullPointerException
at Teste.main(Teste.java:9)

E o problema está aqui:

if (funcionario.isFumante())

Se o retorno do método for null, já que estamos usando a classe Boolean como retorno e não o tipo primitivo boolean, o teste condicional lançará uma NPE.
Esta é a maldição do "boolean de três estados".



Programando ciente do 3VL

Existem algumas estratégias para solução deste problema, e o DataSource da classe de domínio é extremamente importante. Caso venha de uma base SQL, onde NULL é um valor aceitável, ou seja, quer dizer "não informado", a melhor estratégia é implementar na classe cliente, o chamador do método.

Implementar na classe servidora seria mais simples, por exemplo, testando se o valor é nulo e retornando falso, entretanto, se na camada de apresentação for necessário informar em um rótulo ou caixa de texto: Fumante: SIM, ou Fumante: Não, ou Fumante: ____, o terceiro valor (NULL) serviria para manter a informação vazia.

Então, tudo depende. Se não haverá problema na camada de apresentação, e o valor desconhecido, vindo do DataSource, na prática, não é necessário, o melhor é implementar na classe servidora, como no exemplo:

public class Funcionario {
    // bla bla bla
    public Boolean isFumante() {
        return isFumante == null ? false : isFumante;
    }
}

Sempre retornará falso caso o valor seja nulo, o que significa, NPE-Free!

Por outro lado, se o valor desconhecido (NULL) é necessário, implemente no cliente, como no exemplo:

for (Funcionario funcionario : todos) {
    if (funcionario.isFumante() != null && funcionario.isFumante()) {
        fumantes.add(funcionario);
    }
}


Lembre de testar se é diferente de nulo primeiro, assim se for nulo, a segunda parte da expressão não será avaliada, desde que você use o operador && e não apenas um & (o && é conhecido como operador de curto circuito: http://en.wikipedia.org/wiki/Short-circuit_evaluation).



Programação defensiva

Uma sugestão, é procurar saber se o retorno do método é seguro, por exemplo, consultando o Javadoc ou assistente de código do IDE.

Nem sempre temos a mão o código fonte para consultar, e até alterar, a classe servidora, então para garantir:

  • Se o retorno for Boolean, com B maiúsculo, significa que retorna um objeto, um wrapper, e que o valor NULL é admissível,  então, teste antes:

if (classeQualquer.metodoBoleano() != null && classeQualquer.metodoBoleano()) ...

  • Caso contrário, caso o retorno do método seja boolean, com B minúsculo, significa que retorna um tipo primitivo, e realmente, ou virá verdadeiro ou falso, e neste caso o teste não é necessário:

if (classeQualquer.metodoBoleano()) ...


Casos semelhantes acontecem com outros Wrappers, como Integer, Double, etc, mas que ficam para um próximo tópico.


Claro, existem outra soluções viáveis, fiquem a vontade para sugerir.

Obs.: Usando um exemplo, o Apache Velocity é ciente do 3VL, se eu utilizar #if($cliente.isFumante), e isFumante é nulo, a expressão avalia falso sem warnings.

Referências:

Nenhum comentário: