Para o meu choque e horror, acabei de me dar conta de que Tcl pode ser uma alternativa decente ao shell do Unix. Essa galera me convenceu ainda mais disso. Com meia dúzia de funções auxiliares para fazer piping e redirecionamentos de maneira mais conveniente, essa pode ser uma boa solução.
Por um lado, problem solved. Por outro lado, isso me tira uns 80% da motivação para escrever um shell. Talvez o que seria uma boa é criar uma "extensão" de Tcl que permita o uso da sintaxe convencional do shell para pipes e redirecionamentos. O fato de que o Tcl usa strings para representar todos os tipos de dados inclusive torna trivial o problema de passar dados estruturados entre processos no Unix.
Well, melhor seguir adiante com o resto dos meus planos de dominação mundial.
[P.S.: Eis uma introdução interessante aos poderes do Tcl.]
Quando eu reescrevi o blog system, eu resolvi manter o índice de posts em um arquivo CSV e usar as funções fgetcsv e fputcsv para manipulá-lo. Minha intuição prontamente me disse "isso é PHP, tu vai te ferrar", mas eu não lhe dei importância. Afinal, provavelmente seria mais eficiente usar essas funções do que ler uma linha inteira e usar a explode para separar os campos. (Sim, eu usei um txt ao invés de um SQLite. Eu sou feliz assim, ok?)
Na verdade o que eu queria era manter o arquivo como tab-separated values, de maneira que ele fosse o mais fácil possível de manipular com as ferramentas convencionais do Unix (cut, awk, etc.). As funções do PHP aceitam um delimitador como argumento, então pensei eu que bastaria mudar o delimitador para "\t" ao invés da vírgula e tudo estaria bem. Evidentemente eu estava errado: um dos argumentos da fputcsv é um "enclosure", um caractere que deve ser usado ao redor de valores que contêm espaços (ou outras situações? who knows?). O valor padrão para a enclosure é a aspa dupla. Acontece que a fputcsv exige uma enclosure: não é possível passar uma string vazia, ou NULL, por exemplo, para evitar que a função imprima uma enclosure em volta das strings que considerar dignas de serem envoltas. Lá se vai meu tab-separated file bonitinho. Mas ok, não é um problema fatal.
A segunda curiosidade é que a fgetcsv aceita um argumento "escape", que diz qual é o caractere de escape (\ por default). Evidentemente, você tem que usar um caractere de escape; a possibilidade de ler um arquivo em um formato em que todos os caracteres exceto o delimitador de campo e o "\n" tenham seus valores literais é inconcebível. Mas ok, podemos setar o escape para um caractere não-imprimível do ASCII (e.g., "\1") e esquecer da existência dele. Acontece que a fputcsv não aceita um caractere de escape, logo você não tem como usar o mesmo caractere não-imprimível nas duas funções. WTF?
Na verdade, agora testando melhor (já que a documentação não nos conta muita coisa), aparentemente a fputcsv nunca produz um caractere de escape: se o delimitador aparece em um dos campos, ele é duplicado na saída (i.e., a"b vira a""b). Evidentemente, não há como eliminar esse comportamento. Mas então o que será que faz o escape character da fgetcsv?
# php -r 'while ($a = fgetcsv(STDIN, 999, ",", "\"", "\\"))
{ var_export($a); echo "\n"; }'
a,z
array (
0 => 'a',
1 => 'z',
)
a\tb,z
array (
0 => 'a\\tb',
1 => 'z',
)
Ok, o escape não serve para introduzir seqüências do tipo \t. Talvez para remover o significado especial de outros caracteres?
a\,b,z array ( 0 => 'a\\', 1 => 'b', 2 => 'z', ) a b,z array ( 0 => 'a b', 1 => 'z', ) a\ b,z array ( 0 => 'a\\ b', 1 => 'z', )
Muito bem. Como vimos, o escape character serve para, hã... hmm.
Mas o fatal blow eu tive hoje, olhando a lista de todos os posts do blog e constatando que o post sobre o filme π estava aparecendo com o nome vazio. Eis que:
# php -r '
$h = fopen("entryidx.entries", "r");
while ($a = fgetcsv($h, 9999, "\t"))
if ($a[0]=="20120322-pi") var_export($a);'
array (
0 => '20120322-pi',
1 => 'π',
2 => '2012-03-22 23:53 -0300',
3 => 'film',
)
# LC_ALL=C php -r '
$h = fopen("entryidx.entries", "r");
while ($a = fgetcsv($h, 9999, "\t"))
if ($a[0]=="20120322-pi") var_export($a);'
array (
0 => '20120322-pi',
1 => '',
2 => '2012-03-22 23:53 -0300',
3 => 'film',
)
A próxima versão do Blognir deverá usar fgets/explode.
UPDATE: Aparentemente o problema só ocorre quando um caractere não-ASCII aparece no começo de um campo. Whut?
UPDATE 2:
"a b","c d" array ( 0 => 'a b', 1 => 'c d', ) "a"b","c"d" array ( 0 => 'ab"', 1 => 'cd"', ) "a\"b","c d" array ( 0 => 'a\\"b', 1 => 'c d', )
As linguagens orientadas a objetos convencionais são o que podemos chamar de noun-centric: as classes são a "unidade estrutural" mais importante, e os métodos (verbos) pertencem a classes (nomes). Um método é chamado sobre exatamente um objeto, e a escolha de que código será executado para a chamada depende da classe do objeto sobre o qual o método é chamado. A sintaxe objeto.método(args) reflete essa idéia.
Algumas outras linguagens, como Common Lisp e Dylan, entretanto, seguem um modelo "verb-centric": os métodos são definidos fora das classes. Métodos de mesmo "nome" (onde nome = símbolo + pacote ao qual pertence) são agrupados sob uma mesma generic function. As classes de todos os argumentos da generic function são consideradas na hora de escolher que código será executado. Em Common Lisp, a sintaxe (método arg1 arg2...) reflete essa ausência de preferência por um dos argumentos e a existência da função como algo externo a qualquer classe. (Não lembro mais de onde saíram os termos "noun-centric" e "verb-centric" para descrever essa diferença, mas eles não são invenção minha.)
As conseqüências na prática são várias. Uma delas é que no modelo verbocêntrico é possível criar novos métodos para classes existentes sem ter que modificá-las. Por exemplo, se você quiser criar um método novo para trabalhar com strings, você pode defini-lo e usá-lo como qualquer outro método sobre strings, ao invés de criar uma distinção artificial entre métodos nativos da classe String ("foo".method(42)) e métodos definidos somewhere else que trabalham com strings (RandomClass.method("foo", 42)). (Ruby, uma linguagem nominocêntrica, resolve esse problema permitindo "redefinições parciais" de classes. Essa solução, entretanto, tem o problema de que todos os métodos sobre uma classe compartilham o mesmo namespace, i.e., se dois arquivos diferentes quiserem definir method sobre uma mesma classe, as definições conflitarão. Em Common Lisp, cada method estaria em seu próprio pacote, o que evita esse problema.)
Outra vantagem do modelo verbocêntrico é que evitam-se decisões arbitrárias quanto a em que classe se deve incluir um método. Por exemplo, imagine que queiramos definir um método (match string regex), que retorna a primeira ocorrência de regex em string. Esse método vai na classe String, ou em RegEx? E se quisermos procurar por outras coisas além de regexes dentro da string (e.g., outras strings)? E se quisermos procurar regexes em coisas que não são strings (e.g., streams)? No modelo verbocêntrico, essa decisão simplesmente não existe; basta definir match para todas as combinações de tipos de argumentos possíveis.
Uma terceira conseqüência desse modelo é que o conceito de "interface" é desnecessário: "implementar uma interface" consiste em especializar os métodos desejados para a classe em questão. (De certa forma, pode-se imaginar cada generic function como uma interface de um método só, e cada definição de método para a generic function como a implementação da interface para uma combinação de classes. De certa forma, pode-se imaginar muitas coisas.) Uma desvantagem disso é que se perde a garantia estática provida pelas interfaces de que de uma dada classe implementa um conjunto de métodos relacionados. (Garantias estáticas não são exatamente um ponto forte do Common Lisp.)
No modelo nominocêntrico, é possível descrever chamadas de métodos em termos de troca de mensagens: quando escrevemos obj.foo(23, 42), podemos pensar que estamos enviando a mensagem foo(23, 42) para o objeto obj. De fato, em Smalltalk, uma das primeiras linguagens orientadas a objeto, as mensagens são objetos de primeira classe, e é possível fazer algumas coisas interessantes com elas (como por exemplo repassá-las para outros objetos, ou definir um método doesNotUnderstand que trata todas as mensagens que uma classe não reconhece). O modelo de troca de mensagens também é interessante em implementações de objetos distribuídos: nesse caso, uma chamada de método sobre um objeto remoto é de fato uma mensagem enviada pela rede para a máquina onde o objeto se encontra. Uma desvantagem do modelo verbocêntrico é que o a descrição em termos de troca de mensagens não é mais aplicável: um método é chamado sobre múltiplos objetos, e portanto não há um "receptor" da mensagem.
Há quem diga que o CLOS (Common Lisp Object System) não é de fato orientação a objetos, mas sim um paradigma diferente que é capaz de expressar orientação a objetos e (um bocado de) outras coisas (muitas delas não abordadas neste post, tais como before/after methods (que lembram programação orientada a aspectos) e method combinations). Hoje em dia eu me vejo inclinado a concordar com essa posição, já que o CLOS foge da idéia de objetos como caixinhas fechadas manipuladas apenas através dos métodos expostos pela classe correspondente. Encapsulamento é possível em Common Lisp (basta não exportar os nomes dos slots (a.k.a. propriedades, atributos, membros) no pacote onde foram definidos), mas a noção de objeto = dados + operações se perde, de certa forma. Os objetos são apenas os dados, e as operações somos nós estão contidas nas generic functions / métodos.
Por falta de coisa mais interessante para fazer, e já que RSS não é exatamente a tecnologia da modinha, estou disponibilizando experimentalmente um feed do blog no Twitter. A continuidade do "serviço" está sujeita à existência de usuários. [Update: Pensando melhor, provavelmente todo o mundo que tem interesse em seguir blogs usa RSS. Enfim, o feed está aí, por enquanto.]
A parte interessante da história é que eu descobri um bocado de clientes de linha de comando do Twitter no processo. Dos que eu experimentei, o que melhor me satisfez foi o t. (Eu experimentei mais outros dois clientes: o TTYtter, um cliente interativo pra lá de bizarro, mas com mil features e aparentemente fácil de estender; e o twidge, que aparentemente não suporta UTF-8. Existem dúzias de outros clientes, como uma pesquisa no Google revela.)
Os poderes mágicos do t derivam do fato de ele ser particularmente conveniente de usar em scripts. Um exemplo extraído da documentação:
Favorite the last 10 tweets that mention you
t mentions -n 10 -l | awk '{print $1}' | xargs t favorite
É possível instalar o t pelo RubyGems, através do comando gem install t. Antes de instalá-lo, certifique-se de que você tem instalado o Ruby e o RubyGems (pacotes ruby, ruby-dev e rubygems no Debian/Ubuntu; não ter o ruby-dev é um problema comum).
Uma vez instalado, é necessário executar t authorize, para realizar o processo de registro da aplicação no Twitter e de autorização do acesso da aplicação à sua conta. Você pode executar t sem argumentos para ver uma lista dos comandos disponíveis. Para mais informações, dê uma olhada no README na página do projeto.
(Quem me contou foi essa página.)
O SoX (SOund eXchange; pacotes sox e libsox-fmt-all no Debian) é uma biblioteca e um programa de linha de comando que permitem converter entre diversos formatos de arquivo de áudio, opcionalmente aplicando filtros. A sintaxe básica do comando sox é:
sox [opções-globais]
[opções-de-formato] entrada1
[[opções-de-formato] entrada2 ...]
[opções-de-formato] saída
[filtros ...]
Por exemplo, para gravar do microfone (usando ALSA) em um arquivo WAV:
sox -t alsa default -t wav blabla.wav
(Use Control-C para terminar a gravação. Tecnicamente o -t wav pode ser omitido, já que o sox é capaz de deduzir o formato do arquivo pela extensão.)
Um par de filtros particularmente interessante é o noiseprof/noisered, que permitem eliminar ou reduzir ruído constante de fundo. Isso é feito em duas etapas. Primeiro, executa-se o sox com o filtro noiseprof [profile.txt] sobre uma "amostra de silêncio", i.e., um trecho de áudio que consista apenas do ruído de fundo, de maneira a produzir um profile de ruído. Você pode capturar o "silêncio" do microfone ou de algum outro arquivo que consista apenas de silêncio (a opção --null pode ser usada no lugar do arquivo de saída, já que estamos interessados apenas no profile de ruído):
sox -t alsa default --null noiseprof profile.txt sox algum-arquivo-que-consista-apenas-de-silêncio.wav --null noiseprof profile.txt
Alternativamente, você pode selecionar um trecho de um arquivo com o filtro trim início [duração] e usá-lo como fonte de silêncio:
# Seleciona o intervalo de de 1s até 2.5s. Aqui usamos '-t alsa default' como # saída para podermos ouvir se o trecho selecionado de fato corresponde a "silêncio". sox entrada.wav -t alsa default trim 1 1.5 noiseprof profile.txt
Se o nome do arquivo de profile for omitido, o sox escreve o profile na stdout.
Gerado o profile de ruído, podemos usar o filtro noisered [profile.txt [quantidade]] para remover o ruído do arquivo completo. quantidade é um número entre 0 e 1 indicando a quantidade de ruído que deve ser removida. Quanto maior o número, mais ruído será removido – e mais não-ruído também. Experimente com números pequenos (e.g., 0, 0.05, 0.1, etc.) primeiro.
sox entrada.wav saída.wav noisered profile.txt 0.05
Se você tem um microfone problemático, você pode querer guardar o arquivo de profile para usos futuros (assumindo que o padrão de ruído produzido seja sempre o mesmo).
Se o arquivo de entrada para o noisered não for especificado ou for -, o sox lê o profile da stdin. Assim, você pode combinar o profiling e a redução em um pipeline:
sox entrada.wav --null trim 0 1 noiseprof | sox entrada.wav saída.wav noisered - 0.05
Para mais informações, consulte a manpage do sox.
Pode-se dividir as pessoas em dois grupos, segundo sua reação ao serem apresentadas a features novas em uma linguagem de programação:
(Na verdade o que provavelmente existe é um continuum de quanta justificativa é necessária para provocar a reação "whoa, que legal" em uma dada pessoa, but I digress.) O que acontece aí é que eu (que estou no primeiro grupo, mostly) freqüentemente acho complicado pensar em um exemplo de uso de uma feature que convença alguém do segundo grupo de que a feature é interessante. Por exemplo, a pessoa me pergunta "mas por que Lisp tem tantos parênteses?", e eu respondo "Macros!", e a pessoa "why macros?", e eu "code transformation!", e a pessoa "why code transformation", e eu "what do you mean, why?".
Pois, agora acho que encontrei um exemplo decentemente convincente. Meu TCC consiste, entre outras coisas, em desenvolver uma linguagem de programação e integrá-la ao ambiente de programação DrRacket. Para isso, estou implementando a linguagem em Racket, um descendente de Scheme, uma linguagem da família Lisp. Dentre as inúmeras bibliotecas que acompanham o Racket, estão a parser-tools/lex e parser-tools/yacc. Essas bibliotecas implementam funcionalidade equiparável aos famosos programas lex e yacc, que geram analisadores léxicos e sintáticos, respectivamente, a partir de arquivinhos de regras.
A diferença, entretanto, é que as parser-tools são implementadas como macros em Racket, de maneira integrada com o resto da linguagem. Por exemplo, ao invés de usar um programa separado que lê um arquivo meio-em-C, meio-em-lex e gera um arquivo em C, a parser-tools/lex provê uma macro lexer que, quando compilada, produz uma função que recebe uma stream e devolve a próxima token encontrada na stream. Algo do tipo:
(define example-lexer
(lexer
[expressão valor-a-retornar]
[expressão valor-a-retornar]
...))
O parser funciona de maneira análoga. As macros, assim, permitem estender a linguagem com recursos sintáticos novos, sem que se tenha que usar ferramentas externas para fazer transformações de código (que potencialmente exigem reparsear o texto do programa, ao contrário do que acontece com as macros, que já recebem o programa pré-parseado como argumento). A vantagem da sintaxe uniforme (i.e., so-many-parens) é que os novos recursos adicionados mantêm a mesma cara do resto da linguagem, e que o parser do Lisp (que executa antes de a macro ser chamada) pode fazer o seu trabalho sem se preocupar com a semântica dos objetos que está parseando.
A conseqüência dessa integração é que o threshold a partir do qual vale a pena escrever uma transformação de código ao invés de fazer as coisas na mão é muito mais baixo em um Lisp do que em uma linguagem convencional. Por exemplo, certa vez eu estava escrevendo um typechecker/interpretador para uma linguagem simples. Logo que eu comecei a escrever, percebi que seria uma boa eu usar pattern matching para testar com que tipo de expressão o interpretador tinha se deparado. Por exemplo, ao invés de escrever algo do tipo:
;; Avalia a expressão 'expr' no ambiente de variáveis 'env'.
(defun evaluate (expr env)
(cond
;; Se a expressão for do tipo (+ x y), devolve x+y.
((eq (first expr) '+) (+ (evaluate (second expr) env)
(evaluate (third expr) env)))
;; Se for (if t x y), avalia o teste e a sub-expressão correspondente.
((eq (first expr) 'if) (if (evaluate (second expr) env)
(evaluate (third expr) env)
(evaluate (fourth expr) env)))
...))
eu queria poder escrever algo do tipo:
(defun evaluate (expr env)
(match-case
((+ x y) (+ (evaluate x env) (evaluate y env)))
((if test then else) (if (evaluate test env)
(evaluate then env)
(evaluate else env)))
...))
Em 17 linhas (não muito bonitas, mas só porque eu não estava muito preocupado com isso quando as escrevi) de Common Lisp, eu implementei uma macro match-case e segui escrevendo o programa. Se ao invés disso eu tivesse que escrever um programa externo para transformar o código, provavelmente não teria valido a pena o esforço.
#<eof>.
Certa feita (e de repente o "fecha" do espanhol não parece mais tão estranho), não me lembro mais em que contexto nem para quem, eu mostrei um código deste tipo com listas encadeadas em C:
for (item = list;
item && item->value != searched_value;
item = item->next) ;
Que tanto quanto me constasse, era a maneira de se procurar um valor em uma lista em C. Para minha surpresa na época, a pessoa disse que isso era gambiarra. (Eu dificilmente escreveria isso com um while, porque eu tenho uma propensão terrível a esquecer o item = item->next no final do while. Ou será que eu tenho essa propensão por não estar acostumado a fazer esse tipo de coisa com while? But I digress.)
Uma situação similar foi quando eu mostrei para alguém na monitoria de Fundamentos de Algoritmos que dava para usar "tail-recursion com acumuladores" para iterar sobre uma lista. Algo do tipo:
(define (média lista)
(média* lista 0 0))
(define (média* lista soma conta)
(cond [(empty? lista) (/ soma conta)]
[else (média* (rest lista) (+ soma (first lista)) (+ conta 1))]))
Que, novamente, é a maneira padrão de se fazer esse tipo de coisa em uma linguagem funcional (talvez com a definição de média* dentro do corpo de média, ao invés de uma função externa, mas whatever), e novamente a pessoa viu isso como uma "baita gambiarra". Provavelmente, o código imperativo equivalente (com atribuição de variáveis para manter a conta e a soma) é que seria visto como gambiarra por um programador Scheme.
Outro item de contenda é usar x++ em C para usar o valor atual de x em uma expressão e incrementá-lo na mesma operação (que é, bem, exatamente a definição do operador). Ok, usar um x++ perdido no meio de uma expressão grande pode ser fonte de confusão. Mas algo como:
var1 = var2++;
ou more likely:
item->code = max_code++;
não deveria ser nenhum mistério para quem conhece a linguagem.
A impressão que eu tenho é que as pessoas consideram "gambiarra" qualquer coisa feita de uma maneira diferente do que elas estão acostumadas. Eu mesmo já fui vítima desse efeito. No terceiro semestre eu escrevi um "banco de dados" em C (basicamente um programa que aceitava queries em uma linguagem facão, mantinha uns arquivos seqüencias e montava uns índices em memória) para o trabalho final da disciplina de Classificação e Pesquisa de Dados. Como o conteúdo dos arquivos binários era determinado em tempo de execução pelas definições das tabelas, o programa continha um bocado de casts, manipulações de ponteiros e outras "curiosidades" do C no código. Até então, tinha sido o programa mais complicado que eu tinha escrito. Durante um bom tempo, quando o assunto surgia, eu costumava comentar que esse código era "cheio de gambiarras" e coisas de "baixo-nível". Um belo dia, uns dois ou três semestres depois, eu resolvi dar uma olhada no código, porque eu já não lembrava direito o que ele fazia de tão mágico. Para minha surpresa, minha reação ao olhar as partes "sujas" do código foi algo como "pff, que brincadeira de criança". (E para minha surpresa, as partes "sujas" me pareceram bastante legíveis.)
(Um ponto relacionado é que quando estamos ensinando/explicando alguma coisa, devemos nos lembrar que o que nos parece óbvio e simples pode não o ser para a pessoa que tem menos experiência no assunto, mas esse não era o tema original do post.)
Agora dando uma olhada nesse código de novo, lembrei de uma outra coisa que eu tinha pensando quando o tinha revisto pela primeira vez: que certos trechos do código poderiam ter sido simplificados se eu tivesse usado um goto fail; da vida em pontos estratégicos. O que nos leva ao próximo tópico...
Entre muitos nesse mundo da programação existe um culto irracional às Boas Práticas de Programação™. Não me entenda mal; muitas técnicas e práticas ajudam a tornar o código mais legível e fácil de manter. O problema é quando as "boas práticas" se tornam regras fixas sem uma boa justificativa por trás.
Um exemplo é a proibição ao goto. A idéia básica é: "Alguns usos do goto produzem código ilegível; logo, o goto não deve ser usado." Isso é mais ou menos equivalente a "facas cortam os dedos; logo, não devemos usar facas". Ok, usar uma faca para desparafusar parafusos quando se tem uma chave de fenda à mão não é uma boa idéia. Mas usar uma chave de fenda para passar margarina no pão também não é. Verdade que em linguagens de mais alto nível do que C (com exceções e try/finally e labeled loops e what-not) é extremamente raro encontrar um bom motivo para se usar um goto; mas em C, há uma porção de situações em que um goto bem utilizado é capaz de tornar o código mais simples e legível.
Outro exemplo é o uso de TABLEs em HTML. O fundamento por trás da "regra" que "proíbe" certos usos de TABLE é evitar que TABLEs sejam usadas apenas para layout, com coisas que não têm a semântica de uma tabela. So far, so good. Mas no post (que eu nunca mais encontrei) pelo qual eu fiquei sabendo sobre "tableless tables" (i.e., o uso de CSS para aplicar o layout gerado pelas TABLEs do HTML a outros elementos), lembro que um indivíduo tinha postado um comentário semi-indignado dizendo que "usar CSS para simular uma tabela é só uma tabela disfarçada, e não deve ser feito". Ou seja, o camarada internalizou a noção de "usar TABLEs é errado", mas não o porquê.
Outra manifestação do "culto" são coisas do tipo:
Q: O que acontece se eu converter um ponteiro em inteiro e de volta em ponteiro?
A: Isso é uma péssima prática de programação e você não deveria fazer isso.
que eu vejo com alguma freqüência no Stack Overflow, i.e., o camarada não responde a pergunta, e ao invés disso se limita a repetir o mantra "Não Deve Ser Feito".
A freqüência com que eu vejo esse tipo de coisa me preocupa um pouco, na verdade. Acho que o princípio por trás disso é o mesmo que está por trás de zoar/condenar/excluir as pessoas que possuem algum comportamento "non-standard". Mas isso é uma discussão para outro post.
17.4: Is goto a good thing or a bad thing?
Yes.
17.5: No, really, should I use goto statements in my code?
Any loop control construct can be written with gotos; similarly, any goto can be emulated by some loop control constructs and additional logic.
However, gotos are unclean. For instance, compare the following two code segments:
do { foo(); foo(); if (bar()) if (bar()) goto SKIP; break; baz(); baz(); quux(); quux(); } while (1 == 0); SKIP: buz(); buz();Note how the loop control makes it quite clear that the statements inside it will be looped on as long as a condition is met, where the goto statement gives the impression that, if bar() returned a nonzero value, the statements baz() and quux() will be skipped.
_____
(O título do post é uma paráfrase de uma paráfrase de uma expressão idiomática.)
Dracula: "I'm all screwed"
Richter: "I am the instrument of your doom"
Dracula: "Perhaps not... not yet"
Richter: "What!?!?!"
???: "Richter! You killed my father! Prepare to die!"
Richter: "ALUCARD!!!"
Alucard: "You can't know... you'll never know how it feels..."
* TUM *
Richter: "NOOOOOOOO!"
– Castlevania, Hélio's edition
Há pouco mais de um ano eu abandonei o Twitter (well, not quite; eu só parei de postar lá, na verdade) por conta de eles terem vendido o acesso à base de tweets para umas empresinhas de mineração de dados. Há pelo menos metade desse tempo eu venho remoendo essa decisão.
Na época eu usava o Twitter com "tweets protegidos", i.e., apenas as pessoas que eu autorizei que me seguissem podiam ver os meus tweets. Eu assumia assim que apenas uns poucos conhecidos veriam o que eu estava postando.
Não é bem assim que a coisa funciona. Embora tweets protegidos não sejam retweetáveis, nada impede alguém capaz de ler um tweet de copiá-lo e retweetá-lo na mão. Retweets são uma feature fundamental do Twitter; não faz muito sentido contar com que algo postado no Twitter não vá ser retweetado. Proteger os tweets não melhora muito a situação.
Abandonemos, pois, a noção de que existe "publicidade seletiva" no Twitter: assumamos que "o que você diz no Twitter pode ser visto no mundo inteiro instantaneamente" (palavras dos termos de serviço, as it happens). E por "o mundo inteiro", leia-se o mundo inteiro. Na época da matrícula da UFRGS nesse ano eu vi alguns retweets da @ufrgsnoticias que provavelmente não estavam nos planos dos indivíduos retweetados e que eu fiquei cogitando comigo mesmo se não poderiam causar algum problema para os mesmos. Essa "falta de privacidade" é, me parece, uma propriedade fundamental do serviço Twitter, não um erro da companhia Twitter que o fornece. Assim (me parece), não faz sentido condenar o Twitter por esse tipo particular de falta de privacidade, mas tão-somente aprová-la ou não (e utilizar ou não o serviço de acordo).
(Essa falta de privacidade na verdade é uma propriedade compartilhada com blogs e páginas pessoais. No Twitter isso é um pouco mais acentuado pela facilidade de retweetar posts alheios, mas não é fantasticamente diferente. Essa "publicidade fundamental" é diferente do vazamento de informações pessoais para terceiros que não é fundamental para o serviço e não é do interesse do usuário. Mais sobre isso adiante.)
Ok, tweets são públicos. "Tweet privado" é praticamente uma contradição em termos. Aceitemos esse fato e tomemos por princípio que o Twitter só serve para postar coisas que não nos importamos de compartilhar com o mundo (e.g., links para coisas interessantes, recomendações de filmes/livros, etc.). Entendido isto, podemos seguir usando o Twitter com a consciência limpa.
Faltam dois problemas a considerar. O primeiro é o uso que se faz dessa informação além da mera publicação mundial. Isso inclui a venda ao acesso da base de tweets para as empresinhas de data mining, que foi a causa original do meu desgosto pelo Twitter.
Na verdade, o Twitter sempre vendeu o acesso a tweets, mesmo antes desse incidente; a diferença é apenas que o Twitter costumava limitar o acesso aos tweets dos últimos trinta dias. (O fato de que eles já brincavam de vender tweets antes não torna o ato de vender o acesso à base inteira nem mais nem menos ético, mas vamos adiante.)
O segundo problema são as outras informações que caem nas mãos do Twitter além dos tweets, e que uso o Twitter faz delas. Vamos por partes.
Pois, o Twitter vende, e sempre vendeu, o acesso a (porções da) base de tweets. Esses tweets são públicos para início de conversa*. A diferença entre baixá-los você mesmo e comprar o acesso é que as APIs do Twitter impõem certos limites na busca de tweets (e.g., aparentemente existe um limite de 1500 tweets nos resultados de buscas por termos), e que provavelmente a base é oferecida em um formato mais conveniente de manipular. (* Tweets protegidos são outra história. Eles vivem em um limbo questionável: a política de privacidade jamais menciona "tweets protegidos", então pelo menos para mim não está claro se eles são considerados informação pública (e conseqüentemente comercializável), ou se estão inclusos entre os dados pessoais que o Twitter diz não vender (o que não impede que o Twitter seja comprado e com ele seus dados, but I digress).)
A possibilidade de data mining, assim, pode aumentar com o fato de que o acesso à base de tweets é comercializado, mas ela não deixa de existir sem essa comercialização. Na verdade, a Internet inteira é alvo de data mining. Google (a organização) e outros mecanismos de busca já podem facilmente explorar toda informação pública na Internet, independentemente do seu consentimento. A diferença de postar no Twitter é facilitar a vida do Twitter e parceiros, mas postar em qualquer outro local público da web (como o identi.ca, ou seu blog pessoal) tem o mesmo risco.
É uma propriedade da Internet que toda informação pública é (em geral) facilmente acessível. Uma conseqüência disso é que coisas que antes eram muito difíceis (e.g., catar todas as notícias sobre um certo tópico ou pessoa nos jornais dos últimos N anos) agora são viáveis, e, junto com todas as vantagens que isso proporciona, vêm certas conseqüências negativas para a privacidade das pessoas. A sociedade vai ter que dar um jeito de se adaptar a isso, mas não é um problema do qual se possa escapar trocando o serviço em que essas informações serão publicadas (assumindo que se pretenda publicar essas informações para quem quer que as queira ler).
Fica a questão de se é ok o Twitter ganhar dinheiros vendendo acesso aos meus posts sem me pagar nada. Essa questão é sugerida como um exercício para o leitor.
O segundo problema são os outros dados além dos tweets. Esses dados podem ser divididos em duas classes: os dados "inevitáveis", i.e., aqueles que o Twitter necessariamente obtém a partir do uso do serviço; e os dados "evitáveis", i.e., aqueles que você pode evitar mandar com um pouco de cuidado.
Os dados inevitáveis incluem:
Pode parecer (e me parece) improvável que o Twitter esteja interessado nessa informação. Porém, vale lembrar que o Twitter se localiza nos Estados Unidos, e portanto esses dados podem ser solicitados pelo governo daquele país, coisa da qual você pode não gostar. Na verdade, entretanto, seu IP e horários de acesso são um dado visível por todos os roteadores entre você e o serviço, o que significa que eles estão disponíveis para muita gente. Como já mencionado, isso é um problema da infraestrutura da Internet existente. Uma solução seria usar algo como o Tor na Internet inteira, mas isso é assunto para outra discussão.
Um ponto é que esses dados são inevitavelmente mandados para o Twitter. O outro é o que o Twitter faz com eles. Segundo a política de privacidade, "Log Data" é mantida por 18 meses, e depois disso pode ser ou removida ou simplesmente "desidentificada" (o que pode não ser suficiente).
Os dados evitáveis incluem:
Essa informação é usada, entre outras coisas, para sugerir pessoas a seguir baseado nas páginas que você visitou, desde que você tenha optado por ativar essa feature. Se você estiver com o Do Not Track ativado, o Twitter pára de coletar dados no que diz respeito a essa feature. Eles não deixam de receber os dados, e não deixam de mantê-los por dez dias, pelo que eu entendo. Além disso, nem os termos de serviço nem a política de privacidade fazem qualquer menção ao Do Not Track, como comentado no post linkado anteriormente; apenas as páginas de ajuda falam sobre o assunto.
De qualquer forma, você pode evitar mandar essa informação simplesmente bloqueando acesso de sites de terceiros ao Twitter. No Firefox, você pode usar o AdBlock Plus para isso; a maneira mais fácil é acessar qualquer página que contenha um widget do Twitter, clicar no ícone do AdBlock, selecionar "Open blockable items", achar o platform.twitter.com e dar dois cliques sobre o cidadão. Na janela que se abre, clique no botão "Advanced view", e selecione a opção "Third-party only" (i.e., o Twitter só deve ser bloqueado a partir de sites de terceiros). Uma solução mais drástica é usar o RequestPolicy, que bloqueia tudo por padrão, mas pode ser um tanto quanto inconveniente de usar.
Interessantemente, quando você pára o mouse sobre um link desse tipo, a URL original aparece como title-text (i.e., em um popup), ou diretamente como o texto do link se for suficientemente curta. Assim, você pode usar o Greasemonkey e criar um novo user script com este conteúdo para fazer com que o Firefox substitua os links t.co pelos links originais na página do Twitter.
Fica a questão de se é "certo" usar um serviço que coleta dados que você acha que ele não deveria coletar só porque você consegue burlar a coleta. A resposta depende de quão idealista você está se sentindo hoje.
Uma desvantagem de usar um serviço centralizado para publicação de informações (e.g., Twitter, Blogger, Wordpress, Facebook (ok, o Facebook tem inúmeros outros problemas, but I digress)) é que os "dados inevitáveis", tais como logs de acesso, ficam concentrados com uma única organização. Mesmo na arquitetura atual da Internet, em que acessar um serviço implica contar-lhe onde você está e possivelmente outros dados, a situação não é tão ruim se os dados estão espalhados entre vários servidores, o que dificulta sua exploração. (Seu provedor de acesso continua com um bocado de informação, entretanto.)
Por outro lado, uma rede social é mais útil quando um grande número de pessoas a usa; essa é uma vantagem de "todo o mundo" usar o Twitter, ao invés de cada um "roll their own" microblog. Porém, em teoria nada impede que cada um rolle seu own microblog e todos funcionem de maneira integrada, desde que eles usem um protocolo comum de comunicação.
µ.
A conclusão é que talvez eu volte a usar o Twitter no futuro próximo. Ou não.
O GNU Barcode (pacote barcode no Debian) é uma biblioteca e um utilitário de linha de comando capaz de gerar códigos de barras de diversos tipos nos formatos PS e PCL. O programa possui diversas opções, que permitem controlar o tamanho e a posição dos códigos na página gerada, entre outras coisas.
Por exemplo, o comando:
barcode -c -e i25 -b 00123456 >codigo.ps
gera um PS com uma página contendo um código de barras sem checksum (-c) do tipo "interleaved 2-of-5" (-e i25), codificando a string 00123456. (Não vou nem dizer para que alguém geraria um código desses.)
Para converter o PS para um PNG, você pode usar o GIMP, ou o comando convert do ImageMagick (pacote imagemagick):
convert -trim codigo.ps codigo.png
Use it well.
Com este post tenho o objetivo único e exclusivo de reclamar da vida. É improvável que haja qualquer informação proveitosa contida aqui. You have been warned.
Quando eu entrei para a Inf, os Sioux possuíam o mundo as páginas pessoais dos alunos eram contidas no diretório home de seus usuários (especificamente, em ~/public_html). O camarada podia acessar a portal.inf.ufrgs.br por SSH e editar as páginas remotamente com o Vim. Outra vantagem é que se podia rodar scripts remotamente para gerar páginas ou fazer algum procedimento de manutenção. Bem, mais ou menos. O home da portal é montado sem permissão de execução, o que significa que só dá para rodar os binários que já estão lá (e.g., bash, perl), e para poder rodar os scripts que eu tinha no ~/bin sem ter que digitar bash nome-do-script toda vez eu tinha que fazer uma gambiarra no ~/.bashrc para criar uma função com o mesmo nome do script para cada script presente no ~/bin. Mas ok, dava pra tolerar.
Em algum momento da história do mundo, as páginas pessoais foram levadas para um servidor separado, "por motivos de segurança". Conseqüentemente, não dava mais para alterar as páginas via portal. Montar o diretório das páginas por sshfs? Claro que não, a portal não tem sshfs e não executa binários do home. Solicitar à administração da rede para instalarem o sshfs na portal? Não faremos isso, por motivos de segurança. Well, dá pra montar o sshfs usando a máquina de casa, e se divertir com a latência do 3G. Heh.
Um dia me contaram que dava para acessar a portal, e de lá se logar nas máquinas dos laboratórios da graduação, de onde se pode rodar sshfs e tudo o mais que se queira. Hmm, boa! O camarada tem que se logar três vezes (uma na portal, uma nos labs, e uma no sshfs), mas ainda vale a pena. Se o 3G cai meu Vim fica parado lá me esperando e eu não corro o risco de ter salvo meio arquivo no home.
No semestre passado, atualizaram o OS das máquinas dos labs, e agora não é mais possível fazer o login remoto. Yay! Bom, sobrou o sshfs de casa. Felizmente, agora o meu 3G é um pouquinho melhor, com uma latência normalmente abaixo do 1 segundo (vs. os 4 da Tim), e a coisa é pelo menos utilizável.
O servidor das páginas pessoais também monta os homes sem permissão de execução, o que significa que só dá para usar as linguagens de script que já estão lá, i.e., PHP. PHP é uma linguagem/implementação notavelmente medonha, mas ok, é o que temos. Eu quero um blog, e eu quero agora, e eu quero escrever meus posts no Vim e salvar um txt somewhere para postá-los e ser feliz. Let's do it.
O PHP tem uma (so-called) "feature" chamada "magic quotes". A idéia é: programadores são incompetentes e esquecem de escapar as aspas antes de montar uma query de SQL, então vamos escapar automaticamente qualquer entrada que venha de um formulário. (A noção de montar queries de SQL na mão e escapar strings na mão é uma coisa que strikes me as fundamentally wrong, but I digress.) Acontece que o PHP só faz isso se ele estiver configurado para fazer isso no php.ini. E como (pelo que eu entendo) ele faz o escaping antes de rodar o script, o script não tem a oportunidade de desativar as magic quotes. Conclusão? Eu tenho uma função no blog cujo único propósito é receber uma string e remover os escapes se as magic quotes estiverem ativas, ou retornar a string intacta caso contrário. Lindo, hein?
E as closures. Em JavaScript (é, eu vou usar JavaScript, outra linguagem linda, como exemplo, mas acho que assim será mais compreensível do que o Common Lisp que eu ia usar) podemos fazer coisas do tipo:
function menores_que(n, lista) {
return lista.filter(function(x) { return x<n; });
}
menores_que(5, [2,3,5,7,11]); // Retorna [2,3].
Aqui, passamos uma função anônima para o método filter, que seleciona os elementos da lista que satisfazem o predicado passado como argumento. (Esse método só existe no JavaScript da Mozilla, eu acho.) Note que a subfunção usa o n definido fora dela. Em PHP 5.3 foi introduzida uma feature similar. Porém, na versão PHP, você tem que declarar explicitamente quais variáveis externas serão capturadas pela closure. WTF? Isso é tão ridículo que não sei quem mais poderia ter a mesma idéia. Oh, well. (Pelo menos em C++11 há a opção de dizer "captura tudo e não enche".)
Ok, ok. Eu sobrevivi. 753 linhas de PHP. Até que ficou pequeninho para a quantidade de coisas que o blog faz. O problema é que o código ficou todo gambiarrento, e sempre que eu vou adicionar uma feature é um trabalho medonho, e sempre que eu penso em reescrever do zero (coisa que uma hora ou outra eu vou acabar fazendo, já que eu não consigo ficar cinco minutos editando o código atual sem ranger os dentes) eu lembro que terei que fazer isso em PHP (o que produzirá uma quantidade equiparável de ranger de dentes) e abandono a idéia. Mas terá que ser feito, porque PHP é a única coisa que roda na Inf (e muitos outros lugares). Very well. (Actually, not.)
A última coisa que eu quis adicionar no blog foi uma feature de "Comentários recentes" na barra lateral. A idéia original era simplíssima: quando um leitor posta um comentário, além de criar o arquivinho de comentário no diretório apropriado, criamos também um symlink para o arquivinho em um diretório recent_comments da vida. Se o número de links exceder uma quantidade x, removem-se os links mais antigos. Simples, hã? Só que se eu tento usar a função symlink(), ela simplesmente retorna FALSE e nada acontece. What gives?
Acontece que o tal do Suhosin bloqueia a função symlink() do PHP se a opção base_opendir do PHP estiver ativada. Essa opção serve para limitar de que diretórios os scripts podem abrir arquivos. A idéia é que se o usuário puder usar a symlink(), ele poderia em tese despistar a base_opendir criando um link no diretório local para um lugar de onde o script não teria acesso (e.g., /etc/passwd). Porque o PHP não poderia primeiro resolver o symlink e depois testar se o arquivo está dentro dos diretórios permitidos antes de abri-lo, right? Nem a symlink() poderia verificar se o alvo do link está dentro dos diretórios permitidos e criar ou não o link dependendo do caso, right?
Acontece que aparentemete o PHP verifica o caminho resolvido antes de abrir o arquivo. You see, embora o PHP não possa criar symlinks, eu mesmo posso, por sshfs. Quer dizer que qualquer vulnerabilidade que links arbitrários pudessem causar ainda poderiam ser provocadas por mim. Mas eu testei criar um symlink local para o /etc/passwd, e o PHP se recusa a abri-lo, alegando violação da base_opendir. Quer dizer que mesmo que a symlink() estivesse ativa ainda não haveria vulnerabilidade. Mas ela não está, e isso impede gratuitamente minha implementação simples da feature de comentários recentes. Eu poderia entrar em contato com a administração da rede da Inf e pedir para eles habilitarem a feature. Eu não farei isso por duas razões: (1) a admrede vai me dizer que não vai habilitar nada "por motivos de segurança"; (2) um dos princípios por trás deste blog system é rodar em qualquer lugar que tenha um servidor com PHP, sem depender nem de bibliotecas non-standard nem da boa-vontade de sysadmins, e como o symlink() desativado é o padrão do Suhosin, não posso contar com a utilizabilidade dessa função. Tudo bem, o Unix original não tinha symlinks. Vamos brincar de anos setenta.
Copyright © 2012-2013 Vítor Bujés Ubatuba De Araújo
O conteúdo deste site, a menos que de outra forma especificado, pode ser utilizado livremente, com ou sem alterações, desde que seja mencionado o autor, preferivelmente com a URL do documento original.
Powered by Blognir.