Solucionando problemas em 5 etapas: Uma abordagem em programação.
Published: 2021-01-15 17:12:51

<img  width="300" src="https://dev-to-uploads.s3.amazonaws.com/i/renrej67wpavrkdj9s19.png"></img>
#Introdução
Chegou a hora de finalmente entender uma das frases mais famosas no mundo da programação, engenharia e em outras áreas: "Seja um solucionador de problemas". Mais do que isso, você entenderá o sentido prático disso, por meio de um fluxo de trabalho que pode te ajudar a compreender e resolver problemas de um jeito novo.

Para que isso aconteça, você precisa resistir ao impulso de sair escrevendo código assim que se depara com um desafio e passe a enxergar a solução como um conjunto de etapas que, só então, serão implementadas em uma linguagem de programação. Dessa forma, é possível fazer da sua solução mais clara e independente da ferramenta usada para implementa-lá.

Esse material é uma síntese do que aprendi em uma das seções do curso *Javascript Algorithms and Data Structures Masterclass*, do Colt Steele. Recomendo que você confira caso sinta a necessidade de desenvolver uma base nesse assunto, por isso vou deixar o link para acessar o curso, ao final desse post.

## Quem se importa?

Você deveria. Resolver problemas de diferentes níveis é algo extremamente recorrente no dia a dia de um dev, além de ser um componente chave em muitas entrevistas de emprego na área. Por isso, é importante ter ao menos uma noção de como começar a trabalhar, caso um problema novo surja de repente. Tendo em mente o segundo exemplo, o cenário desenvolvido nesse post tem como base uma entrevista fictícia, onde os entrevistadores pedirão a você para implementar algum tipo de solução e você deve mostrá-los a sua abordagem para a situação dada.

## Apresentando o cenário

Imagine que, em uma entrevista técnica para uma vaga em programação, é solicitado que você desenvolva uma solução que verifique a ocorrência de cada caractere em uma string qualquer.

Apesar de ser um desafio simples e você pode até já ter pensado em uma forma de implementar isso, resista ao impulso de disparar código no editor de texto, de imediato. Você verá que há muita coisa a ser discutida.

## Fluxo de trabalho

O fluxo apresentado no curso que mencionei sugere que abordemos cada novo problema em 5 passos: Entender o problema, Explorar exemplos, Decompor em etapas, Resolver/Simplificar o problema e, finalmente, Refatorar. Isso vai garantir que você (e os entrevistadores) visualizem a sua linha de pensamento e onde você pretende chegar, antes mesmo de escrever uma única linha de código.

## Passo 1: Entenda o problema

O primeiro passo é garantir que você realmente compreendeu o problema e possa esclarecer aspectos importantes sobre o que é preciso ser feito. Para fazer isso, Steele recomenda que você faça as seguintes perguntas:

- *Posso repetir o problema em minhas próprias palavras?*
- *Quais entradas devem ser introduzidas?*
- *Quais saídas são esperadas, dessa solução?*
- *As saídas podem ser deduzidas das entradas? Ou seja, tenho informações suficientes para gerar o resultado esperado?*
- *Como eu deveria rotular os dados presentes nessa solução?*

As três primeiras podem ser feitas aos entrevistadores, caso estejam abertos a consulta, e as duas últimas devem ser seus questionamentos.

Reformulando o problema, você declara que *"Devo construir uma função que analise uma string e devolva o número de vezes em que cada caractere apareceu"*. Confirmada a informação, seguimos adiante.

Digamos que, após tentar esclarecer o problema, você obteve as informações de que a entrada deve ser uma string simples e que a saída pode ser expressa por meio de um objeto cujas chaves são os caracteres e os valores, suas respectivas ocorrências.

## Passo 2: Explore exemplos

Agora que você tem uma noção clara do que deve ser inserido e o que deve ser retornado como resultado, você pode rabiscar, em seu editor (ou quadro, se for o caso), alguns exemplos práticos do que deve ser esperado. Nesse momento, é recomendado que você busque explorar:

- casos simples
- casos mais complexos
- casos com entradas vazias
- casos com entradas inválidas

Assim, você chega a um esboço como esse

```js
charCount("hello"); // { h: 1, e: 1, l: 2, o: 1 }
charCount("Hello World!"); // ???
charCount("My phone number is 23453"); // ???
charCount(); // ???
charCount(""); // ???
charCount(true); // ???
charCount(12345) // ???
```

Note que agora você tem alguns casos especiais em seu rascunho, levando aos questionamentos *"Devo considerar espaços, números e caracteres especiais? E quanto aos minúsculos e maiúsculos? O que fazer em casos de entradas inválidas?"*

Esclarecidas essas infomações, é definido, por exemplo, que apenas caracteres alfanuméricos devem ser considerados na contagem, todos devem ser analisados em minúsculo e entradas inválidas devem retornar um objeto vazio como resultado.

```js
charCount("hello"); // { h: 1, e: 1, l: 2, o: 1 }
charCount("Hello World!"); // { h: 1, e: 1, l: 3, o: 2, w: 1, d:1 } 
charCount("My phone number is 23453"); // { m: 2, p: 1, ..., 2: 1, 3: 2, ... }
charCount(); // {}
charCount(""); // {}
charCount(true); // {}
charCount(12345) // {}
```

## Passo 3: Decomponha em etapas

Definida a interface da sua solução, é preciso fazer um esboço das etapas necessárias para atingir os resultados esperados. Você pode fazer isso utilizando *Pseudocódigo* ou apenas comentando as etapas, de maneira clara.

```js
function charCount(str) {
// criar o objeto de resultado
// verificar entradas inválidas e, se for o caso, retornar objeto vazio
// remover caracteres indesejados e espaços
// iterar sobre a string modificada
  // se o caractere já estiver adicinado ao objeto, incrementar 1
  // se o caractere não estiver, criar chave e adicionar o valor 1
//retornar objeto de resultado
}
```

Percebe-se, agora, que o processo de resolução ficou extremamente claro e, mesmo que você não implemente a solução completamente, é possível saber com clareza aonde você pretendia chegar.

## Passo 4: Resolva/Simplifique

Chegou a hora de codar a solução desenvolvida e ver algumas dicas para nos prepararmos para alguns imprevistos que podem ocorrer.

```js
function charCount(str) {
  // criar o objeto de resultado
  const result = {};
  // verificar entradas inválidas e, se for o caso, retornar objeto vazio
  if (typeof str != 'string') {
    return result;
  }
  // remover caracteres indesejados e espaços
  const sanitizedString = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  // iterar sobre a string modificada
  for (let i = 0; i < sanitizedString.length; i++) {
    let char = sanitizedString[i];
    if (result[char] > 0) {
      // se o caractere já estiver adicinado ao objeto, incrementar 1
      result[char]++;
    } else {
      // se o caractere não estiver, criar chave e adicionar o valor 1
      result[char] = 1;
    }
  }
  //retornar objeto de resultado
  return result;
}
```
Temos aqui uma implementação que resolve o problema dado, mas nem sempre pode ser tão simples. Por exemplo, você pode acabar esquecendo a *expressão regular* que seleciona os caracteres que devem ser excluídos ou até mesmo o método que deixa strings em caixa baixa. Quando checar a resposta para esses pequenos problemas não for uma opção, você pode utilizar duas abordagens. A primeira consiste em identificar a dificuldade e *simplificar* a solução, eliminando os pontos que você não lembra como implementar e ignorando-os, temporariamente. Nesse caso, mantendo caracteres especiais e letras maiúsculas na string e prosseguindo com a solução. Em algumas situações você pode lembrar qual a "peça" que está faltando, enquanto faz outra coisa. O outro caminho é ser comunicativo a respeito a sua dificuldade, dizendo coisas como "não consigo lembrar qual método converte strings em minúsculas, nesse momento" ou qualquer outra dificuldade similar. Isso vai sinalizar que você sabe o que está fazendo e, quem sabe, fazer os entrevistadores darem pequenas dicas.

## Passso 5: Volte e Refatore

A solução resolveu o problema, isso quer dizer que chegou a hora de fechar o notebook e finalizar o desafio, certo?


Nada disso.


É extremamente tentador se contentar com a solução que simplismente funciona. Em casos de demandas muito urgentes, é a única opção, mas nem sempre é assim. Agora é o momento da autoavaliação e de sermos curiosos, persistentes e julgadores do nosso próprio trabalho. Remover variáveis desnecessárias, renomear elementos e considerar soluções alternativas são boas práticas. Novamente, Steele nos deixa com uma lista de perguntas que podemos fazer a nós mesmos ou a outras pessoas, quando nos deparamos com a primeira implementação funcional:

- *Você pode checar os resultados?*
- *Você desenvolveria isso de outra forma?*
- *Consegue entender a solução de primeira?*
- *É possível utilizar este resultado para solucionar outro problema?*
- *Você consegue melhorar a performance da sua solução?*
- *Consegue pensar em maneiras de refatorar isso?*
- *Como é que outras pessoas resolveram esse mesmo problema?*

Nem é preciso dizer o quanto essas perguntas podem mudar o aspecto do seu código. Com relação ao último item, é recomendado que você procure em sites como o Github ou outras plataformas, para entender as diferentes abordagens. Um detalhe importante é que nem sempre o que você procura estará implementado na linguagem que você está utilizando no momento, por isso é extremamente recomendado ver códigos feitos com outras ferramentas (desde que você se sinta confortável com isso).

Para finalizar, deixo algumas alternativas para solucionar o mesmo problema, que variam em estética, performance e até linha de raciocínio utilizada.

```js
function charCount(str) {
  const result = {};
  if (typeof str != 'string') {
    return result;
  }
  const sanitizedString = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  for (let char of sanitizedString) {
    result[char] = ++result[char] || 1;
  }
  return result;
}
```


```js
function charCount(str) {
  const result = {};
  for (let char of str) {
    char = char.toLowerCase();
    if (/[a-z0-9]/.test(char)) {
      result[char] = ++result[char] || 1;
    }
  }
  return result;
}
```

```js
function charCount(str) {
  const result = {};
  for (let char of str) {
    if (isAlphaNumeric(char)) {
      char = char.toLowerCase();
      result[char] = ++result[char] || 1;
    }
  }
  return result;
}

function isAlphaNumeric(char) {
  let code = char.charCodeAt(0);
  if (!(code > 47 && code < 58) && // numeric (0-9)
    !(code > 64 && code < 91) && // upper alpha (A-Z)
    !(code > 96 && code < 123)) { // lower alpha (a-z)
    return false
  }
  return true
}
```

## Conclusão

Encarar problemas de maneira metódica e planejar seu ataque, antes de iniciá-lo, pode fazer você enxergar novas possibilidades em suas soluções atuais, ou melhorar (e muito) as próximas. Lembre-se de que a programação é sobre fazer mais gastando menos recursos, isso significa parar para analisar cuidadosamente os caminhos disponíveis e, ainda assim, saber que existem opções que ainda não foram descobertas.

### Links úteis
[Javascript Algorithms and Data Structures Masterclass](https://www.udemy.com/course/js-algorithms-and-data-structures-masterclass/)