Advento do Código 2019 e Como Aprendi a Amar a Familiaridade

Como tradição anual da minha, passei uma facada no desafio de programação do Advent of Code ao longo de dezembro. Sempre foi uma oportunidade para eu me testar para colocar à prova o que havia aprendido ao longo do ano.

Durante parte de 2019, em meu trabalho anterior, usamos um script do PowerShell para criar e implantar o programa em que estávamos trabalhando. Algumas de suas funcionalidades eram herdadas quando entrei, já que agora você pode executar vários projetos simultaneamente no Visual Studio diretamente. Uma funcionalidade que ele estava implantando em uma instância para a equipe de controle de qualidade avaliar e um problema é que ela tentaria escrever parcialmente sobre a instância e o erro já em execução. A solução improvisada quando ingressei foi apenas excluir manualmente os arquivos na pasta da instância de controle de qualidade (tentar várias vezes os arquivos ainda estavam em uso) e depois tentar novamente. Uma correção rápida e levemente hacky que eu sugeri e implementei foi apenas tentar novamente essa parte do script até que funcionasse com êxito, o que parecia simplificar bastante nosso trabalho.

Esse foi o primeiro gosto do PowerShell que eu já havia experimentado. Em retrospectiva, eu realmente não fiz muito; Eu apenas apresentei uma variável e um porquê para determinar se todos os arquivos foram publicados com êxito e fui embora. No entanto, eu sabia que realmente não sabia muito sobre o PowerShell e que precisaria aprender a usá-lo melhor no futuro.

Avanço rápido para dezembro; o dia 1 do advento do código terminou e eu estava pronto para tentar me testar de uma nova maneira, tentando concluir o maior número possível de desafios usando o PowerShell. O primeiro desafio parecia bastante simples; execute uma operação em cada número em uma lista e adicione todos eles. Eu sabia que podia fazer isso quase trivialmente em qualquer outro idioma, mas como o PowerShell era novo para mim, tive que começar do zero.

Primeiro, eu precisava configurar algum tipo de ambiente. No primeiro dia, eu realmente não sabia o que mais precisava, então minha base de código era simplesmente dois scripts; um para cada parte do desafio do primeiro dia.

Meu código era bastante simples, pois aprendi sobre o Get-Content, como executar uma função e como pelo menos representar o valor na tela, embora eu não entendesse completamente como simplesmente declarar $ TotalMass no final realmente afetava o conjunto estado do programa. Tudo que eu sabia era que estava pelo menos mergulhando no PowerShell e estava pronto para continuar aprendendo coisas novas.

Eu também queria introduzir testes de unidade em meu trabalho. Provavelmente foi um exagero no começo, mas acho que a decisão de começar a adicionar testes tão cedo foi provavelmente para melhor, pois me levou a criar soluções que visavam se adequar aos testes. Felizmente, muitos dos desafios tiveram exemplos que efetivamente eram testes. Nesse primeiro dia, o teste foi realizado apenas em alguns números isolados e não em uma lista completa deles, então tentei substituir argumentos no script para que você pudesse especificar um arquivo ou apenas um número único.

Digite Pester: possivelmente a ferramenta de teste mais simples e surpreendentemente poderosa que eu já testemunhei. Esse conjunto de testes baseado no PowerShell permite que você escreva casos de teste muito simples, simplesmente direcionando a saída de algum programa para um método especial que compara o resultado. Como funciona através da tubulação, você pode escrever efetivamente usando qualquer idioma ou ferramenta e, desde que seja emitido, basta comparar se ele corresponde ao resultado esperado. Configurei alguns e vinculei tudo ao novo recurso Actions do GitHub para integrar efetivamente o IC ao meu repositório. Novamente, provavelmente é um exagero, considerando que este é um repositório de uma pessoa que não será trabalhado após este mês, mas foi uma boa oportunidade para encontrar algumas ferramentas que eu possa usar no futuro.

No entanto, o dia 2 foi um pouco complicado, era um programa que tinha que interpretar esse bytecode (ou intcode como eles se referiam a ele, porque tecnicamente os valores eram representados como números inteiros). Para este, eu tive que lidar com loops e matrizes, ambos eram um pouco duvidosos. De repente, agora eu tinha que usar apenas a funcionalidade .NET regular sobre o código do PowerShell. Por exemplo, eu precisava ler a entrada como uma única sequência e dividi-la em todas as vírgulas, e a maneira mais simples de fazer isso era exatamente a mesma sintaxe que em C #.

Mas foi no dia 3 que comecei a ficar um pouco preocupado com a forma como abordava o código do PowerShell. Para esse desafio, você recebeu os caminhos de dois fios como uma lista de direções e distâncias e teve que determinar a interseção mais próxima. Minha abordagem imediata foi criar uma função que determinasse onde estavam todas as interseções e, em seguida, uma função que, dado um caminho, calcula quanto tempo ao longo desse caminho estava a interseção.

Foi nesse momento que percebi que o PowerShell era muito diferente do que eu pensava originalmente. Eu sabia que estaria reutilizando essa metodologia para encontrar as interseções dos dois caminhos, então sabia que poderia generalizá-la para uma função com um argumento no qual você passa o caminho. O que eu queria retornar era o número inteiro de etapas demorou para chegar a um determinado ponto. No entanto, minha função não estava retornando nada e partes posteriores do código entraram em pânico porque não podiam funcionar com um valor nulo. Voltei a essa função e adicionei algumas instruções de impressão para saber o que estava fazendo. Em vez disso, minha função agora retornava um booleano. Não é útil, mas foi um comportamento curioso. O que era estranho era que a função parecia calcular o valor corretamente, mas não podia devolvê-lo corretamente. A adição de instruções de impressão alterou o que a função retornou. Depois de uma longa pesquisa, descobri que o problema era que as funções não retornam tanto quanto são executadas. Isso explicava por que as declarações de impressão quebraram, mas não explicou por que meu valor não estava retornando. Ainda não estou 100% certo de por que não estava retornando o que eu queria, mas ficou muito frustrante continuar trabalhando com ele, principalmente porque eu queria acompanhar os desafios diários.

Sem adivinhar, decidi escrever uma solução em C #, que tem sido minha linguagem de goto há bastante tempo. A solução veio naturalmente para mim, pois era quase exatamente o que eu pensava antes. De fato, minha solução para a parte um foi quase totalmente feita com a funcionalidade LINQ simples; obtenha todas as coordenadas cobertas pelos dois caminhos, cruze-as e encontre o valor mínimo. Para finalizar, migrei os dias anteriores de trabalho (que eram muito simples, independentemente) e reimplementei o teste usando o Xunit.

Olhando introspectivamente, o que esse movimento diz sobre mim? Uma resposta ingênua seria que eu segui o caminho mais fácil e não queria mais me desafiar, embora eu pudesse definitivamente argumentar que alguns dos dias posteriores (particularmente o dia 18 com o labirinto das portas das chaves) eram muito difíceis, independentemente do uso um idioma que eu estava acostumado. Penso que uma maneira menos acusadora de encarar isso é que percebi que o desafio que estava enfrentando era mais semântico e específico e menos técnico e programático. Eu não estava tentando resolver o problema tanto quanto estava lutando com o conjunto de ferramentas que precisava para colocá-lo em ação. Se eu quisesse me limitar no último, teria mudado tudo para C, onde não posso confiar em ferramentas fáceis, como escalar dinamicamente matrizes ou hashsets.

E, definitivamente, senti que mudar para o C # foi uma grande mudança apenas por causa do escopo dos desafios posteriores. Esse intérprete intcode continuava se desenvolvendo a cada dois dias, e muitos desafios efetivamente dariam um programa com o qual você teria que trabalhar para encontrar uma solução. Os desafios que exigiam que você executasse vários ao mesmo tempo foram bastante interessantes, especialmente porque cada intérprete precisava lidar com entrada e saída. Eu implementei isso com threads e lambdas, o que significava convenientemente que eu tinha uma classe generalizada para esses problemas de intcode a partir do dia 9, e tudo que eu precisava fazer era manipular como a E / S funcionava. Isso também significava que eu poderia facilmente direcionar funções específicas para teste, se quisesse (embora eu tenha ficado surpreso que a maioria dos problemas não tenha tantos exemplos posteriormente).

Porém, isso não deve ser visto como uma cópia do PowerShell; definitivamente me deu um gosto decente sobre o que posso e o que não posso fazer com ele, e quando devo usá-lo. Fico feliz por ter uma classe dedicada para interpretar esse problema recorrente do intcode, em vez de precisar usá-lo manualmente a cada vez ou, de alguma forma, fazer referência a ele em algum local comum. Eu acho que seria muito bom usar o PowerShell para tarefas menores que não precisam ser dimensionadas, principalmente nos sistemas Windows em que você não pode instalar facilmente o Python ou algo assim. Pester também é muito legal e eu não me importaria de escrever testes para coisas estranhas em qualquer outro idioma.

Mas, teoricamente, você provavelmente poderia fazer o que quisesse no PowerShell. O que me irritou foi que eu estava efetivamente referenciando bibliotecas .NET diretamente, em vez de poder escrever coisas puras do PowerShell. Parecia mais natural simplesmente desistir e escrever puramente em um idioma, em vez de uma mistura de ambos nesse cenário. E, finalmente, nesse cenário, senti que era mais importante aprender sobre como resolver problemas desafiadores com novos algoritmos, em vez de colocar novas ferramentas na mistura também.