Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript no terminal

JavaScript no terminal

Se o JavaScript está em todos os lugares, obviamente ele também está no terminal. Nessa talk vamos ver como desenvolver CLIs com JavaScript usando boas práticas, como módulos e testes que irão garantir a flexibilidade e manutenção de nossas ferramentas.

fernahh

April 23, 2016
Tweet

More Decks by fernahh

Other Decks in Programming

Transcript

  1. JAVASCRIPT NO TERMINAL
    fernahh.com.br
    $_

    View Slide

  2. @fernahh
    fernahh.com.br

    View Slide

  3. contaazul.com

    View Slide

  4. Os dois porques
    1. Por que CLI?

    View Slide

  5. Agilidade
    Tarefas podem ser executadas de forma mais ágil,
    sem contar que é mais rápido do que abrir um
    programa.

    View Slide

  6. Versatilidade
    Programas que podem ser usados por humanos ou
    robôs.

    View Slide

  7. Os dois porques
    2. Por que JavaScript?

    View Slide

  8. WTF?!!!

    View Slide

  9. O principal responsável pela universalização do JavaScript. Nos
    dá módulos e ferramentas que facilitam o desenvolvimento de
    programas maduros.

    View Slide

  10. NPM

    View Slide

  11. NPM
    ● + 250 mil pacotes

    View Slide

  12. NPM
    ● + 250 mil pacotes
    ● Facilita a instalação

    View Slide

  13. NPM
    ● + 250 mil pacotes
    ● Facilita a instalação
    ● Gerenciamento de versão

    View Slide

  14. JAVASCRIPT É FODA!
    Está em todos os lugares e, segundo
    o Stack Overflow, é a linguagem mais
    popular do mundo.

    View Slide

  15. View Slide

  16. Ken Thompson

    View Slide

  17. Ken Thompson
    ● Criador da codificação UTF-8

    View Slide

  18. Ken Thompson
    ● Criador da codificação UTF-8
    ● Co-criador do Go

    View Slide

  19. Ken Thompson
    ● Criador da codificação UTF-8
    ● Co-criador do Go
    ● Primeiro a criar a Filosofia Unix

    View Slide

  20. Filosofia Unix
    Conjunto de normas e abordagens para o
    desenvolvimento de software.

    View Slide

  21. Regra da Composição
    Projete programas que podem ser usados com
    outros programas.

    View Slide

  22. $ ls

    View Slide

  23. $ ls
    auto Documents Personal
    Code Downloads Pictures
    Desktop Music Public

    View Slide

  24. $ ls | grep Do

    View Slide

  25. $ ls | grep Do
    Documents
    Downloads

    View Slide

  26. process
    Objeto global do Node.js que possui atributos e funções que
    podem ser usados para ouvir, ler ou escrever algo.

    View Slide

  27. process.argv
    Retorna um array de argumentos, os quais retornam as entradas
    do usuário.

    View Slide

  28. #!/usr/bin/env node
    console.log(process.argv)

    View Slide

  29. #!/usr/bin/env node
    console.log(process.argv)

    View Slide

  30. #!/usr/bin/env node
    console.log(process.argv)

    View Slide

  31. $ node ./index.js Foo Bar

    View Slide

  32. $ node ./index.js Foo Bar
    [
    '/home//bin/node',
    '/home//index.js',
    'Foo',
    'Bar'
    ]

    View Slide

  33. #!/usr/bin/env node
    const argv = require('minimist')(process.argv.slice(2))
    console.log(argv)

    View Slide

  34. #!/usr/bin/env node
    const argv = require('minimist')(process.argv.slice(2))
    console.log(argv)

    View Slide

  35. #!/usr/bin/env node
    const argv = require('minimist')(process.argv.slice(2))
    console.log(argv)

    View Slide

  36. $ node ./index.js Foo Bar --verbose

    View Slide

  37. $ node ./index.js Foo Bar --verbose
    { _: [ 'Foo', 'Bar' ], verbose: true }

    View Slide

  38. process.stdin, process.stdout
    Devem ser usados, respectivamente, para ler e escrever.
    Dessa forma o usuário poderá usar outros programas ao mesmo
    tempo.

    View Slide

  39. #!/usr/bin/env node
    const logger = str => process.argv.write(`${str} \n`)
    logger('RSJS')

    View Slide

  40. #!/usr/bin/env node
    const logger = str => process.argv.write(`${str} \n`)
    logger('RSJS')

    View Slide

  41. #!/usr/bin/env node
    const logger = str => process.argv.write(`${str} \n`)
    logger('RSJS')

    View Slide

  42. $ node ./index.js

    View Slide

  43. $ node ./index.js
    RSJS

    View Slide

  44. process.exit()
    Serve para sinalizar se a execução deu certo ou errado.

    View Slide

  45. #!/usr/bin/env node
    if (error)
    process.exit(1)

    View Slide

  46. #!/usr/bin/env node
    if (error)
    process.exit(1)

    View Slide

  47. Regra da Simplicidade
    Projete para a simplicidade; adicione complexidade
    somente onde é necessário.

    View Slide

  48. package.json
    É muito mais que um manifesto da sua aplicação. Pode ser
    usado para automatizar tarefas e facilitar a instalação de seu
    programa.

    View Slide

  49. {
    "name": "RSJS",
    "version": "0.1.0",
    "description": "A Simple CLI Example",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "bin": {
    "rsjs": "./index.js"
    },
    "dependecies": {
    "minimist": "^1.2.0"
    }
    }

    View Slide

  50. {
    "name": "RSJS",
    "version": "0.1.0",
    "description": "A Simple CLI Example",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "bin": {
    "rsjs": "./index.js"
    },
    "dependecies": {
    "minimist": "^1.2.0"
    }
    }

    View Slide

  51. $ npm test

    View Slide

  52. $ npm test
    Error: no test specified
    npm ERR! Test failed. See above for more details.

    View Slide

  53. {
    "name": "RSJS",
    "version": "0.1.0",
    "description": "A Simple CLI Example",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "bin": {
    "rsjs": "./index.js"
    },
    "dependecies": {
    "minimist": "^1.2.0"
    }
    }

    View Slide

  54. $ npm install -g

    View Slide

  55. $ npm install -g
    $ rsjs

    View Slide

  56. $ npm install -g
    $ rsjs
    RSJS

    View Slide

  57. $ which rsjs

    View Slide

  58. $ which rsjs
    /home//bin/rsjs

    View Slide

  59. {
    "name": "RSJS",
    "version": "0.1.0",
    "description": "A Simple CLI Example",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "bin": {
    "rsjs": "./index.js",
    "foo": "./foobar.js"
    },
    "dependecies": {
    "minimist": "^1.2.0"
    }
    }

    View Slide

  60. Evite decorações
    Headers e decorações sempre devem ser evitados,
    pois eles dificultam o parse de seu output.

    View Slide

  61. Regra da Modularidade
    Escreva partes simples, conectadas por interfaces
    simples.

    View Slide

  62. Convenção CommonJS
    Segundo a convenção CommonJS, executáveis
    devem ficar em um diretório bin.

    View Slide

  63. ├── package.json
    ├── index.js
    └── bin
    └── rsjs
    #!/usr/bin/env node
    'use strict'
    const logger = require('../index.js')
    const argvs = require('minimist')(process.argv.slice(2))
    logger(argvs._)

    View Slide

  64. ├── package.json
    ├── index.js
    └── bin
    └── rsjs
    #!/usr/bin/env node
    'use strict'
    const logger = require('../index.js')
    const argvs = require('minimist')(process.argv.slice(2))
    logger(argvs._)

    View Slide

  65. ├── package.json
    ├── index.js
    └── bin
    └── rsjs
    #!/usr/bin/env node
    'use strict'
    const logger = require('../index.js')
    const argvs = require('minimist')(process.argv.slice(2))
    logger(argvs._)

    View Slide

  66. ├── package.json
    ├── index.js
    └── bin
    └── rsjs
    'use strict'
    const logger = str => process.stdout.write(`${str} \n`)
    module.exports = logger

    View Slide

  67. CLI.js e API privada
    Uma boa prática é isolar a lógica de negócio em uma
    “API” privada.

    View Slide

  68. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = str => process.stdout.write(`${str} \n`)
    module.exports = logger

    View Slide

  69. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = str => process.stdout.write(`${str} \n`)
    module.exports = logger

    View Slide

  70. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const run = argvs => logger(argvs)
    exports.run = argvs => run(argvs)

    View Slide

  71. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const run = argvs => logger(argvs)
    exports.run = argvs => run(argvs)

    View Slide

  72. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const run = argvs => logger(argvs)
    exports.run = argvs => run(argvs)

    View Slide

  73. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const run = argvs => logger(argvs)
    exports.run = argvs => run(argvs)

    View Slide

  74. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    #!/usr/bin/env node
    'use strict'
    const cli = require('../cli.js')
    const argvs = require('minimist')(process.argv.slice(2))
    cli.run(argvs._)

    View Slide

  75. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    #!/usr/bin/env node
    'use strict'
    const cli = require('../cli.js')
    const argvs = require('minimist')(process.argv.slice(2))
    cli.run(argvs._)

    View Slide

  76. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    #!/usr/bin/env node
    'use strict'
    const cli = require('../cli.js')
    const argvs = require('minimist')(process.argv.slice(2))
    cli.run(argvs._)

    View Slide

  77. Regra do Reparo
    Quando é inevitável falhar, falhe ruidosamente e o
    mais cedo possível.

    View Slide

  78. Testes automatizados
    Com testes, temos a garantia que erros sejam
    capturados antes de chegar aos usuários de um
    programa.

    View Slide

  79. Mito sobre testes
    “A melhor forma de garantir uma
    funcionalidade é testar comandos
    no shell.”

    View Slide

  80. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    const rsjs = require('../bin/rsjs')
    const test = require('ava')
    test('should return the entry as log', t => {
    const result = shell.exec('rsjs 2016', {
    silent: true
    })
    t.is(result.output, '2016')
    })

    View Slide

  81. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    const rsjs = require('../bin/rsjs')
    const test = require('ava')
    test('should return the entry as log', t => {
    const result = shell.exec('rsjs 2016', {
    silent: true
    })
    t.is(result.output, '2016')
    })

    View Slide

  82. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    const rsjs = require('../bin/rsjs')
    const test = require('ava')
    test('should return the entry as log', t => {
    const result = shell.exec('rsjs 2016', {
    silent: true
    })
    t.is(result.output, '2016')
    })

    View Slide

  83. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    const rsjs = require('../bin/rsjs')
    const test = require('ava')
    test('should return the entry as log', t => {
    const result = shell.exec('rsjs 2016', {
    silent: true
    })
    t.is(result.output, '2016')
    })

    View Slide

  84. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── logger.test.js
    const rsjs = require('../bin/rsjs')
    const test = require('ava')
    test('should return the entry as log', t => {
    const result = shell.exec('rsjs 2016', {
    silent: true
    })
    t.is(result.output, '2016')
    })

    View Slide

  85. Command shell anti-pattern
    Escrever testes automatizados que dependam de
    comandos shell para comparar resultados é lento e
    difícil de manter.

    View Slide

  86. Teste o que é necessário
    Não faz sentido testarmos um executável, por
    exemplo, quando ele funciona apenas como um
    proxy.

    View Slide

  87. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── logger.test.js
    const logger = require('../src/logger.js')
    const test = require('ava')
    let output = ''
    const entry = 'RSJS'
    const write = process.stdout.write
    test.beforeEach(t => process.stdout.write = str =>
    output += str)
    test.afterEach(t => process.stdout.write = write)
    test('should return the entry as log', t => {
    logger(entry)
    t.is(output, entry)
    })

    View Slide

  88. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── logger.test.js
    const logger = require('../src/logger.js')
    const test = require('ava')
    let output = ''
    const entry = 'RSJS'
    const write = process.stdout.write
    test.beforeEach(t => process.stdout.write = str =>
    output += str)
    test.afterEach(t => process.stdout.write = write)
    test('should return the entry as log', t => {
    logger(entry)
    t.is(output, entry)
    })

    View Slide

  89. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── logger.test.js
    const logger = require('../src/logger.js')
    const test = require('ava')
    let output = ''
    const entry = 'RSJS'
    const write = process.stdout.write
    test.beforeEach(t => process.stdout.write = str =>
    output += str)
    test.afterEach(t => process.stdout.write = write)
    test('should return the entry as log', t => {
    logger(entry)
    t.is(output, entry)
    })

    View Slide

  90. ├── cli.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── logger.test.js
    const logger = require('../src/logger.js')
    const test = require('ava')
    let output = ''
    const entry = 'RSJS'
    const write = process.stdout.write
    test.beforeEach(t => process.stdout.write = str =>
    output += str)
    test.afterEach(t => process.stdout.write = write)
    test('should return the entry as log', t => {
    logger(entry)
    t.is(output, entry)
    })

    View Slide

  91. Regra da Clareza
    A clareza é melhor que a inteligência.

    View Slide

  92. Good patterns
    Output === API

    View Slide

  93. $ node rsjs

    View Slide

  94. $ node rsjs
    RSJS2016 23 04 POA
    RSJS2015 16 05 POA
    RSJS2014 17 05 POA
    RSJS2013 23 03 POA

    View Slide

  95. $ node rsjs | grep 05

    View Slide

  96. $ node rsjs | grep 05
    RSJS2015 16 05 POA
    RSJS2014 17 05 POA

    View Slide

  97. Good patterns
    Deve funcionar sem o
    usuário

    View Slide

  98. ex: git push --force

    View Slide

  99. Good patterns
    Disponibilize flags
    padrões ao usuário

    View Slide

  100. --no-color
    Existem terminais que não suportam cores. Seu output deve ser
    extremamente legível mesmo sem suporte.

    View Slide

  101. $ git diff

    View Slide

  102. $ git diff
    -test('test2', t => {
    - const result = shell.exec('rsjs 2016', { silent:
    true })
    -
    - t.is(result.output, '2016')
    + t.not('foo', entry)
    })

    View Slide

  103. $ git diff --no-color

    View Slide

  104. $ git diff --no-color
    -test('test2', t => {
    - const result = shell.exec('rsjs 2016', { silent:
    true })
    -
    - t.is(result.output, '2016')
    + t.not('foo', entry)
    })

    View Slide

  105. --help
    O problema com artefatos de ajuda (e documentações), é que
    são facilmente esquecidos de serem atualizados.

    View Slide

  106. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    'use strict'
    const help = `
    RSJS - O evento mais gaudério de JavaScript
    Usage
    $ rsjs
    Example
    $ RSJS '2016'`
    modules.exports = help

    View Slide

  107. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── src
    ├── logger.js
    └── test
    └── cli.test.js
    'use strict'
    const help = `
    RSJS - O evento mais gaudério de JavaScript
    Usage
    $ rsjs
    Example
    $ RSJS '2016'`
    modules.exports = help

    View Slide

  108. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const help = require('./help.js')
    const run = argvs => {
    if (argvs.help) {
    process.stdout.write(help)
    return
    }
    logger(argvs._)
    }
    exports.run = argvs => run(argvs)

    View Slide

  109. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const help = require('./help.js')
    const run = argvs => {
    if (argvs.help) {
    process.stdout.write(help)
    return
    }
    logger(argvs._)
    }
    exports.run = argvs => run(argvs)

    View Slide

  110. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const help = require('./help.js')
    const run = argvs => {
    if (argvs.help) {
    process.stdout.write(help)
    return
    }
    logger(argvs._)
    }
    exports.run = argvs => run(argvs)

    View Slide

  111. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── doc
    ├── help.txt
    └── src
    └── logger.js
    RSJS - O evento mais gaudério de JavaScript
    Usage
    $ rsjs
    Example
    $ rsjs '2016'

    View Slide

  112. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── doc
    ├── help.txt
    └── src
    └── logger.js
    'use strict'
    const help = require('fs').readFileSync('./doc/help.txt')
    exports.run = help

    View Slide

  113. --version
    O output da versão de seu programa deve sempre espelhar o
    que está no package.json.

    View Slide

  114. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const help = require('./help.js')
    const pkg = require('./package.json')
    const run = argvs => {
    if (argvs.help) {
    process.stdout.write(help)
    return
    }
    if (argvs.version) {
    process.stdout.write(`${pkg.name} v${pkg.version}`)
    return
    }
    logger(argvs._)
    }
    exports.run = argvs => run(argvs)

    View Slide

  115. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    └── src
    └── logger.js
    'use strict'
    const logger = require('./src/logger.js')
    const help = require('./help.js')
    const pkg = require('./package.json')
    const run = argvs => {
    if (argvs.help) {
    process.stdout.write(help)
    return
    }
    if (argvs.version) {
    process.stdout.write(`${pkg.name} v${pkg.version}`)
    return
    }
    logger(argvs._)
    }
    exports.run = argvs => run(argvs)

    View Slide

  116. --verbose
    Por padrão, nossos programas devem ser silenciosos, porém, é
    importante dar mais informações ao usuário quando ele
    precisar.

    View Slide

  117. Good patterns
    Ao invés de emitir logs,
    emita eventos

    View Slide

  118. EventEmitter
    Classe do Node.js que é exposta pelo módulo events. Através
    dela que são implementados listeners e emitters de eventos.

    View Slide

  119. Regra da Diversidade
    Desconfie de todas as alegações sobre a existência
    de um “modo correto” de fazer as coisas.

    View Slide

  120. Seja independente de SO
    Nossos programas podem funcionar, sem muito
    esforço, tanto no universo Unix como Windows.

    View Slide

  121. Good patterns
    Evite usar paths “hard-
    code”

    View Slide

  122. path
    Módulo do Node.js que auxilia na manipulação de caminhos de
    arquivos e diretórios.

    View Slide

  123. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── doc
    ├── help.txt
    └── src
    └── logger.js
    'use strict'
    const path = require('path').join
    const read = require('fs').readFileSync
    const help = read(path(__dirname, './doc/help.txt'))
    exports.run = help

    View Slide

  124. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── doc
    ├── help.txt
    └── src
    └── logger.js
    'use strict'
    const path = require('path').join
    const read = require('fs').readFileSync
    const help = read(path(__dirname, './doc/help.txt'))
    exports.run = help

    View Slide

  125. ├── cli.js
    ├── help.js
    ├── package.json
    ├── bin
    ├── rsjs
    ├── doc
    ├── help.txt
    └── src
    └── logger.js
    'use strict'
    const path = require('path').join
    const read = require('fs').readFileSync
    const help = read(path(__dirname, './doc/help.txt'))
    exports.run = help

    View Slide

  126. Regra da Extensibilidade
    Projete para o futuro, pois ele chegará antes do que
    você imagina.

    View Slide

  127. O futuro?

    View Slide

  128. View Slide

  129. obrigado!
    @fernahh
    fernahh.com.br

    View Slide