Dans les périls des écoles de Java, Joel discute de son expérience à Penn et de la difficulté des "défauts de segmentation". Il dit
[Les erreurs de segmentation sont difficiles jusqu'à ce que vous] "prenez une profonde respiration et essayez vraiment de forcer votre esprit à travailler à deux niveaux d'abstraction différents simultanément."
Étant donné une liste de causes courantes de défauts de segmentation, je ne comprends pas comment nous devons travailler à 2 niveaux d'abstraction.
Pour une raison quelconque, Joel considère que ces concepts sont au cœur de la capacité des programmeurs à résumer. Je ne veux pas trop en supposer. Alors, qu'est-ce qui est si difficile avec les pointeurs / récursivité? Des exemples seraient bien.
Réponses:
J'ai d'abord remarqué que les pointeurs et la récursivité étaient difficiles au collège. J'avais suivi quelques cours typiques de première année (l'un était C et assembleur, l'autre était en programme). Les deux cours ont commencé avec des centaines d'étudiants, dont beaucoup avaient des années d'expérience en programmation de niveau secondaire (généralement BASIC et Pascal, à l'époque). Mais dès que des pointeurs ont été introduits dans le cours C et que la récursion a été introduite dans le cours Scheme, un grand nombre d'étudiants - peut-être même une majorité - ont été complètement déconcertés. C'étaient des enfants qui avaient écrit BEAUCOUP de code auparavant et qui n'avaient eu aucun problème, mais lorsqu'ils frappaient des pointeurs et des récursions, ils heurtaient également un mur en termes de capacité cognitive.
Mon hypothèse est que les pointeurs et la récursivité sont les mêmes en ce sens qu'ils vous obligent à garder deux niveaux d'abstraction dans votre tête en même temps. Il y a quelque chose à propos des niveaux d'abstraction multiples qui nécessite un type d'aptitude mentale que certaines personnes n'auront jamais.
Je serais également parfaitement disposé à accepter qu'il est possible d'enseigner des pointeurs et / ou une récursivité à n'importe qui ... Je n'ai aucune preuve d'une manière ou d'une autre. Je sais qu'empiriquement, être capable de vraiment comprendre ces deux concepts est un très, très bon prédicteur de la capacité de programmation générale et que dans le cours normal de la formation de premier cycle en CS, ces deux concepts sont parmi les plus grands obstacles.
la source
La récursivité n'est pas seulement "une fonction qui s'appelle elle-même". Vous n'allez pas vraiment comprendre pourquoi la récursivité est difficile jusqu'à ce que vous vous retrouviez à dessiner des cadres de pile pour comprendre ce qui n'a pas fonctionné avec votre analyseur de descente récursif. Souvent, vous aurez des fonctions récurrentes (la fonction A appelle la fonction B, qui appelle la fonction C, qui peut appeler la fonction A). Il peut être très difficile de comprendre ce qui s'est mal passé lorsque vous êtes N stackframes au fond d'une série de fonctions mutuellement récursives.
En ce qui concerne les pointeurs, encore une fois, le concept de pointeurs est assez simple: une variable qui stocke une adresse mémoire. Mais encore une fois, lorsque quelque chose ne va pas avec votre structure de données compliquée de
void**
pointeurs qui pointent vers différents nœuds, vous verrez pourquoi cela peut devenir difficile lorsque vous avez du mal à comprendre pourquoi l'un de vos pointeurs pointe vers une adresse poubelle.la source
goto
.goto
.int a() { return b(); }
peut être récursif, mais cela dépend de la définition deb
. Donc, ce n'est pas aussi simple qu'il y paraît ...Java prend en charge les pointeurs (ils sont appelés références) et il prend en charge la récursivité. Donc, en surface, son argument semble inutile.
Ce dont il parle vraiment, c'est de la capacité de débogage. Un pointeur Java (err, référence) est garanti pour pointer vers un objet valide. Le pointeur AC ne l'est pas. Et l'astuce dans la programmation C, en supposant que vous n'utilisez pas d'outils comme valgrind , est de savoir exactement où vous avez foiré un pointeur (c'est rarement au point trouvé dans un stacktrace).
la source
Le problème avec les pointeurs et la récursivité n'est pas qu'ils sont nécessairement difficiles à comprendre, mais qu'ils sont mal enseignés, en particulier en ce qui concerne les langages comme C ou C ++ (principalement parce que les langages eux-mêmes sont mal enseignés). Chaque fois que j'entends (ou lis) quelqu'un dire "un tableau n'est qu'un pointeur", je meurs un peu à l'intérieur.
De même, chaque fois que quelqu'un utilise la fonction Fibonacci pour illustrer la récursivité, je veux crier. C'est un mauvais exemple car la version itérative n'est pas plus difficile à écrire et elle fonctionne au moins aussi bien ou mieux que la version récursive, et elle ne vous donne aucune idée réelle de la raison pour laquelle une solution récursive serait utile ou souhaitable. Le tri rapide, la traversée d'arbre, etc., sont de bien meilleurs exemples du pourquoi et du comment de la récursivité.
Devoir se moquer des pointeurs est un artefact de travailler dans un langage de programmation qui les expose. Des générations de programmeurs Fortran construisaient des listes et des arbres et des piles et des files d'attente sans avoir besoin d'un type de pointeur dédié (ou d'une allocation de mémoire dynamique), et je n'ai jamais entendu personne accuser Fortran d'être un langage de jouet.
la source
GOTO target
) . Je pense que nous avons dû créer nos propres piles d'exécution, cependant. Cela fait assez longtemps que je ne me souviens plus des détails.Il y a plusieurs difficultés avec les pointeurs:
C'est pourquoi un programmeur doit réfléchir plus à fond lorsqu'il utilise des pointeurs (je ne connais pas les deux niveaux d'abstraction ). Voici un exemple des erreurs typiques commises par un novice:
Notez que le code comme ci-dessus est parfaitement raisonnable dans les langages qui n'ont pas de concept de pointeurs mais plutôt de noms (références), d'objets et de valeurs, comme le font les langages de programmation fonctionnels et les langages avec garbage collection (Java, Python). .
La difficulté avec les fonctions récursives se produit lorsque des personnes sans formation mathématique suffisante (où la récursivité est courante et requiert des connaissances) essaient de les approcher en pensant que la fonction se comportera différemment selon le nombre de fois où elle a été appelée auparavant . Ce problème est aggravé parce que les fonctions récursives peuvent en effet être créées de manière à ce que vous ayez à penser de cette façon pour les comprendre.
Pensez à des fonctions récursives avec des pointeurs transmis, comme dans une implémentation procédurale d'un arbre rouge-noir dans lequel la structure de données est modifiée sur place; c'est quelque chose de plus difficile à penser qu'une contrepartie fonctionnelle .
Ce n'est pas mentionné dans la question, mais l'autre problème important avec lequel les novices ont des difficultés est la concurrence .
Comme d'autres l'ont mentionné, il existe un problème supplémentaire, non conceptuel, avec certaines constructions de langage de programmation: c'est que même si nous comprenons, des erreurs simples et honnêtes avec ces constructions peuvent être extrêmement difficiles à déboguer.
la source
malloc()
Les pointeurs et la récursivité sont deux bêtes distinctes et il y a différentes raisons qui qualifient chacune comme étant "difficile".
En général, les pointeurs nécessitent un modèle mental différent de l'affectation de variable pure. Lorsque j'ai une variable de pointeur, c'est juste cela: un pointeur vers un autre objet, les seules données qu'il contient sont l'adresse mémoire vers laquelle il pointe. Donc, par exemple, si j'ai un pointeur int32 et que je lui attribue une valeur directement, je ne change pas la valeur de l'int, je pointe vers une nouvelle adresse mémoire (il y a beaucoup d'astuces intéressantes que vous pouvez faire avec cela ). Encore plus intéressant est d'avoir un pointeur sur un pointeur (c'est ce qui se produit lorsque vous passez une variable Ref en tant que paramètre de fonction en C #, la fonction peut affecter un objet entièrement différent au paramètre et cette valeur sera toujours dans la portée lorsque la fonction sort.
La récursion prend un léger bond mental lors de la première apprentissage parce que vous définissez une fonction en termes d'elle-même. C'est un concept sauvage lorsque vous le rencontrez pour la première fois, mais une fois que vous avez saisi l'idée, cela devient une seconde nature.
Mais revenons au sujet en question. L'argument de Joel ne concerne pas les pointeurs ou la récursivité en eux-mêmes, mais plutôt le fait que les étudiants sont davantage éloignés du fonctionnement réel des ordinateurs. C'est la science en informatique. Il y a une différence nette entre apprendre à programmer et apprendre comment fonctionnent les programmes. Je ne pense pas que ce soit tellement une question de "je l'ai appris de cette façon, donc tout le monde devrait avoir à l'apprendre de cette façon", comme lui en faisant valoir que de nombreux programmes de CS deviennent des écoles de métiers glorifiées.
la source
Je donne à P. Brian un +1, car j'ai l'impression que c'est le cas: la récursivité est un concept si fondamental que celui qui a les moindres difficultés devrait mieux envisager de chercher un emploi chez mac donalds, mais alors, même il y a récursivité:
Certes, le manque de compréhension a aussi à voir avec nos écoles. Ici, on devrait introduire des nombres naturels comme Peano, Dedekind et Frege l'ont fait, donc nous n'aurions pas autant de difficultés plus tard.
la source
goto top
pour une raison IME.Je ne suis pas d'accord avec Joel que le problème est de penser à plusieurs niveaux d'abstraction en soi, je pense que c'est plus que les pointeurs et la récursivité sont deux bons exemples de problèmes qui nécessitent un changement dans le modèle mental que les gens ont de la façon dont les programmes fonctionnent.
Les pointeurs sont, je pense, le cas le plus simple à illustrer. La gestion des pointeurs nécessite un modèle mental d'exécution de programme qui tient compte de la façon dont les programmes fonctionnent réellement avec les adresses et les données de la mémoire. D'après mon expérience, il arrive souvent que les programmeurs n'y aient même pas pensé avant d'en savoir plus sur les pointeurs. Même s'ils le connaissent dans un sens abstrait, ils ne l'ont pas adopté dans leur modèle cognitif du fonctionnement d'un programme. Lorsque des pointeurs sont introduits, cela nécessite un changement fondamental dans leur façon de penser le fonctionnement du code.
La récursivité est problématique car il y a deux blocs conceptuels à la compréhension. Le premier est au niveau de la machine, et tout comme les pointeurs, il peut être surmonté en développant une bonne compréhension de la façon dont les programmes sont réellement stockés et exécutés. L'autre problème avec la récursivité est, je pense, que les gens ont une tendance naturelle à essayer de déconstruire un problème récursif en un problème non récursif, ce qui brouille la compréhension d'une fonction récursive en tant que gestalt. C'est soit un problème avec des personnes ayant une formation mathématique insuffisante, soit un modèle mental qui ne lie pas la théorie mathématique au développement de programmes.
Le fait est que je ne pense pas que les pointeurs et la récursivité soient les deux seuls domaines problématiques pour les personnes coincées dans un modèle mental insuffisant. Le parallélisme semble être un autre domaine dans lequel certaines personnes se retrouvent simplement coincées et ont du mal à adapter leur modèle mental pour en tenir compte, c'est juste que souvent les pointeurs de temps et la récursivité sont faciles à tester dans une interview.
la source
Le concept de données et de code auto-référentiels sous-tend respectivement la définition des pointeurs et de la récursivité. Malheureusement, une exposition généralisée aux langages de programmation impératifs a conduit les étudiants en informatique à croire qu'ils doivent comprendre l'implémentation via le comportement opérationnel de leurs runtimes quand ils doivent faire confiance à ce mystère pour l'aspect fonctionnel du langage. La somme de tous les nombres jusqu'à une centaine semble être une simple question de commencer par un et de l'ajouter au suivant dans la séquence et de le faire à l'envers à l'aide de fonctions auto-référentielles circulaires semble perverse et même dangereuse pour beaucoup non habitués à la sécurité de fonctions pures.
Le concept de données et de code auto-modifiables sous-tend respectivement la définition des objets (c'est-à-dire les données intelligentes) et des macros. Je les mentionne car ils sont encore plus difficiles à comprendre, en particulier lorsqu'une compréhension opérationnelle du runtime est attendue d'une combinaison des quatre concepts - par exemple, une macro générant un ensemble d'objets qui implémente un analyseur décent récursif à l'aide d'un arbre de pointeurs . Plutôt que de suivre pas à pas l'intégralité du fonctionnement de l'état du programme à travers chaque couche d'abstraction, les programmeurs doivent impérativement apprendre à faire en sorte que leurs variables ne soient affectées qu'une seule fois dans des fonctions pures et que les invocations répétées de la même fonction pure avec les mêmes arguments donnent toujours le même résultat (c'est-à-dire la transparence référentielle), même dans un langage qui prend également en charge les fonctions impures, comme Java. Courir en rond après l'exécution est une entreprise infructueuse. L'abstraction devrait simplifier.
la source
Très similaire à la réponse d'Anon.
Mis à part les difficultés cognitives pour les débutants, les pointeurs et la récursivité sont très puissants et peuvent être utilisés de manière cryptique.
L'inconvénient d'une grande puissance, c'est qu'ils vous donnent une grande puissance pour bousiller votre programme de manière subtile.
Le stockage d'une valeur fausse dans une variable normale est déjà assez mauvais, mais le stockage de quelque chose de faux dans un pointeur peut provoquer toutes sortes de choses catastrophiques retardées.
Et pire encore, ces effets peuvent changer à mesure que vous essayez de diagnostiquer / déboguer la cause du comportement bizarre du programme. Mais, si quelque chose est mal fait subtilement, il peut être difficile de comprendre ce qui se passe.
De même avec la récursivité. Cela peut être un moyen très puissant d'organiser des trucs délicats - en mettant le truc dans la structure de données cachée (pile).
la source