Lidando com dados inesperados em JavaScript
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코모undefined
ounull
,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:
투입
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-parser
atravé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:
page
e size
como 검색 문자열을 상상해 보세요.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 o
null
,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.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:
null
eundefined
com 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 ocatch
deexists
para 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,um
map
oufilter
está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 famoso
findById
.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!
Reference
이 문제에 관하여(Lidando com dados inesperados em JavaScript), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/khaosdoctor/lidando-com-dados-inesperados-em-javascript-1n2i텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)