O que desenvolvedores sênior podem aprender com os iniciantes

Nos últimos anos, tive o luxo de trabalhar e ser mentor de alguns iniciantes. Embora eu obviamente tenha testemunhado várias histórias de programação, as coisas não são tão preto e branco como parecem.

Há um punhado de padrões e comportamentos que eu tenho visto consistentemente em todos os iniciantes.

 

E embora alguns desses comportamentos sejam equivocados e prejudiciais, muitos apresentam uma oportunidade de aprendizado para desenvolvedores seniores. Ao contrário da crença popular, há algumas lições que podem ser aprendidas com aqueles com menos experiência, pois têm uma perspectiva sem preconceitos (mente iniciante).

 

Inversão condicional

 

 

Um dos anti-padrões mais comuns que já vi com iniciantes é algo que gosto de chamar de "inversão condicional" (pode já existir com um nome melhor). A inversão condicional pode acontecer ao usar declarações de ramificação para controlar ou limitar o fluxo de código. Abaixo está um exemplo do código invertido:

 

function atleastOneOdd(potentialOdds) {

  if (potentialOdds !== undefined) {

    if (potentialOdds.length) {

      return potentialOdds.some((num) = num 1);

    }

  }

  return false; 

}

 

exagerado para o efeito (mas eu definitivamente já vi pior)

 

Se você acha que o código acima parece muito complexo para uma tarefa tão simples, você não está sozinho. A complexidade percebida é resultado do aninhamento, pois o aninhamento contribui fortemente para a "complexidade percebida" do código. Aqui está uma implementação funcionalmente equivalente, mas muito mais legível:

 

function atLeastOneOdd(potentialOdds) {

  if (potentialOdds === undefined) {

    return false;

  }

  if (!potentialOdds.length) {

    return false;

  }

  return potentialOdds.some((num) = num 1);

}

 

A lição:

 

Em geral, menos aninhamento torna o código mais fácil de ler e manter. Como em todas as regras, sempre há exceções, por isso certifique-se de entender o contexto antes de tomar uma decisão. Aqui está um ótimo tópico que cobre este cenário específico com mais profundidade:

 

Saber quando olhar

 

É importante lembrar que não é apenas que os iniciantes sabem "menos", é também que eles ainda não desenvolveram um instinto para o que eles devem esperar. Depois de um certo número de horas gastas de codificação (não literalmente), você desenvolve um sentido aguçado para a granularidade da lógica que você pode esperar estar disponível a partir de bibliotecas e pacotes padrão. Um grande exemplo disso é uma situação que surgiu com minha namorada. Minha namorada é estudante de ciência da computação, e recentemente eu estava dando a ela algum feedback sobre o código que ela escreveu para uma tarefa C++. Como parte da tarefa, ela precisava verificar se um determinado char de entrada era uma carta. Esta foi mais ou menos a implementação dela:

 

bool isLetter(const char someChar) {

  const char letters [] = {

    ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’,

    ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’

  };

 

  int i = 0;

  for (i = 0; i 26; ++i) {

    if (letters[i] === someChar) {

      return true;

    }

  }

  return false;

}

 

Eu não podia nem estar bravo. É uma implementação tão direta e lógica. Mas como um desenvolvedor experiente, o instinto diz que tem que haver uma solução para um problema tão comum. Na verdade, meu instinto imediato era tirar vantagem da natureza numérica dos c chars e simplesmente verificar se um char de entrada está dentro de uma faixa ascii a-z específica. Mas devido à minha "intuição de programação" eu suspeitava que ainda era muito trabalho para um problema tão comum. Eu disse a ela para o Google se há uma maneira de verificar se Char é uma carta. Acontece que existe,isalphaque eu provavelmente usei cem vezes, mas não me lembro (algo que acontece com programação suficiente - você não consegue lembrar de tudo).

 

A lição:

 

Desenvolver uma intuição para saber se já existe algo que resolva seu problema é fundamental para ter sucesso como desenvolvedor. Para o bem e para o mal, a única maneira de fortalecer essa intuição é escrever mais códigos em um conjunto diversificado de linguagens e paradigmas.

 

Encontrar a solução mais direta para um problema

 

Iniciantes são absolutamente incríveis em encontrar soluções diretas ou literais para um problema. Basta pegar o exemplo isLetter da seção anterior. Embora a solução da minha namorada definitivamente não seja a mais eficiente (memória ou computação sábia), é muito limpa e faz o trabalho. Embora a tendência de gravitar para a solução de força mais bruta possa ser cara, na maioria das vezes não importa. Eu não estou dizendo que você deve jogar desempenho ao vento e apenas confiar em soluções de força bruta, mas há uma lição a ser aprendida aqui. Para entender essa lição, é importante olhar para uma das principais diferenças entre iniciantes e especialistas: o que eles estão tentando alcançar. 

 

Um iniciante geralmente está programando para aprender a programar, enquanto um desenvolvedor experiente geralmente está programando como um meio de alcançar um fim (trabalho, projeto pessoal etc). Na faculdade, um professor de ciência da computação raramente dará uma tarefa que tenha um requisito de desempenho. Isso resulta em alunos aprendendo programação sem desempenho em mente. No início, isso pode parecer um enorme déficit na maneira como ensinamos a programação das pessoas, mas eu realmente acho que é uma das poucas coisas que a educação de nível superior faz corretamente. Preocupar-se com o desempenho é geralmente um sintoma de uma doença muito mortal conhecida como "otimização prematura". Pelas minhas observações, uma vez que um programador começa a se preocupar com o desempenho, é muito difícil para eles pararem. 

 

Isso não seria um problema se a otimização para o desempenho não leva tempo extra, mas leva. Combine isso com o fato de que o desempenho quase nunca importa (isso vem de um programador C/x86 de baixo nível), e é claro por que sempre otimizar para o desempenho pode ser problemático. Esse efeito é tão potente, que eu realmente vi iniciantes chegarem a uma solução de trabalho mais rápido do que um desenvolvedor muito capaz e experiente simplesmente porque o iniciante só se importava em fazê-lo funcionar e o desenvolvedor sênior assumiu que tinha que ter um bom desempenho. A ironia é que mesmo depois que o veterano terminou a solução "performante", você não poderia dizer a diferença entre o deles e os iniciantes.

 

A lição:

 

 

A solução ingênua direta é geralmente suficiente, também conhecido como Razorish de Occam

 

Estou assumindo que muitos leitores terão problemas com esta seção porque pode parecer que estou encorajando as pessoas a escrever códigos ruins. Essa não é absolutamente a intenção, e se alguma coisa, eu acho que mais tempo deve ser gasto tornando o código legível e mantenedor e menos tempo otimizando o desempenho. */

 

Tudo tem que ser uma classe

 

Um dos comportamentos mais frustrantes que vi consistentemente em iniciantes é a incapacidade de codificar fora do paradigma de classe. Eu culpo esta em todas as faculdades que só introduz alunos a Java em programas de ciência da computação. Eu sou geralmente agnóstico de linguagem, mas eu sempre achei que o requisito "tudo tem que ser uma classe" para ser um aspecto particularmente bobo de Java. Se alguém da Sun gostasse muito do OOP. O preço para essa obsessão é que 20 anos depois, Java ainda está tentando se remontar em uma linguagem que as pessoas realmente querem usar.

 

Para entender por que isso é um problema real e não apenas uma reclamação sobre Java, temos que pensar em como os iniciantes aprendem. Iniciantes tendem a internalizar conceitos na granularidade que são apresentados. Assim, quando eles usam linguagens como Java, que impõem o paradigma de classe, eles tendem a desenvolver a compreensão de que a unidade mínima de código é uma classe e a expectativa de que cada variável age como um objeto. O uso de outra linguagem orientada a objetos, como o C++, provará rapidamente que a unidade mínima de código não precisa ser uma classe — muitas vezes é exagero e complicado escrever uma classe inteira para lógica primitiva e apátrida. Não me faça mal, há toneladas de situações da vida real onde essas semânticas e garantias são desejáveis — na verdade, é a razão pela qual Java é uma das línguas mais onipresentes.

 

O problema é que essas semânticas pintam um quadro enganoso para programadores iniciantes. Quando você faz zoom no mundo Java, você notará que pouquíssimas linguagens de programação são tão orientadas para o objeto. É por isso que é tão perigoso aprender programação com apenas uma língua. Você não acaba aprendendo programação, você acaba aprendendo um idioma. 

 

Quando eu discuti essa crença com outros desenvolvedores, eles muitas vezes argumentam que essas desvantagens são realmente vantagens porque Java evita ter iniciantes atirando em si mesmos no pé. Embora isso possa ser uma grande razão para uma empresa da Fortune 100 que precisa contratar muitos programadores não confiáveis, não devemos introduzir programação para iniciantes como este.

 

A lição:

 

A capacidade de generalizar as coisas é uma das ferramentas mais importantes que você tem como desenvolvedor. Começar aprendendo uma única língua é completamente compreensível, mas é absolutamente crucial que você se ramifique rapidamente e diversifique sua compreensão. Fazendo isso irá transformá-lo de um linguagem muito boa Inserir linguagem aqui programador para um grande desenvolvedor.

 

Sintaxe e estrutura fortes

 

Iniciantes tendem a depender de sintaxe e estrutura consistentes muito mais do que desenvolvedores experientes. Isso realmente faz muito sentido uma vez que você pensa sobre isso, considerando que os seres humanos são orientados por padrões e a sintaxe de código e semântica é um dos exemplos mais explícitos de padrões. Para um desenvolvedor experiente, entender o que uma função faz (e como ela opera) geralmente é uma questão de ir à sua definição e ler o código-fonte. Pode parecer impossível imaginar não operar dessa forma, mas eu quase garanto que mesmo os melhores desenvolvedores não começaram assim. A correspondência de padrões é tão fundamental para a maneira como os humanos aprendem, que se estende muito além da programação. Por exemplo, o inglês é notoriamente uma das línguas mais difíceis de aprender. Quando os alunos de inglês são questionados sobre o que torna o aprendizado do inglês tão difícil em comparação com outros idiomas, eles geralmente apontam para a inconsistência e a falta de regras de idioma confiáveis.

 

/** FTR: Um dos melhores argumentos sobre Java para iniciantes é sua semântica forte e consistente */

 

Um exemplo prático de programação disso aconteceu quando recentemente eu estava ajudando um iniciante a escrever uma classe C++ que tinha um estado interno relativamente complexo. Com base nos objetivos/uso pretendido da classe, fazia sentido ter alguns dos métodos mutando diretamente o estado enquanto outros retornavam uma cópia. Depois de escrever alguns dos métodos juntos, deixei o iniciante para escrever o resto sozinho. Quando voltei mais tarde, meu aluno não tinha feito muito progresso e comunicou que eles estavam presos. Especificamente, eles ficaram confusos sobre quais métodos estavam mutando o estado e quais métodos não estavam. Esta é uma situação que um desenvolvedor experiente não encontra, pois eles simplesmente olharão para a lógica interna do método e determinarão se o estado está sendo mutado. Mas um iniciante ainda não é fluente o suficiente para analisar rapidamente essa mesma lógica (mesmo que eles a tenham escrito anteriormente), e em vez disso depende da sintaxe e estrutura do código. Depois de rever o código, percebi que a luta deles foi parcialmente minha culpa.

 

Quando implementamos os métodos iniciais juntos, certifiquei-me de que havia uma boa mistura de mutável e imutável. O que eu não tinha percebido é que esses métodos apresentavam um padrão enganoso para um iniciante. Especificamente, cada método mutável era uma função vazia, e cada método imutável tinha um tipo de retorno.

 

class MyType {

  void addElem(int elem);

  MyType createCopy();

  ...

};

Eu tinha ensinado ao meu aluno involuntariamente um padrão que obviamente não é verdade na prática. Assim, no momento em que eles precisavam implementar o bool mutable bool removeElem(int elem) ou a impressão de vazio imutávelElems() , as coisas desmoronaram. void printElems() Eu digo que a culpa foi minha porque no fim do dia, eu era preguiçoso. Como um desenvolvedor experiente, eu não dependia de sintaxe e estrutura tanto quanto a lógica real que eu estava realmente implementando. Isso é bobagem porque o código que escrevi poderia ser melhorado a ponto de deixar zero ambiguidade para iniciantes e remover o potencial de bugs a quase zero custo para mim. Como consegui isso? Através do uso da palavra-chave const, que me permite indicar explicitamente se um método tem o potencial de mutar o estado:

 

class MyType {

  void addElem(int elem);

  bool removeElem(int elem);

  void printElems() const;

  MyType createCopy() const;

};

 

A lição:

 

É muito fácil esquecer como outras pessoas podem interpretar o código que você escreve. Mantenha-se consistente e utilize recursos de linguagem que permitem escrever código digestível e explícito.

 

Conclusão

 

Muitos dos padrões iniciais que você abraça e adota como iniciante cairão no esquecimento à medida que você é introduzido a soluções mais eficientes e sustentáveis. Dito isto, espero que este artigo ilustrou que às vezes até mesmo os desenvolvedores mais experientes podem se beneficiar de um pouco de "desaprevenido". Embora os iniciantes possam não estar tão confortáveis com recursos avançados e idiossincrasias linguísticas, às vezes isso é um presente quando o trabalho só precisa ser feito.

 

 

O Avance Network é uma comunidade fácil de usar que fornece segurança de primeira e não requer muito conhecimento técnico. Com uma conta, você pode proteger sua comunicação e seus dispositivos. O Avance Network não mantém registros de seus dados; portanto, você pode ter certeza de que tudo o que sai do seu dispositivo chega ao outro lado sem inspeção.


Strong

5178 ブログ 投稿

コメント