Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

Noun-centric vs. verb-centric OO

2013-05-11 01:36 -0300. Tags: comp, prog, lisp, pldesign

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.

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.

2 comentários

Incomparável

2013-05-10 18:39 -0300. Tags: random, img

Incomparável como seu carinho

Incomparável... como seu carinho.

2 comentários

Twitter via linha de comando

2013-05-07 14:27 -0300. Tags: comp, unix, web, about

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.)

4 comentários

Culinária para os Totalmente Perdidos #1: Miojo com molho de atum/guisado

2013-05-06 05:31 -0300. Tags: cook, home

Isso aqui é tão simples que praticamente não conta como receita, mas dada a freqüência com que eu ouço gente dizer que come o Miojo puro (ou ainda, com aquele temperinho maligno (shudder)), talvez este post possa ser útil.

Ingredientes

O molho

Atum edition: Abra a lata de atum e despeje fora o óleo/água. Despeje o atum em uma panela e deixe cozinhar por cerca de um minuto, mexendo ocasionalmente.

Guisado edition: Despeje um pouquinho assim de óleo (cerca de 5 colheres de sopa) em uma panela e deixe aquecer por uns 30 segundos. Despeje o guisado e deixe cozinhar por uns três minutos, ou até que a carne não esteja mais vermelha, mexendo ocasionalmente.

Em ambos os casos: Feitos os passos acima, despeje o molho pronto sobre a coisa toda e misture, e deixe cozinhar por cerca de dois minutos, mexendo ocasionalmente. Enquanto o molho cozinha, você pode adicionar o orégano (umas três colheres de chá, ou o quanto seu coração disser), se desejar. (Na minha experiência, o orégano fica melhor com atum do que com guisado.) Está feito.

Rendimento: o suficiente para duas porções de massa (veja adiante). Mesmo que você só vá fazer uma porção de massa, se optar pelo atum, é melhor fazer a quantidade "inteira" e guardar o que sobrar, pois atum estraga depois de aberto se não for cozido.

A massa

Use de ¾ a 1 tablete de massa, dependendo do grau de fome, para cada pessoa. (Para referência: 1 tablete = 125g; 1 miojinho = 80g). Ferva água suficiente para submergir o miojo. Despeje o miojo na água e deixe cozinhar por 5 minutos (sim, cinco; aquela história de "pronto em 3 minutos" funciona para o miojinho, mas nem tanto para o miojão), ou até que a consistência lhe agrade. Despeje a água fora e misture a massa com o molho. Divirta-se.

Observações

Você pode fazer a massa e o molho em paralelo e ganhar tempo, ou fazer primeiro a massa e depois o molho e ganhar uma panela a menos para lavar.

Há quem diga que não se deve derramar o óleo do atum na pia, pois provoca entupimento e caos e destruição. Ao invés disso, deve-se coletá-lo e entregá-lo em um daqueles lugares míticos em que se coleta óleo. (Imagino eu que derramar o óleo em uma garrafa e colocá-lo no lixo também seja uma alternativa razoável.)

2 comentários

#123

2013-05-02 23:20 -0300. Tags: random, life, mind, ramble

Caro mundo,

Ao invés de estar aproveitando meu tempo de uma maneira produtiva, resolvi passar o final do meu dia lendo o Hyperbole and a Half desde o primeiro post. Por mais improdutivo que seja, não posso dizer que foi uma má decisão. Além disso, ler certas coisas me fez pensar que morar nessa casa com o chão todo torto e que está começando a ficar úmido com a proximidade do inverno e que em breve vai começar a verter água, ao mesmo tempo em que caracóis começam a invadir a casa, até que não é algo tão ruim assim. O tempo está com cara de tempestade iminente, o que significa que meu 3G vai começar a se arrastar como os recém-mencionados caracóis, o que pode dificultar minha continuada leitura do recém-mencionado blog, mas enfim. (A concretização da tempestade, por sua vez, pode levar à falta de luz na rua em que se localiza a recém-mencionada residência pela qual eu acabo de expressar renovado apreço, rua esta com uma infraestrutura elétrica menos que invejável. Mas enfim. O tempo está bonito, pelo menos.)

(As a sidenote, se por acaso alguém estiver preocupado com o sumiço da Allie no final de 2011, saiba que ela deu sinais de vida no Reddit em março de 2012. Eu fiquei contente quando encontrei isso, anyway. [Update: She's back! Good timing, huh?])

(Como se diz "sidenote" em português? "Preterlóquio?" (Estou surpreso pela total ausência de resultados na minha busca no Google por "preterlóquio". Considerem a palavra criada (ainda que, talvez, não necessariamente com o mesmo exato sentido).))

Mas não é sobre nada disso que eu vim falar hoje. O que eu vim falar aqui originalmente é sobre o design de shells. Lembram que eu escrevi um post quatro meses atrás dizendo que estava querendo escrever um shell? Pois bem, obviamente eu não fiz isso, mas nesse meio tempo eu tive idéias. (Se me pagassem para ter idéias de software de aplicabilidade questionável eu estaria com a vida ganha. Ou não.) Na verdade eu agora fui olhar o respectivo post e descobri que ele é muito maior do que eu me lembrava, de tal maneira que eu já nem sei se vale a pena escrever outro post sobre o assunto. Quem sabe assim eu resolvo criar vergonha na cara e de fato implementar as idéias, ao invés de ficar discutindo o quão fantasticamente legais elas são. Na verdade meu objetivo original era pedir sugestões, então talvez até houvesse um ponto em escrever o post, mas não sei mais. Se alguém quiser dar sugestões anyway, sinta-se à vontade.

Ao invés disso, eu vou falar de outra coisa: vou falar do fato de que os seres humanos se acostumam com as coisas e com o tempo passam a perceber qualquer situação em que se encontrem como algo dado e corriqueiro. Não, isso não é novidade nenhuma; você já se acostumou com essa idéia também. Mas às vezes o módulo de assumptions da minha cabeça tem um lapso de funcionamento e eu tenho um choque de realidade. Por exemplo, estava eu hoje à tarde debruçado sobre a mesa da sala da monitoria tentando dormir (já que normalmente não aparece ninguém lá para pedir ajuda e é difícil fazer qualquer coisa produtiva por lá, pois é difícil se concentrar com o barulho (mas não dormir, you see (embora eu não tenha conseguido dormir de fato (so far este blog contém 1083 pares de parênteses, por sinal, sem contar trechos de código entre tags PRE, mas isso não vem ao caso no momento)))). Sem sucesso em minha tentativa de dormir, levanto e resolvo sair para ir ao banheiro (leia-se: pretexto para caminhar um pouco). Em um estado semi-acordado, começo a andar pelo corredor para a rua. Nesse momento eu olho ao redor e penso: "WTF? I'm in the fucking Instituto de Informática? Na Universidade Federal do Rio Grande do Sul? Que viagem!" Não só estou no fucking Instituto de Informática, como também no final do curso. Especificamente, o curso termina em dois meses se eu me pilhar de terminar o TCC neste semestre (estrategicamente, entretanto, isso não tem vantagem nenhuma; é um semestre de RU a menos). How mad is that? E ainda por cima eu estou morando sozinho? Eu tenho uma casa só pra mim? Sou eu que pago as contas? Eu posso fazer whatever the hell I please da minha vida? Por que diabos ninguém tinha me contado isso antes?

E é isso, galera. A moral é que às vezes a gente precisa de uns tapas na cabeça para ela ir para o lugar certo. (Não digo "voltar para o lugar certo" porque às vezes ela nunca esteve no lugar certo.) Agora, se vocês me permitem, eu vou continuar lendo Hyperbole and a Half até dormir em cima do teclado, já que eu descobri há meia hora atrás que não vou ter aula amanhã.

5 comentários

Diru tuj, kiom estas la batalpovo de Kakarotto?

2013-04-28 23:52 -0300. Tags: esperanto, random, img

Estas pli ol ok mil!!!

Não, eu não tenho nada melhor para fazer (um TCC, por exemplo).

[P.S.: Tecnicamente era para ter um "ĝi" no começo da frase, eu suponho, mas não teria o mesmo impacto.]

3 comentários

Gravando áudio e eliminando ruído com o SoX

2013-04-26 19:52 -0300. Tags: comp, unix, audio, mundane

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.

2 comentários

Underground revolutions

2013-04-20 02:48 -0300. Tags: about

O blog system foi completamente reescrito.

Se você não percebeu a diferença, a operação foi um sucesso.

(Caso contrário, deixe seu bug report nos comentários (assumindo que os comentários estejam funcionando).)

O código-fonte será publicado assim que algumas bagunças forem eliminadas (ou assim que alguém pedir, o que vier primeiro).

[Update (23/04/2013): Bagunças não foram eliminadas. O código está disponível.]

3 comentários

So many parens

2013-04-17 17:17 -0300. Tags: comp, prog, lisp

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>.

9 comentários

One man's gambiarra is another man's technique

2013-04-17 14:52 -0300. Tags: comp, prog, ramble

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...

Considered harmful?

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.

Appendix A

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.

Infrequently Asked Questions in comp.lang.c

_____

(O título do post é uma paráfrase de uma paráfrase de uma expressão idiomática.)

12 comentários

Main menu

Posts recentes

Tags

comp (72) unix (27) life (27) prog (26) random (22) about (18) mundane (18) lang (17) mind (14) web (13) img (10) rant (9) privacy (8) bash (7) esperanto (7) ramble (6) conlang (5) pldesign (5) freedom (4) home (4) lisp (4) misc (4) book (4) copyright (4) kbd (3) film (3) academia (3) security (3) politics (3) music (3) android (2) editor (2) poem (2) physics (2) network (2) worldly (2) cook (2) wrong (2) c (2) php (2) mestrado (2) perl (1) pointless (1) kindle (1) shell (1) audio (1)

Elsewhere

Quod vide


Copyright © 2012-2014 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.