About me

About

2012-04-30

Lições que podemos aprender com a NASA.

A NASA é a agência espacial dos EUA e é-lhe associada uma imagem de excelência, pelos feitos alcançados ao longo da sua história, apesar de terem tido a sua quota de reveses.

O contexto aeroespacial justifica condições ímpares que uma empresa comum não pode alcançar, no entanto algumas das práticas que a NASA tem empregue não exigem meios significativos e servem como exemplos a seguir.

Aprender com os erros

Especialmente no início da exploração espacial aconteceram muitos erros, houve muitas falhas e alguns desastres com consequências trágicas. O que a NASA fez foi aprender com esses erros. Com isto melhorou os protótipos seguintes, melhorou os métodos de treino, as técnicas de construção, etc.

O conceito de aprender com os erros em si nem é uma grande lição – o termo “lessons learned” ouve-se nestes dias em qualquer empresa – a sua prática efectiva é que o é. É preciso analisar o que se passou com objectividade, fugindo à tentação humana de (des)culpabilização. E com base nessa análise é preciso tomar acções correctivas.

Ora isto é algo que, da minha experiência, nunca acontece. Isto é, ou nem sequer é feita a análise, ou é mal feita, ou não se tomam acções de correcção.

Registos

A NASA dá muito importância aos registos. Registos de construção, registos de procedimentos, registos fotográficos, recolha de amostras, levantamento de materiais e equipamentos, etc.

Os registos são importantes para analisar problemas ocorridos, para servir de base a novos desenvolvimentos, para reconstituir cenários, etc.

Redundância

Ao longo do tempo popularizou-se o conceito que a NASA faz tudo a dobrar. Isto tem origem nos seus procedimentos reais desde longa data: eles mantinham duas equipas “iguais”, a principal e a de backup, sujeitas aos mesmos treinos durante todo o ciclo de preparação para uma missão, por exemplo. Também tinham réplicas de equipamentos e peças. Os próprios equipamentos construídos eram desenhados com vários sistemas de backup em casa de falha dos componentes principais.

Estar preparado

Além de tudo o exposto, a NASA tem mostrado o conceito de estar preparado, fazendo uso, por exemplo, das mais mundanas técnicas e materiais.

Um exemplo é a fita adesiva, que desde longa data faz parte do material levado pelos astronautas para o espaço, tendo sido já útil por algumas vezes. Não sendo um material de alta tecnologia, foi importante em certos contextos devido à sua versatilidade.


No próximo artigo, usando o caso real da missão Apollo 13, mostrarei como muitos dos eventos reais ocorridos se encaixam nestas práticas.

2012-04-27

O Word matou a documentação técnica.

Sejamos sinceros: escrevemos pouca e má documentação. Sobretudo técnica. Porquê?

  • porque não temos cultura documental: escrevemos pouco… e lemos menos;
  • porque não aprendemos a fazê-lo nas escolas;
  • porque o mercado é demasiado pequeno para haver technical writers;
  • e porque… usamos o Word?

Sim, o Word! O Word porque é universal e porque não é apropriado para escrever documentação técnica. Logo, se universalmente usamos algo inapropriado para uma finalidade, destruímo-la. QED :-)

Universalidade

Ainda estou para conhecer alguém, profissionalmente, que nunca me tenha enviado um .doc ou .docx. Toda a gente usa o Word! Seja para escrever 3 ou 4 linhas de anotações, seja para fazer o CV, seja para escrever regulamentação. Faz-se tudo com o Word. E nem se espera que os destinatários não abram os documentos ou não os consigam editar. É assumido que sim.

Isto “secou” as alternativas, que até existem, mas têm pouca comunidade. Não me serve de muito escrever em markdown (texto) se mais ninguém o fizer.

Inapropriado para documentação técnica

Primeiro algumas premissas que considero pertinentes na produção documental:

  • A documentação técnica deve residir ao lado do código, num sistema de controlo de versões (SCV). Para poder evoluir com o código. É a única forma prática de o conseguir.

  • Uma parte substancial da documentação deve ser gerada automaticamente, como APIs, tabelas, diagramas ou modelos de dados.

  • Ferramentas simples e universais devem poder ser usadas para manter documentação: controlar versões, obter diferenças, corrigir ortografia, substituições massivas, etc.

  • Devem poder ser gerados vários formatos finais para diferentes meios de publicação. Exemplos: PDF, html único, html paginado.

  • Os formato finais devem poder ser construídos automaticamente.

Ora nada disto se consegue em termos práticos com o Word! O Word implica ter um enorme programa lançado, que só corre em Windows, usando as suas idiossincrasias. Ficamos fechados nesse interface, com um sistema de automatização complexo e limitado em VBA. Torna-se difícil descobrir diferenças entre versões. Torna-se impossível haver edição partilhada.

A técnica mais frequente de edição em equipa é penosamente sequencial: consiste em passar um .doc de mail em mail pelos vários editores. Isto tem impactos em termos de tempo – tem que se esperar que o actual “proprietário” passe o documento – e impede a resolução imediata de conflitos. No cenário em que o editor N estraga parte das edições do editor N-1, como passa o documento em seguida ao editor N+1, o N-1 só lhe voltará a ter acesso na revisão final, o que irá dificultar bastante o seu trabalho de correcção. Na realidade já assisti a muitas edições perderem-se desta forma.

Aos pontos acima acrescento um novo:

  • A tarefa de documentar deve ser tão leve e fácil como a de programar!

Abrir o Word para documentar uma API, por exemplo, é pesado e “difícil”. Um fluxo típico de trabalho consiste em saltar de janela em janela, fazendo vários copy-pastes. Copy da janela de código, paste para a janela do Word. Todos os aspectos gráficos terão de ser feitos manualmente, um a um. Exemplo: colocar todas as variáveis em itálico, ou trechos de código numa fonte fixa.

Solução: texto!

Todos os pontos anteriores conseguem-se cumprir facilmente usando texto como representação fonte da nossa documentação.

Como tal a documentação deve ser escrita directamente em texto, ou pelo menos ter uma representação textual 1, para que possa ser processada por ferramentas simples e comuns. O texto é um formato verdadeiramente universal, logo qualquer ferramenta virtualmente o suporta.

Assim:

  • Edição leve e fácil: basta um editor de texto. Pode ser o nosso preferido. Pode ser o mesmo que usamos para editar código. Isto significa que podemos alterar a documentação sem sequer sair do contexto de desenvolvimento.

  • Colocando os ficheiros de documentação num SCV a gestão de versões é imediata. Quem editou o quê? O que mudou entre duas versões? Conseguimos reverter uma edição errada, ou até melhor, reverter as porções erradas de um dada edição. Tal como já o fazemos em relação ao código.

  • Edição simultânea em equipa: várias pessoas têm a hipótese de trabalhar na documentação em simultâneo, deixando o SCV tratar do merge das edições.

  • O sistema de construção do projecto pode ser adaptado para construir a documentação, incluindo também a geração das partes automáticas.

Referências


  1. a edição até pode ser visual, através de um editor WYSIWYG, mas o formato dos ficheiros gravados deveria ser texto compreensível.

2011-10-14

Dennis Ritchie, 1941-2011

Morreu Dennis Ritchie, da fama do C e Unix, aos 70.

Um dos meus ídolos, fez parte da geração de ouro da computer science dos Bells Labs, a par de Ken Thompson, Brian Kernighan, Alfred Aho, Douglas McIlroy, Joe Ossanna (falecido em 1977), entre outros. Não sei porquê mas sempre foi o meu preferido.

A inspiração não dá para mais que isto:

main() { int a=58,b=a/2^3<<4,c=b^5; 
printf("%c%c%c%c",a,b,c,a&017); }

2011-09-30

Numeração romana e sed.

A lógica da numeração romana é curiosa porque não se baseia em nenhuma base, embora o 5 e o 10 tenham um papel tipo base.

Outro aspecto interessante da sua lógica é que obedece a um padrão greedy. Como tal podem-se usar expressões regulares para converter de/para romano. Entra o sed.

  • conversão de romano para decimal

    #! /bin/sed -f

    # fr_roman.sed -- from roman (to decimal)
    # Carlos Duarte, 980707

    # usage: echo MCMXCVIII | sed -f fr_roman.sed
    # output: 1998
    #
    # converts roman literals to decimal
    # some input error checking is done, but not all cases are covered
    # (bigger constructions following smallers, for instance)
    #

    # build table, all possible constructs that evaluate to [1-9]0* are here
    1{
    x
    s/$/0/
    s/$/I1/
    s/$/II2/
    s/$/III3/
    s/$/IV4/
    s/$/V5/
    s/$/VI6/
    s/$/VII7/
    s/$/VIII8/
    s/$/IX9/
    s/$/X10/
    s/$/XX20/
    s/$/XXX30/
    s/$/XL40/
    s/$/L50/
    s/$/LX60/
    s/$/LXX70/
    s/$/LXXX80/
    s/$/XC90/
    s/$/C100/
    s/$/CC200/
    s/$/CCC300/
    s/$/CD400/
    s/$/D500/
    s/$/DC600/
    s/$/DCC700/
    s/$/DCCC800/
    s/$/CM900/
    s/$/M1000/
    s/$/MM2000/
    s/$/MMM3000/
    x
    }

    # converts from MMXXII to 2000,20,2, from lookup table
    s/^/\
    /
    G

    ta
    :a
    s/\n\(..*\)\(.*\n.*[0-9]\)\1\([1-9]0*\)/\3,\
    \2\1\3/
    tb

    s/\n/>>> error:/
    s/\n.*//
    #q
    b

    :b
    /\n\n/!ba
    s/\n\n.*$//

    # convert from 2000,20,2, to 2022. this is not trivial!
    #
    # steps:
    # 2000,20,2, -> 2000<20>2, <20> is the work digit
    :d
    s/,/</
    te
    :e
    s/,/>/
    tf

    # exit point here: contains `2022,' so, remove the `,' and go
    s/.$//
    b

    # 2000<20>2, -> 2000,:20<20>2, : is the cursor for changing the 20 in ,:20
    # into 00 (for later, remove these from 2000)
    :f
    s/<\(.*\)>/,:\1&/

    # 2000,:20<20>2, -> 2000,00:<20>2,
    tc
    :c
    s/:[0-9]/0:/
    tc

    # 2000,00:<20>2, -> 2020,2, convert next digit
    s/\(0*\),\1:<//
    s/>/,/
    bd
  • conversão de decimal para romano

    #! /bin/sed -f

    # to_roman.sed -- converts decimal to roman
    # Carlos Duarte, 980707

    # do not perform error checking
    # converts input treated as arabic decimals, to roman literal
    #
    # eg: echo 1998 | sed -f to_roman.sed
    # outputs: MCMXCVIII
    #

    # build table, all possible constructs that evaluate to [1-9]0* are here
    1{
    x
    s/$/1I/
    s/$/2II/
    s/$/3III/
    s/$/4IV/
    s/$/5V/
    s/$/6VI/
    s/$/7VII/
    s/$/8VIII/
    s/$/9IX/
    s/$/10X/
    s/$/20XX/
    s/$/30XXX/
    s/$/40XL/
    s/$/50L/
    s/$/60LX/
    s/$/70LXX/
    s/$/80LXXX/
    s/$/90XC/
    s/$/100C/
    s/$/200CC/
    s/$/300CCC/
    s/$/400CD/
    s/$/500D/
    s/$/600DC/
    s/$/700DCC/
    s/$/800DCCC/
    s/$/900CM/
    s/$/1000M/
    s/$/2000MM/
    s/$/3000MMM/
    x
    }

    s/.*/:&:/
    ta
    :a
    s/:.\([^:]*\):$/&\1:/
    ta

    :c
    s/:\(.\)/\1,/
    tb
    :b
    s/,[0-9]/0,/
    tb
    /,::$/bd
    s/,/;/
    bc

    :d
    s/...$/;/
    s/;00*//g
    s/^/,/

    G
    te
    :e
    s/,\([^;]*\);\(.*\n.*\)\1\([IVXLCDM][IVXLCDM]*\)/\3,\2\1\3/
    te
    s/.\n.*//
  • compilador de romano para expressão numérica do equivalente decimal

    • echo MCXLIII | sed -f from_roman.sed => 1000+100+40+1+1+1
    • echo MCMLXXXI | sed -f from_roman.sed => 1000+900+50+10+10+10+1
    #! /bin/sed -f

    # from_roman.sed -- output bc(1) code that convert roman to decimal
    # Carlos Duarte, 980707

    # usage: echo MCXLIII | sed -f from_roman.sed | bc

    s/$/\
    0M1000CM900D500CD400C100XC90L50XL40X10IX9V5IV4I1/

    s/^/\
    /
    ta
    :a
    s/\n\(..\)\(.*\n.*[0-9]\)\1\([1-9]0*\)/\3+\
    \2\1\3/
    tb
    s/\n\(.\)\(.*\n.*[0-9]\)\1\([1-9]0*\)/\3+\
    \2\1\3/
    tb

    s/\n/0; ">>> error:/
    s/\n.*/"/
    #q
    b

    :b
    /\n\n/!ba
    s/.\n\n.*$//

2011-09-29

Código de ordenação em awk.

  • bubble sort

    function bubble_sort(arr, a, b,     i,done,t) {
    i = a
    done = 1
    for (;;) {
    if (i == b) {
    if (done)
    break
    i = a
    done = 1
    }
    if (arr[i] > arr[i+1]) {
    t = arr[i]
    arr[i] = arr[i+1]
    arr[i+1] = t
    done = 0
    }
    i = i+1
    }
    }


    # main
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { bubble_sort(x, 1, n); for (i=1; i<=n; i++) print x[i] }
  • heap sort

    ## works only on arrays indexed [1..n]

    function heap_sort(arr, n, i,t) {
    make_heap(arr, n)
    for (i=n; i>1; i--) {
    t = arr[1]
    arr[1] = arr[i]
    arr[i] = t
    sift_down(arr, i-1, 1)
    }
    }

    function make_heap(arr, n, i) {
    for (i=int(n/2); i>=1; i--)
    sift_down(arr, n, i)
    }

    function sift_down(arr, n, i, k,j,t) {
    k = i
    do {
    j = k
    if (2*j <= n && arr[2*j] > arr[k])
    k = 2*j
    if (2*j < n && arr[2*j+1] > arr[k])
    k = 2*j+1
    t = arr[j]
    arr[j] = arr[k]
    arr[k] = t
    } while (j != k);
    }

    # main
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { heap_sort(x, n); for (i=1; i<=n; i++) print x[i] }
  • insert sort

    # sorts array ARR[A..B]
    function insert_sort(arr, a, b, i,j,t) {
    for (i=a+1; i<=b; i++) {
    if (arr[i] > arr[i-1])
    continue
    t = arr[i]
    j=i-1
    do
    arr[j+1] = arr[j];
    while (--j>0 && t < arr[j]);
    arr[j+1] = t
    }
    }

    # main
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { insert_sort(x, 1, n); for (i=1; i<=n; i++) print x[i] }
  • merge sort

    function merge_sort(arr, a, b,      k) {
    if (a<b) {
    k = int((a+b)/2)
    merge_sort(arr, a, k)
    merge_sort(arr, k+1, b)
    merge(arr,a,k,b)
    }
    }

    function merge(arr, a, k, b, i,j,p,c) {
    j = i = a
    p = k+1
    while (a <= k && p <= b) {
    if (arr[a] <= arr[p]) {
    c[i++] = arr[a++]
    } else {
    c[i++] = arr[p++]
    }
    }
    while (a <= k) c[i++] = arr[a++]
    while (p <= b) c[i++] = arr[p++]

    while (j<=b) {
    arr[j] = c[j]
    j++
    }
    }

    ## main
    function pr(arr,i,j) { while (i<=j) print arr[i++] }
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { merge_sort(x, 1, i); pr(x, 1, i); }
  • quick sort

    function quick_sort(arr, a, b,      pivot,i,j,t) {

    if (a >= b)
    return

    if (a+1 == b) {
    if (arr[a] > arr[b]) {
    t = arr[a]
    arr[a] = arr[b]
    arr[b] = t
    }
    return
    }

    pivot = median(arr[a], arr[b], arr[int((a+b)/2)])
    i = a-1;
    j = b+1;
    for (;;) {
    while (arr[++i] < pivot)
    ;
    while (arr[--j] > pivot)
    ;

    if (i>=j)
    break

    t = arr[i]
    arr[i] = arr[j]
    arr[j] = t
    }
    quick_sort(arr, a, i-1)
    quick_sort(arr, i, b)
    }

    function median(a, b, c) {
    if (a<b) {
    if (b<c)
    return c
    if (a<c)
    return c
    return a
    }
    if (a<c)
    return c
    if (b<c)
    return c
    return b
    }

    # main
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { quick_sort(x, 1, n); for (i=1; i<=n; i++) print x[i] }
  • selection sort

    # sorts array ARR[A..B]
    function selection_sort(arr, a, b, i,j,k,t) {
    for (i=a; i<=b; i++) {
    k=i
    for (j=i+1; j<=b; j++) {
    if (arr[k] > arr[j])
    k = j
    }
    if (k != i) {
    t = arr[i]
    arr[i] = arr[k]
    arr[k] = t
    }
    }
    }

    # main
    { x[++n] = substr($0,1) } ## lexical sort
    #{ x[++n] = $0+0 } ## numerical sort
    END { selection_sort(x, 1, n); for (i=1; i<=n; i++) print x[i] }

E por falar em ordenação...

E por falar em ordenação…

Um z80, lançado originalmente em 1976, ordenava 1000 números em 60s, usando o algoritmo selection sort, programado directamente em código máquina, com o processador dedicado a 100% à operação. Ou seja, aproveitando as potencialidades máximas do sistema.

Alguns anos mais tarde, vejamos o seguinte exercício:

  • CPU: Intel(R) Core(TM)2 Duo CPU T7250 @ 2.00GHz, mas fixado a 800MHz
  • sistema operativo linux 2.6.33.7 32 bits
  • 101 processos mas load average 0.00, i.e. máquina virtualmente sem carga
  • linguagem awk: interpretada, de “alto nível”
  • cenário: ordenação de strings de tamanho arbitrário
  • algoritmos: selection sort, insertion sort, heap sort, bubble sort, merge sort, quick sort

Execução:

dd if=/dev/urandom | tr -c -d A-Za-z0-9_\\n | sed 10000q | \time mawk -f quick_sort.awk | sort -c
  1. dd if=/dev/urandom | tr -c -d A-Za-z0-9_\\n | sed 10000q: produção de 1000 linhas de texto aleatórias;
  2. \time mawk -f quick_sort.awk: ordenação do input e medição do tempo que demorou
  3. sort -c: validação da correcção da ordenação

Algumas medições efectuadas, com os tempos sempre em segundos:

  • selection sort revisitado às 1000 linhas: 0.37;

  • Benchmark de 10k linhas (tempo em segundos):
    • bubble_sort.awk => 100.78
    • selection_sort.awk => 26.70
    • insert_sort.awk => 16.76
    • heap_sort.awk => 1.66
    • quick_sort.awk => 1.41
    • merge_sort.awk => 1.33

Agora algumas contas:

  • se o selection sort é O(n^2), significa que o tempo de execução num ambiente pode ser calculado derivando uma constante c;
  • assim: t=n^2/c; logo c=n^2/t;
  • como já temos n=1000 e t=26.70 no caso do selection sort, fica c=3745318;
  • qual o número de itens necessários para a ordenação demorar 60s? n^2=ct => n=sqrt(ct), sabemos c=3745318 e pretendemos t=60, logo n=14990;
  • testando:
    • dd if=/dev/urandom | tr -c -d A-Za-z0-9_\\n | sed 14990q | \time mawk -f selection_sort.awk |sort -c
    • Resultado: 63.87. Perfeito. :)

Estes resultados mostram uma coisa que por vezes é negligenciada: o hardware tem evoluído bastante mas a dimensão dos dados também. Se os algoritmos que processam os dados não forem eficientes, o novo hardware traz poucos ganhos em escala e pode acontecer a lentidação causada pelo crescimento dos dados ultrapassar os ganhos introduzidos pelo novo hardware.

Neste pequeno exemplo, uma ordenação de 1k números no velhinho spectrum corresponde a uma ordenação de 15k num portátil hoje em dia, em powersave :), mais coisa menos coisa. A comparação não foi 100% rigorosa (assembler vs awk, etc) mas tem o rigor suficiente para o que se demonstra.

2011-09-27

Memórias do zx spectrum... e código máquina!

No fim de semana estive a fazer uma limpeza (leia-se: pôr no lixo) a coisas velhas. Invariavelmente nestas alturas acontecem duas coisas: um ataque de nostalgia… e a redescoberta de algumas preciosidades.

Acabei por encontrar literalmente centenas de páginas de código para o ZX Spectrum que fiz quando era mais novo. A maior parte é código máquina para o z80, o processador que equipava o spectrum. Centenas de páginas em papel mesmo, tudo manuscrito, que era a única forma que eu tinha na altura de fazer código: escrevê-lo em papel.

Curiosidades:

  • fazia o código todo em papel primeiro, em rascunho, e posteriormente passava a limpo, para ficar legível, como se vê nas fotos mais abaixo;

  • normalmente as coisas funcionavam bem à primeira, o que até é lógico, porque escrevendo as coisas no papel presta-se muito mais atenção que fazendo-o directamente no computador;

  • o detalhe dado à documentação! A frente de cada folha servia para listar o código máquina e o verso para o documentar, com casos de uso, notas, fluxogramas e tudo. Fiquei seriamente surpreendido:

    1. primeiro pelos bons hábitos que tinha antes de ser profissional, sem qualquer tipo de formação… e que se perderam quando passei a sê-lo!

    2. pela paciência que tinha!

    3. o meu motto no linkedin nunca esteve tão certo como nesta altura: “Goal: to get a life!”.

Este material data dos anos 1991 ~ 1992…
Rotina de ordenação em assembler.

Tinha desenhado estas pautas para escrever os meus programas. A primeira coluna era o endereço de memória onde o programa iria residir, a segunda era o código máquina em hexadecimal e a terceira era a mnemónica assembler. Tudo feito à mão. Que paciência :-)

Nesta altura ainda não sabia nada de algoritmos nem estruturas de dados, como tal “inventei” um algoritmo qualquer de ordenação que me veio à cabeça. Acho que é o selection sort.

código de ordenação

A correspondente documentação no verso da página. Algumas preciosidades:

  • Ocupa 70 bytes + 2 para dados”.
  • Demora cerca de 60 segundos para ordenar 1000 números”.
  • A seguinte fórmula dá-nos aprx o tempo em segundos da ordenação: n^2/16000” — interessante que sem qualquer conhecimento de algoritmia e notações O(), relacionei claramente o tempo de execução com a ordem assimptótica do algoritmo, O(n^2). Eheh!!

documentação nas costas

Páginas e páginas e páginas de código e documentação manuscritas.

papéis