Lidando com dados inesperados em JavaScript

Um dos grandes problemas de linguagens dinamimente tipadaséque não temos como garantir que o fluxo de dados vai ser sempre correto,uma vez que não temos como“forçar”que Um par–metro ou uma variável,por exemplo,nãseja nulo.saída padrão que utilizamos quando temos estes casoséo simples teste:
function foo (mustExist) {
  if (!mustExist) throw new Error('Parameter cannot be null')
  return ...
}
Ogrande problema nisso é a polui ão do nosso có digo, pois temos que testaras variá veis em todos os lugares, e n ão há uma forma de garantirque todas as pessoas que estão desenvolvendo có digo vãO, de fato, realizar este em todos lugares onde uma variá vel ou par metron ão; possa ser nulo, muitas vezes nem sabemos que tal par metro pode vire. O코모undefinedounull,istoémuito comum quando temos times differentes para backend e frontend,o queéa grande maioria dos casos.
Visando melhorar um pouco este cenário,comecei a pesquisar sobre como podemos minimular os efeitos“inesperados”da melhor maneira e quais seriam as melhores estratégias para isto.Foi quando me deparei comeste artigo incrível do Eric Elliott.A ideia aquinãoécontradizer completamente o artigo dele,mas acrescentar algumas informaões interestsantes que acabei por descobrir com o tempo e experiência naárea de desenvolvimento JavaScript.
Antes de começar,queria dar uma pincelada por alguns pontos que são discutidos por este artigo e dar a minha opinião pessoal como desenvolvedor backend,pois o foco deste artigoémais o frontend.

최초의 문제


O dados pode ter várias fontes 문제.하나의 주요 원인은comcerteza,o가 Dousuário를 입력하는 것이다.Porém,existem outras origens de dados mal formados,além das que foram citadas no artigo:
  • 등록처
  • Funões que retronam dados nulos implicitamente
  • API externas
  • Vamos ter um tratamento diferente para cada tipo de caso que pegarmos e Vamos passar por todos eles mais a frente,lembrando que nadaéuma bala de prata.하나의 큰 목표는vem de erros humanos에서 기원한 것이다. isto porque muitas는linguagensest ão preparadas para lidarcom dados nulos ou não definidos,porém o fluxo de transforma ão destestes dados pode não estar preparado para lidarcom eles이다.

    투입


    Neste caso não temos muito como fugir,se o problemaéo input de usuário,temos que lidar com ele através do que chamamos de Hydration(ou hidrataão)do mesmo,ou seja,temos que pegar o input cru que o usuário nos envia,por examplo,em um payload de uma api,e transformar ele em algo que possamos trabalhar sem erros.
    백엔드가 없습니다.quando estamos utilizando um webserver como Express,podemos realizar todo tratamento de inputs de usuário vindos do frontend através de padr öes comoJSON Schemaou ferramentas comoJoi.
    Um exemplo do que podemos fazer utilizando uma rota com Express e AJV seria o seguinte:
    const Ajv = require('ajv')
    const Express = require('express')
    const bodyParser = require('body-parser')
    
    const app = Express()
    const ajv = new Ajv()
    
    app.use(bodyParser.json())
    
    app.get('/foo', (req, res) => {
      const schema = {
        type: 'object',
        properties: {
          name: { type: 'string' },
          password: { type: 'string' },
          email: { type: 'string', format: 'email' }
        },
        additionalProperties: false
        required: ['name', 'password', 'email']
      }
    
      const valid = ajv.validate(schema, req.body)
        if (!valid) return res.status(422).json(ajv.errors)
        // ...
    })
    
    app.listen(3000)
    
    Veja que estamos validando body de uma rota,obrigatoriemente o body éum objeto que vamos receber dobody-parseratravés deum payload,neste caso estamos passando mesmo através deum JSON Schema paraque ele seja validado,seuma dessas propriedades tiverum tipo diferente oum formato diferente(caso do 이메일 없음).

    Importante: Veja que estamos retornando um código HTTP 422, que significa Unprocessable Entity. Muitas pessoas tratam um erro de request, como um body ou query string errados como um erro 400 Bad Request, o que não é totalmente errado, porém o problema neste caso não foi com a request em si, mas com os dados que o usuário mandou nela. Então a melhor resposta que podemos dar para um usuário é 422, informando que a request está certa, porém não pode ser processada porque está fora do que esperamos


    Uma outra opço além do AJVéo uso de Uma biblioteca que criei em conconcento com o,que chamamos deExpresso,um concento de libiliotecas para facilitar e deixar mais rápido desenvolvimento de APIs que utilizam o Express.Umadessasferramentaséo@expresso/validatorquefazbasicamenteoquemostramosanteriormente,porémelepodeserpassadocomoum중간부품.

    Par – metros opcionais com valores 기본값


    Além do que validamos anteriormente,abrimos possibilidade de que um valor nulo possa passar para dentro de nossa aplicaão se ele não for enviado em um campo opcional.예를 들어que temos uma rota de pagina ão que recebe dois par – metros: pagee sizecomo 검색 문자열을 상상해 보세요.Mas eles não são obrigatórios e,se não recebidos,devem assumir um valor padrão.
    O 이상적인 tenhamos uma fun ã O em nosso 컨트롤러 que fa ça algo deste tipo:
    function searchSomething (filter, page = 1, size = 10) {
      // ...
    }
    

    Nota: Assim como o 422 que retornamos anteriormente, para consultas paginadas, é importante que retornemos o código correto, o 206 Partial Content. Sempre que tivermos uma request cuja quantidade de dados retornados seja somente uma parte de um todo, vamos retornar como 206, quando a última página for acessada pelo usuário e não houver mais dados além destes, podemos retornar 200 e, se o usuário tentar buscar uma página além do range total de páginas, retornamos um 204 No Content ou então (até melhor) um 416 Request Range Not Satisfiable.


    Isso resolveria no caso de recebermos os dois valores em branco, poré mé aique entramos em ponto bastante 쟁의 do JavaScript no geral.Os par–metros opcionais sóobtém seu valor default se,e somente se,ele for vazio,porém isto não functiona para onull,então se fizermos este teste:
    function foo (a = 10) {
      console.log(a)
    }
    
    foo(undefined) // 10
    foo(20) // 20
    foo(null) // null
    
    Portanto,não podemos Confir somente aos par–metros opcionais o tratamento de informaçes comonull.Então,para estes casos podemos fazer de duas formas:
  • 타타모스 디레타멘트 컨트롤러 없음
  • function searchSomething (filter, page = 1, size = 10) {
      if (!page) page = 1
      if (!size) size = 10
      // ...
    }
    
    O que nãOémuito bonito.
  • Tratamos na rota,com JSON 모드
  • Novatemente podemos recorrer ao AJV ou ao@expresso/validator para poder tratar estes dados para nós
    app.get('/foo', (req, res) => {
      const schema = {
        type: 'object',
        properties: {
          page: { type: 'number', default: 1 },
          size: { type: 'number', default: 10 },
        },
        additionalProperties: false
      }
    
      const valid = ajv.validate(schema, req.params)
        if (!valid) return res.status(422).json(ajv.errors)
        // ...
    })
    

    Lidando com Null e 정의되지 않음


    유럽연합,pessoalmente,não souum grande fãdessa dialéticaque o JavaScriptutiliza para mostrar que um valor est á em branco,por vá rios motivos,alé m de ser mais complado de abstrair estes concitos,temos o caso dos par – metros opcionais.Se vocêainda tem dúvidas sobre os conceitos,uma grande explicaão prática seria imagem a seguir:

    Uma vez que agora sabemos ao que cada definição se REFEREE,Uma grande adiçãao JavaScript em 2020 seráum CONCONCONTO de duas FUNCIONALIDDES.O 비어 있는 결합 연산자 e O 선택적 링크.Não vou entar em detalhes porquejá escrevi um artigo sobre isto,mas estas duas adiçes vão facilitar muito pois vamos poder focar nos dois conceitos:nulleundefinedcom um operador próprio,o??,ao invés de termos que utilizar as negaões booleanas como!obj,que sãpropensas a vários erros.

    Funões implícitamente nulas


    Esteéum problema bem mais complexo de se resolver porque eleéjustamente implícito.Algumas funões tratam dados assumindo que os mesmos sempre serão preenchidos,porém em alguns casos isto pode não ser verdade,vamos pegar um examplo clássico:
    function foo (num) {
      return 23*num
    }
    
    Senum 대표null, o resultado desta fun, o será0.O que pode nãO ser esperado.둥지 casos n ão temos muito o que fazer a n ãser testar o código.Podemos Realizator duas formas de teste,a primeira seria o simplesif:
    function foo (num) {
        if (!num) throw new Error('Error')
      return 23*num
    }
    
    A segunda forma seria utilizar um Monad chamado other,que foi explicado no artigo que citei,eéumaótima forma de tratar dados ambíguos,ou seja,que podem ser nulos ou não.Isto porque o JavaScript jápossui um nativo que suporta dois fluxos de Aão,A Promise.
    function exists (value) {
      return x != null ? Promise.resolve(value) : Promise.reject(`Invalid value: ${value}`)
    }
    
    async function foo (num) {
      return exists(num).then(v => 23 * v)
    }
    
    Desta forma podemos delegar ocatchdeexistspara a funão que chamou a funãofoo:
    function init (n) {
      foo(n)
        .then(console.log)
        .catch(console.error)
    }
    
    init(12) // 276
    init(null) // Invalid value: null
    

    Dados e API Externas 은행 등기소


    Esteéum caso bastante comum principalmente quando temos sistemas que foram desenvolvidos em cima de bancos de dados jápreviamente criados e populatos.예를 들어,um novo produto que utiliza a mesma base de um produto de sucesso front,integra çes de usuá riosentre sistemas diferentes e Por ai vai.
    O grande problema aquinãO fato de que O bancoédesconhecido,na verdade essaéa causa,como nãO sabemos O que foi feito no banco,nãtemos como atestar se O dado vai ou nãO vai vir nulo ou indefinitdo.Um outro casoéo de mádocumentaço,onde o banco de dados nãoédocumentdo de forma satisfatória e acabamos com o mesmo problema front.
    Nãhá muito que fugir neste caso, 유럽연합,pessoalmente,prefiro testarse o dado est á de uma forma que u Não poderei utilizar.Porém nãoébom fazer isso com todos os dados,uma vez que muitos objetos returnados podem ser simplesmente grandes demais.Entãoésempre uma boa prática verificar se o dado sob o qual vocèestárealizando alguma funão,por exemplo,ummapoufilterestáou não indefinitdo antes de realizar a operaão.

    Retronando erros 회사


    _maboa prática ter o que chamamos de Assertion Functions para bancos de dados e também para APIs externas,basicamente estas funões retronam o dado,se o mesmo existir,ou então estouram um erro quando dado não existe.O caso mais comum deste usoéquando temos uma API para,por exemplo,buscar algum tipo de dado por um ID,O famosofindById.
    async function findById (id) {
      if (!id) throw new InvalidIDError(id)
    
      const result = await entityRepository.findById(id)
      if (!result) throw new EntityNotFoundError(id)
      return result
    }
    

    Substitua Entity pelo nome da sua entidade, por exemplo, UserNotFoundError.


    Istoébom porque podemos,dentro de um mesmo controller,ter uma funão,por exemplo,para encontar um usuário por ID,e outra funão que utiliza se de um usuário para buscar outro dado,digamos,os perfis deste usuário em outra base.Quando chamarmos a funão de busca de perfis,vamos fazer uma asserão para garantir que o usuário realmente existe no banco,caso contrário a funão nem seráexecutada e poderemos buscar o erro diretamente na rota.
    async function findUser (id) {
      if (!id) throw new InvalidIDError(id)
    
      const result = await userRepository.findById(id)
      if (!result) throw new UserNotFoundError(id)
      return result
    }
    
    async function findUserProfiles (userId) {
      const user = await findUser(userId)
    
      const profile = await profileRepository.findById(user.profileId)
      if (!profile) throw new ProfileNotFoundError(user.profileId)
      return profile
    }
    
    Veja que não executaremos uma chamada no banco de dados se o usuário não existir,porque a primeira funão garante sua existia.Agora na rota podemos fazer algo do tipo:
    app.get('/users/{id}/profiles', handler)
    
    // --- //
    
    async function handler (req, res) {
      try {
        const userId = req.params.id
        const profile = await userService.getProfile(userId)
        return res.status(200).json(profile)
      } catch (e) {
        if (e instanceof UserNotFoundError || e instanceof ProfileNotFoundError) return res.status(404).json(e.message)
        if (e instanceof InvalidIDError) return res.status(400).json(e.message)
      }
    }
    
    Podemos saber qual tipo de erro returnar somente com o nome da instancia da classe de erro que temos.

    결론


    수출 형식은podermostratarosnossosdadosparaquetenhamosumfluxocontínuoeprevisíveldeinformaçes입니다.Vocêconhece alguma outra dica?!Deixa ela aqui nos comentários:D
    Não deixe de acompanhar mais do meu conteúdo nomeu bloge seinscreva na newsletterpara receber notícias semanais!

    좋은 웹페이지 즐겨찾기