domingo, 29 de agosto de 2010

Manuais, mentiras e telefonemas

O manual que eu escrevi, onde estará? O usuário não o leu, com certeza. Eu tento dar a dica:

-Isso está na segunda página do manual.

Mas a sutileza perde-se. Isso eu até suporto, mas não consigo entender por que o usuário mente para mim.

-Nunca fiz isso.

E o registro, brilhando na minha cara, indicando que fez. Ou ainda pior, diz que fez, mas nunca entrou no sistema.

Não se pode confiar no usuário. O primeiro passo de um atendimento é coletar todas as informações necessárias, porque nada do que ele diz é confiável. Nada mesmo. Ele pode dizer que usa IE, mas estar num Firefox. Pode dizer que usa XP, mas estar num Vista. Pode nem ser ele o usuário.

O usuário mente até no cadastro:

-Não recebi a senha.
-A senha foi para o email xyz@abc.com.
-Não uso esse email.
-Agora seria um bom momento para reativá-la, porque ainda não implementamos o subsistema de localização de emails secretos de usuários.

Está bem, não posso responder isso. Então, escrevo aqui. O usuário não tem senso de humor.

Atender por telefone é uma arte. Há um jogo muito sutil que precisa ser jogado. Tenho que fazer o usuário entender o que ele não quer entender (se quisesse, teria lido o manual). Quero que ele perceba que é um estorvo, mas não posso dizer diretamente. Minhas frases têm que continuar depois do ponto final. Lá no fim da frase mais polida, o usuário tem que perceber que paira no éter um "sua besta".

-De nada. Se quiseres trocar a senha outra vez, basta clicar no primeiro botão da primeira tela - aquele que diz "Trocar senha" - e seguir os passos que estão na terceira página do manual. É simples; são só três campos. Tenha uma boa semana.

Viste?

Todos os anos sai a triste estatística de quanto livros em média cada brasileiro lê. Eu adicionaria a estatística de quantos manuais o brasileiro não lê.

segunda-feira, 23 de agosto de 2010

Mais regressões lineares em SQL

Vasculhando o manual do Oracle, encontrei umas funções estatísticas e entre elas algumas funções para calcular regressões lineares. A função REGR_SLOPE() logo revelou-se a mais útil. Ela retorna a inclinação da reta que indica a tendência dos dados.

Para repetir o cálculo do artigo anterior, fiz o seguinte:

select min(idhm)+(2010-min(ano))*regr_slope(idhm, ano)
from indices_municipais
where municipio=130

Tenho a tendência de idhm sobre ano, então adiciono o menor valor de idhm ao produto da tendência pela diferença de anos e obtenho o resultado esperado para 2010 (a base tem dados de 1991 e 2000).

Depois, resolvi classificar os municípios pelo IDHM futuro:

select trunc(min(idhm)+(2010-min(ano))*regr_slope(idhm, ano),3), nome
from indices_municipais natural join municipio
group by nome
order by 1 desc

Essa consulta tem um join com a tabela de municpios, porque eu ainda não decorei todos os códigos. E adicionei um TRUNC(), porque resultados com 18 dígitos cansam os olhos.

A consulta é muito mais simples que a do primeiro artigo, mas será mais rápida? Reescrevi a consulta original para poder classificar todos os municípios.

select trunc(a+2010*b,3), nome from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b, nome from (
select count(1) n, sum(ano) x, sum(ano*ano) x2,
sum(idhm) y, sum(idhm*idhm) y2, sum(ano*idhm) xy,
nome
from indices_municipais natural join municipio
group by nome
)
) order by 1 desc

Tabulei os tempos:
Consulta com REGR_SLOPEConsulta complicada
0,015s0,014s
0,015s0,014s
0,016s0,013s
0,014s0,017s
0,021s0,015s
0,013s0,014s
0,016s0,015s
0,023s0,015s
0,015s0,021s
0,015s0,014s

Então, a consulta complicada foi mais rápida em 8 das 10 execuções. Ambas produzem 467 linhas (o município campeão é Ibiaçá).

segunda-feira, 16 de agosto de 2010

Regressão linear em SQL

Uma das tarefas mais divertidas e inúteis para economistas amadores (como eu) passarem o tempo é procurar maneiras de prever o futuro. E uma das maneiras mais simples de se fazer isso é usando a regressão linear (finalmente uma entrada da Wikipédia cuja versão em português é melhor que a versão inglesa!).

O objetivo é simples: dada uma série, encontrar uma reta que descreva a tendência. Com essa reta, pode-se imaginar para que lado as medidas estão indo.

Decidi usar uma tabela de IDHs municipais para testar meu SQL. A tabela tem, entre outras colunas, ANO, IDHM (indíce de desenvolvimento municipal) e MUNICIPIO.

Conforme o artigo da Wikipédia, o objetivo é encontrar os termos a e b da equação y=a+bx. As equações para cada termo são trabalhosas, então decidi montar a consulta em 3 partes. Na primeira, calculo os pedaços das equações; na segunda calculo a e b; na última, prevejo o futuro.


select a+2010*b from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b from (
select count(1) n, sum(ano) x, sum(ano*ano) x2,
sum(idhm) y, sum(idhm*idhm) y2, sum(ano*idhm) xy
from indices_municipais
where municipio=130
)
)

Eu tenho os dados de 1991 e 2001. Com essa pequena consulta, projeto a reta e descubro qual a tendência para 2010. Trocando o 2010 por 1991 e 2001 pude verificar que estava tudo certo; a consulta produziu os valores que já estavam na base. Como eu só tinha duas medidas, a reta tinha que passar exatamente por elas!

A consulta não é tão complicada e só o select mais de dentro muda, então resolvi escrever uma função parametrizada para poder repetir o experimento em outras tabelas com mais facilidade.


create or replace function regressao_linear (
valor number,
x varchar2,
y varchar2,
tabela varchar2,
restricao varchar2)
return number is
resultado number;
begin
execute immediate '
select a+'||valor||'*b from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b from (
select count(1) n, sum('||x||') x, sum('||x||'*'||x||') x2,
sum('||y||') y, sum('||y||'*'||y||') y2, sum('||x||'*'||y||') xy
from '||tabela||'
where '||restricao||'
)
)
' into resultado;

return resultado;
end;


Os parâmetros x e y são os nomes das colunas; tabela não preciso explicar e restricao é tudo que vai na cláusula where. E valor é o x que se quer projetar além dos dados. A interpolação de strings no Oracle é mesmo pavorosa e essa multiplicação de x e y não ajuda. Mas agora posso usar a função e fazer de conta que é trivial; a sujeira foi toda para debaixo do tapete.

Para executar a consulta inicial usando a nova função, basta fazer o seguinte:

select regressao_linear(2010, 'ano', 'idhm',
'indices_municipais', 'municipio=130')
from dual


Esse município tem IDHs 0,746 e 0,822 para 1991 e 2001. O valor retornado pela função é 0,906. Quando sair o censo eu vou conferir e cobrar do prefeito se não estiver conforme o esperado.

quarta-feira, 11 de agosto de 2010

Truques com calculadoras

Um amigo de infância poucos dias lembrou de "quantas tardes jogando Elite nessa calculadora" passamos. A tal calculadora podia fazer, na melhor das hipóteses, uma adição de 8 bits em 2 ciclos (e em 6 na pior). Rodando a 2MHz, era possível, no máximo, 1 milhão de somas por segundo. Isto se o computador não tivesse mais nada para fazer.

Os computadores hoje estão nos gigahertz e agora os truques ficaram mais interessantes. Mesmo assim, o mecanismo básico é o mesmo: pegar um número, executar uma operação muito simples e guardar o resultado.

A primeira calculadora foi inventada por Pascal. O pai dele era coletor de impostos e o incumbiu de resolver uma papelada infindável. Para diminuir a carga e evitar que o tédio se prolongasse, ele inventou um aparelho mecânico que somava e subtraía. O primeiro processador foi inventado pela Intel para simplificar e baratear o projeto de uma calculadora e é, em essência, uma pequena calculadora programável.

Camadas e camadas de abstração mais tarde e temos comunicações, internet, som, vídeo e jogos rodando em cima de calculadoras. Calculadoras muito rápidas, mas calculadoras mesmo assim.

Por isso, não sei se o melhor nome para o curso seria Ciências da Computação. Nas outras ciências, as pessoas partem da observação da complexidade do mundo e tentam encontrar os princípios subjacentes. Na Computação, partimos de princípios básicos muito simples e rígidos e tentamos criar a maior complexidade possível. Acho que o curso deveria chamar-se Arte da Computação ou
Mágica com Números ou Truques com Calculadoras.

quinta-feira, 5 de agosto de 2010

IT nightmares

Um dos programas de TV mais divertidos dos últimos tempos é o Kitchen Nightmares, no qual o chef Gordon Ramsay visita restaurantes à beira do precipício e tenta retorná-los à lucratividade.

Invariavelmente, os problemas são causados pelo dono, pelo cozinheiro-chefe ou por ambos. Os temas mais comuns são a falta de higiene, a falta de coordenação entre os cozinheiros e pratos desnecessariamente complicados. Com freqüência, há um funcionário promissor sendo ofuscado pela teimosia dos líderes.

Eu sonho em ver um IT Nightmares! Um CIO experiente vai visitar CPDs e colocá-los no rumo. Haverá uma série de embates até o chefe convencer-se de que está complicando as coisas e impedindo sua talentosa equipe de brilhar.

O papel do cozinheiro-chefe teimoso e complicado vai ser preenchido pelo arquiteto multi-certificado e suas infinitas camadas, frameworks e arquivos de configuração.

O chef Ramsay insiste em criar pratos "simples e honestos" que, quase sempre, são mais baratos e, por isso, mais lucrativos. Pois, eu torço por soluções simples e honestas. Os sistemas não precisam resolver todos os problemas presentes e futuros; eles precisam resolver com eficiência os problemas de hoje. Quem sabe quais serão os problemas de amanhã?!

Na informática, já vi muitos finais desastrosos. Uma vez dentro de uma situação complicada, é difícil achar tempo e meios de resolvê-la com inteligência. Refletindo sobre projetos passados, pude ver claramente os erros, mas somente meses depois. Quem olha de fora, sem estresse e sem pressão, pode analisar melhor a situação.

Em alguns casos, teria sido ótimo ver o Gordon dar uns tabefes no chefe!

terça-feira, 27 de julho de 2010

Upgrade histórico

Eu queria ter instalado drives de 3.5" no meu BBC B+ há 20 anos. Infelizmente, naquela época, os drives eram caros, eu pouco sabia sobre o assunto e os drives de 5.25" ainda funcionavam bem. As circunstâncias não eram propícias.

A foto abaixo (estrelada pela linda Maria Alice) mostra a configuração campeã. É um BBC B+ com 128KB de RAM e um processador 65C12 a 2MHz. Há também um monitor com conector TTL RGB e um par de drives de 5.25" que funcionam mal.


A controladora dos drives é uma Western Digital 1770. Ela é capaz de suportar densidade simples e densidade dupla, mas o sistema operacional só suporta densidade simples (256 bytes por setor). Os drives suportam as duas densidades e 40 ou 80 trilhas. Então, num único disquete de 5.25", gravam-se 400KB (80 trilhas x 10 setores x 256 bytes x 2 lados). Num drive de 3.5" moderno pode-se usar o mesmo formato; o computador não tem idéia do que está ligado nele.

No fim do ano passado, eu já tinha descoberto todas as questões a resolver para ligar um drive novo e as anotei num artigo. Naquele momento, eu já havia conectado um drive e gravado com sucesso um "Hello, World!" escrito em BASIC. No entanto, eu só consegui que o drive se comportasse como o segundo. Para conectar o drive como o primeiro, foi preciso adaptar um cabo.

Os drives modernos são todos configurados para serem o drive B. E para economizar uns centavos, eles nem têm um mecanismo de reconfiguração. Afinal, eles estão custando R$15 agora! No PC, as linhas 12 e 14 acionam, respectivamente, os drives B e A; as linhas 16 e 10 acionam o motor, conforme o drive selecionado. No padrão Shugart, que o BBC usa, as linhas 10, 12 e 14 acionam os drives 0, 1 e 2. E apenas a linha 16 aciona o motor. O BBC só usa os drives 0 e 1.

A tabela abaixo mostra as diferenças:

LinhaPCShugart
10Motor drive A:Aciona drive 0
12Aciona drive B:Aciona drive 1
14Aciona drive A:Aciona drive 2
16Motor drive B:Aciona motor

Os cabos novos têm uma voltinha para que quando o PC envie sinal para o drive A, ele o receba mesmo estando configurado como B. Para ligar o BBC, basta inverter as linhas 10 e 12. Ou seja, para acionar o drive 1, o BBC liga as linhas 12 e 16, exatamente como o PC faz para o drive B. Para acionar o drive 0, é preciso fazer o sinal da linha 10 chegar na linha 12. É simples, mas fabricar o cabo é difícil, porque as linhas são pequenas e os conectores menores ainda. A foto abaixo mostra os dois tipos de cabo (o da direita é o comum de PC).

Não dá para disfarçar o caráter artesanal do cabo! Eu cortei a ponta de um cabo normal, desfiz a volta e troquei as linhas 10 e 12. Bastam um estilete e muita paciência.

Na primeira tentativa, o BBC lia os disquetes, mas não gravava. A linha de proteção de gravação (linha 28 ou /WPT para os íntimos) provavelmente estava encostada numa vizinha. Na segunda tentativa, funcionou, mas o cabo não ficou firme e acabou se desmanchando. Na terceira, sucesso! O disquete gravado há 7 meses também funcionou muito bem. Como os disquetes modernos são fabricados para gravar em densidade alta, tive medo de que não funcionariam bem com densidade simples. A foto abaixo mostra que é preciso tapar o buraco oposto ao de proteção de gravação para enganar o drive a pensar que trata-se de um disquete de densidade simples ou dupla (repare no da direita; o furo está tapado com fita isolante):

As fotos a seguir mostram a instalação em funcionamento.


Na tela está o conteúdo do disquete: um arquivo BASIC chamado HELLO e um arquivo texto chamado TEXT.

No canto, vê-se o terminador da fonte que seria ligado ao computador. Sem aquele cabinho vermelho, a fonte não liga.

O próximo passo é escrever um programa para ler disquetes de PC. Com um pouco de código de máquina é possível ler disquetes com FAT de 360KB ou 720KB. E não estou exagerando, porque o sistema de disco (DFS ou Disc Filing System) cabe todo numa ROM de 16KB.

sexta-feira, 23 de julho de 2010

Hierarquias legíveis no SQL

A maneira mais comum de modelar hierarquias em tabelas é usar uma coluna para referenciar a própria tabela. Isso funciona muito bem no Oracle, porque a sintaxe tem umas facilidades para tratar esse tipo de construção. No entanto, não creio que seja a melhor maneira de resolver esse problema.

Joe Celko difundiu largamente a solução dos conjuntos aninhados. Eles já são um grande avanço, mas ainda não resolvem o problema que eu quero resolver: tornar os dados mais legíveis, sem precisar usar consultas complicadas. Eu queria executar um simples select com order by e pronto.

Uma maneira simples de fazer isso é colocar a hierarquia num texto usando uma notação posicional. Por exemplo, para representar um nodo "3.2.4" usa-se um texto "030204". O pai desse nodo (3.2) seria o "0302". Usando apenas números, tem-se 99 elementos por nível. Com o alfabeto, pode-se mais. Para todas as aplicações que vi até hoje, basta.

Criei uma tabela de testes assim (a chave é a coluna POSICAO):

Posição (POSICAO)Nome (NOME)
011
01011.1
01021.2
0101011.1.1
022
02012.1
02022.2
0202012.2.1

Para enumerar a hierarquia toda de uma única vez, basta a seguinte consulta:

select posicao, nome
from hierarquia
order by 1

Dá até mesmo para usar a sintaxe especial do Oracle e com isso, achatar a hierarquia e mostrar em cada registro todos os ascendentes:

select posicao, SYS_CONNECT_BY_PATH(nome,'>') nodo
from hierarquia
connect by prior posicao = substr(posicao,1,length(posicao)-2)
start with length(posicao)=2
order siblings by posicao

Isso vai mostrar algo assim:

POSICAONODO
01>1
0101>1>1.1
010101>1>1.1>1.1.1
0102>1>1.2
02>2
0201>2>2.1
0202>2>2.2
020201>2>2.2>2.2.1

Não é preciso completar os espaços vazios com zeros; isso dificulta muito as consultas. Por exemplo, na primeira tentativa, usei "010000" para o nível 1. Os zeros adicionais não só não ajudam, como tornam a consulta anterior muito mais complicada; sem eles, basta tirar dois caracteres do fim para achar o nodo pai; com eles, é preciso encontrar onde os pares de zeros terminam (ou começam, para quem vem da esquerda).

Se for preciso representar mais nodos por nível, basta usar mais posições. Com 3 por nível, já são possíveis 999 nodos. E para tornar a hierarquia mais alta, basta ir adicionando caracteres .

Esse tipo de construção, além de tornar os dados mais legíveis, facilita sobremaneira algumas pesquisas. Por exemplo, encontrar todos os nodos de terceiro nível com uma tabela auto-referenciada seria muito mais complicado.