Nos perigos das escolas java, Joel discute sua experiência na Penn e a dificuldade de "falhas de segmentação". Ele diz
[Segfaults são difíceis até que você] "respire fundo e realmente tente forçar sua mente a trabalhar em dois níveis diferentes de abstração simultaneamente".
Dada uma lista de causas comuns para segfaults, não entendo como precisamos trabalhar em dois níveis de abstração.
Por alguma razão, Joel considera esses conceitos essenciais para a capacidade de abstração dos programadores. Não quero assumir demais. Então, o que é tão difícil sobre ponteiros / recursão? Exemplos seria bom.
Respostas:
Percebi pela primeira vez que os indicadores e a recursão eram difíceis na faculdade. Eu tinha participado de alguns cursos típicos do primeiro ano (um era C e Assembler, o outro era no Scheme). Ambos os cursos começaram com centenas de estudantes, muitos dos quais tinham anos de experiência em programação no ensino médio (normalmente BASIC e Pascal, naqueles dias). Porém, assim que os indicadores foram introduzidos no curso C e a recursão foi introduzida no esquema, um grande número de estudantes - talvez até a maioria - ficou completamente confuso. Eram crianças que escreveram MUITO código antes e não tiveram nenhum problema, mas quando atingiram os indicadores e a recursão, também atingiram uma parede em termos de capacidade cognitiva.
Minha hipótese é que indicadores e recursão são os mesmos, pois exigem que você mantenha dois níveis de abstração em sua cabeça ao mesmo tempo. Há algo sobre os múltiplos níveis de abstração que requer um tipo de aptidão mental que é muito possível que algumas pessoas nunca terão.
Eu também estaria perfeitamente disposto a aceitar que é possível ensinar sugestões e / ou recursões a alguém ... Eu não tenho nenhuma evidência de uma maneira ou de outra. Eu sei que, empiricamente, ser capaz de realmente entender esses dois conceitos é um bom preditor da capacidade geral de programação e que, no curso normal do treinamento de graduação em CS, esses dois conceitos permanecem como alguns dos maiores obstáculos.
fonte
A recursão não é apenas "uma função que se chama". Você realmente não vai entender por que a recursão é difícil até que você se encontre desenhando quadros de pilha para descobrir o que deu errado com seu analisador de descida recursiva. Freqüentemente, você terá funções recursivas mutuamente (a função A chama a função B, que chama a função C, que pode chamar a função A). Pode ser muito difícil descobrir o que deu errado quando você está N quadros de pilha em uma série de funções mutuamente recursivas.
Quanto aos ponteiros, novamente, o conceito de ponteiros é bastante simples: uma variável que armazena um endereço de memória. Porém, novamente, quando algo dá errado com sua complicada estrutura de dados de
void**
ponteiros que apontam para nós diferentes, você verá por que isso pode ficar complicado quando você tenta descobrir por que um de seus ponteiros está apontando para um endereço de lixo.fonte
goto
.goto
.int a() { return b(); }
pode ser recursivo, mas depende da definição deb
. Por isso não é tão simples como parece ...Java suporta ponteiros (eles são chamados de referências) e suporta recursão. Então, na superfície, seu argumento parece inútil.
O que ele está realmente falando é a capacidade de depurar. Um ponteiro Java (err, referência) é garantido para apontar para um objeto válido. Ponteiro AC não é. E o truque na programação C, supondo que você não use ferramentas como o valgrind , é descobrir exatamente onde você errou um ponteiro (raramente é no ponto encontrado em um rastreamento de pilha).
fonte
O problema com ponteiros e recursão não é necessariamente difícil de entender, mas é ensinado mal, principalmente no que diz respeito a idiomas como C ou C ++ (principalmente porque os próprios idiomas estão sendo mal ensinados). Toda vez que ouço (ou leio) alguém diz "uma matriz é apenas um ponteiro", morro um pouco por dentro.
Da mesma forma, toda vez que alguém usa a função Fibonacci para ilustrar a recursão, quero gritar. É um péssimo exemplo, porque a versão iterativa não é mais difícil de escrever e apresenta um desempenho tão bom ou melhor que o recursivo, e não fornece uma visão real sobre por que uma solução recursiva seria útil ou desejável. Quicksort, travessia de árvore, etc., são exemplos muito melhores do porquê e como da recursão.
Ter que mexer com ponteiros é um artefato de trabalhar em uma linguagem de programação que os expõe. Gerações de programadores do Fortran estavam construindo listas, árvores, pilhas e filas sem precisar de um tipo de ponteiro dedicado (ou alocação dinâmica de memória), e nunca ouvi alguém acusar o Fortran de ser uma linguagem de brinquedo.
fonte
GOTO target
) . Acho que tivemos que criar nossas próprias pilhas de tempo de execução. Isso foi há tempo suficiente para eu não me lembrar mais dos detalhes.Existem várias dificuldades com os ponteiros:
É por isso que um programador deve pensar mais profundamente ao usar ponteiros (não sei sobre os dois níveis de abstração ). Este é um exemplo dos erros típicos cometidos por um novato:
Observe que códigos como o acima são perfeitamente razoáveis em linguagens que não têm um conceito de ponteiros, mas um nome (referências), objetos e valores, como linguagens de programação funcionais e linguagens com coleta de lixo (Java, Python). .
A dificuldade com funções recursivas ocorre quando pessoas sem formação matemática suficiente (onde a recursividade é comum e requer conhecimento) tentam abordá-las pensando que a função se comportará de maneira diferente dependendo de quantas vezes ela foi chamada anteriormente . Esse problema é agravado porque as funções recursivas podem realmente ser criadas de maneiras pelas quais você precisa pensar dessa maneira para entendê-las.
Pense em funções recursivas com ponteiros sendo distribuídos, como em uma implementação processual de uma Árvore Vermelho-Preta na qual a estrutura de dados é modificada no local; é algo mais difícil de pensar do que uma contraparte funcional .
Não é mencionado na pergunta, mas a outra questão importante com a qual os novatos têm dificuldade é a simultaneidade .
Como outros já mencionaram, há um problema adicional, não conceitual, em algumas construções da linguagem de programação: é que, mesmo que entendamos, erros simples e honestos com essas construções podem ser extremamente difíceis de depurar.
fonte
malloc()
Não é mais provável do que qualquer outra função para fazer isso.)Ponteiros e recursão são dois animais separados e existem diferentes razões que qualificam cada um deles como "difícil".
Em geral, os ponteiros requerem um modelo mental diferente da atribuição pura de variáveis. Quando tenho uma variável de ponteiro, é apenas isso: um ponteiro para outro objeto, os únicos dados que ele contém são o endereço de memória para o qual apontam. Por exemplo, se eu tenho um ponteiro int32 e atribui um valor diretamente a ele, não estou alterando o valor do int, estou apontando para um novo endereço de memória (há muitos truques legais que você pode fazer com isso ) Ainda mais interessante é ter um ponteiro para um ponteiro (é o que acontece quando você passa uma variável Ref como uma função Parâmetro em C #, a função pode atribuir um objeto totalmente diferente ao Parâmetro e esse valor ainda estará no escopo quando a função saídas.
A recursão dá um pequeno salto mental na primeira aprendizagem, porque você define uma função em termos de si mesma. É um conceito selvagem quando você se depara com ele, mas depois de entender a idéia, ela se torna uma segunda natureza.
Mas voltando ao assunto em questão. O argumento de Joel não é sobre indicadores ou recursão por si só, mas o fato de os alunos estarem sendo mais afastados de como os computadores realmente funcionam. Esta é a ciência em ciência da computação. Há uma diferença distinta entre aprender a programar e aprender como os programas funcionam. Eu não acho que seja uma questão de "eu aprendi dessa maneira, para que todos tenham que aprender dessa maneira", como ele argumentando que muitos programas de CS estão se tornando escolas de comércio glorificadas.
fonte
Dou a P. Brian um +1, porque sinto que ele faz: recursão é um conceito tão fundamental que quem tem as menores dificuldades com ele deve considerar procurar um emprego na mac donalds, mas, mesmo assim, existe recursão:
Certamente, a falta de compreensão também tem a ver com nossas escolas. Aqui deve-se introduzir números naturais como Peano, Dedekind e Frege, para que não tenhamos tantas dificuldades mais tarde.
fonte
goto top
por algum motivo, IME.Eu discordo de Joel de que o problema é pensar em vários níveis de abstração per se, acho que os indicadores e a recursão são dois bons exemplos de problemas que exigem uma mudança no modelo mental que as pessoas têm de como os programas funcionam.
Acho que os ponteiros são o caso mais simples para ilustrar. Lidar com ponteiros requer um modelo mental de execução de programa que explique como os programas funcionam de fato com endereços e dados de memória. Minha experiência foi que muitas vezes os programadores nem pensam nisso antes de aprenderem sobre ponteiros. Mesmo que o conheçam em um sentido abstrato, não o adotaram em seu modelo cognitivo de como um programa funciona. Quando os ponteiros são introduzidos, é necessária uma mudança fundamental na maneira como eles pensam sobre como o código funciona.
A recursão é problemática porque existem dois blocos conceituais para a compreensão. O primeiro está no nível da máquina e, assim como os indicadores, pode ser superado através do desenvolvimento de um bom entendimento de como os programas são realmente armazenados e executados. O outro problema com a recursão é, penso eu, que as pessoas têm uma tendência natural de tentar desconstruir um problema recursivo em um não recursivo, o que atrapalha a compreensão de uma função recursiva como gestalt. Isso é um problema com pessoas com formação matemática insuficiente ou um modelo mental que não vincula a teoria matemática ao desenvolvimento de programas.
O fato é que não acho que indicadores e recursão sejam as únicas duas áreas problemáticas para pessoas presas em um modelo mental insuficiente. O paralelismo parece ser outra área em que algumas pessoas simplesmente ficam presas e têm dificuldade em adaptar seu modelo mental para dar conta, é apenas que muitas vezes é fácil testar indicadores e recursões em uma entrevista.
fonte
O conceito de dados e códigos auto-referenciais está subjacente à definição de ponteiros e recursão, respectivamente. Infelizmente, a ampla exposição a linguagens de programação imperativas levou os estudantes de ciência da computação a acreditar que precisam entender a implementação através do comportamento operacional de seus tempos de execução, quando devem confiar esse mistério no aspecto funcional da linguagem. Somar todos os números até cem parece uma simples questão de começar com um e adicioná-lo ao próximo na sequência e fazê-lo ao contrário com o auxílio de funções circulares de auto-referência parece perverso e até perigoso para muitos não acostumados à segurança de funções puras.
O conceito de dados e códigos auto-modificativos está subjacente à definição de objetos (dados inteligentes) e macros, respectivamente. Menciono isso porque eles são ainda mais difíceis de entender, especialmente quando é esperado um entendimento operacional do tempo de execução a partir de uma combinação dos quatro conceitos - por exemplo, uma macro que gera um conjunto de objetos que implementa um analisador decente recursivo com a ajuda de uma árvore de ponteiros . Em vez de rastrear a operação inteira do estado do programa passo a passo através de cada camada de abstração de uma só vez, os programadores imperativos precisam aprender a confiar que suas variáveis são atribuídas apenas uma vez dentro de funções puras e que invocações repetidas da mesma função pura com os mesmos argumentos sempre produzem o mesmo resultado (ou seja, transparência referencial), mesmo em uma linguagem que também suporta funções impuras, como Java. Correr em círculos após o tempo de execução é um esforço infrutífero. Abstração deve simplificar.
fonte
Muito parecido com a resposta de Anon.
Além das dificuldades cognitivas para iniciantes, os indicadores e a recursão são muito poderosos e podem ser usados de maneiras enigmáticas.
A desvantagem do grande poder é que eles oferecem grande poder para estragar seu programa de maneiras sutis.
Armazenar um valor falso em uma variável normal já é ruim o suficiente, mas armazenar algo falso em um ponteiro pode causar todos os tipos de coisas catastróficas atrasadas.
E pior, esses efeitos podem mudar à medida que você tenta diagnosticar / depurar qual é a causa do comportamento bizarro do programa.
Da mesma forma com recursão. Pode ser uma maneira muito poderosa de organizar coisas complicadas - colocando as coisas complicadas na estrutura de dados ocultos (pilha).
Mas, se algo for feito sutilmente errado, pode ser difícil descobrir o que está acontecendo.
fonte