11
$\begingroup$

In my beginner experience as a teacher, the thing that's strucking me most is a sensation of my students' inability to express their (valid!) thoughts in code. Let me deepen it a bit.

I inherited a last year class that was supposed to know C from their previous years. Anyway they struggled in manually tracing the execution of the code they see, being it mine or theirs. After some work I could test and evaluate them again and it now seems to me that they are actually quite good at reasoning about a sort of "abstract idea" of what they want their algorithms to do: they can use our natural language to say things like scan this, sum that, stop here or there, and they are pretty much correct. What they seem to have big difficulties in is saying the same thing using a formal language.

They moreover have this (to me, crazy) habit of writing huge blocks in main() without splitting anything, and even worse the only way they think about of producing output is writing to the world: they don't contemplate returning from a function. One (brilliant!) boy once asked me with big amazement after my seemingly strong assertion: "oh, so you mean that strlen() is a function?". Of course I may understand where this comes from, but I suspect it's not healthy for their learning.

It seems to me that a possible reason of this observed behaviour might lie in having taught them to build huge state machines without making them have a clue that they are building huge state machines: they can reproduce some they have already seen but are really lost when needing to make a new one. Even having them apply small modifications seems to me more like a blind trial from their part than some reasoning about the meanings. The building blocks of the language do not play well together in their minds or they are just unable to think about the state transitions in the code.

If I have to push myself a bit more I can say that I suspect all this is a reflection of the non composability of state machines, making their use in teaching to absolute novices not effective, whereas functions are known to be composable and would presumably lead to better results, in terms of being a means for mapping an individual's thoughts to formal language.

Is my analysis plausible? Have you experienced anything similar? Is there any available scientific literature on these phenomena? How would you test them to investigate deeper their difficulties and how would you try to show them a more effective route? Is there anything you would do to improve the methodology for next classes?

Should state machines be introduced somehow more explicitly, with better examples, showing how do they actually solve problems before throwing them to the poor students with a burden of syntax that they know nothing about? They even told me clearly that syntax is the only thing they focused all their energies on in their previous years.

What I am doing in younger classes is proposing functions first (as in functional paradigm), to solidify the concept of building a small composable block that has input and output, waiting a bit more in the future to introduce state transitions using the classic drawing turtle of fame with the hope that its visual feedback might make things tangible.

$\endgroup$
  • 4
    $\begingroup$ I think answering their questions (and ensuring they feel comfortable asking) should probably be much higher priority in your teaching. If they're still learning, they probably need a lot of examples, too. I'm guessing they're not as fluent with C as your impression seems to be. They probably need more practice with it. $\endgroup$ – Brōtsyorfuzthrāx Feb 22 '19 at 23:01
  • $\begingroup$ I find your phrase "mapping an individual's thoughts to formal language" mildly disturbing. Just sayin'... $\endgroup$ – Scott Rowe Feb 23 '19 at 21:40
  • 1
    $\begingroup$ state machines, as in finite automata? $\endgroup$ – kate Mar 7 '19 at 4:48
  • $\begingroup$ Scott: writing code is giving a shape of code to one's thoughts of operations and the like, for disturbing it may be. $\endgroup$ – user9137 Mar 7 '19 at 15:30
  • $\begingroup$ Kate: yes, state machines as in automata. $\endgroup$ – user9137 Mar 7 '19 at 18:13
7
$\begingroup$

There is a lot to unpack here, but first, YES, the teaching methodology can be improved, but that is always true, an unending quest.

Next, I'll note that C is a language with enough pitfalls that many people struggle with it at the detail level, finding it harder to think more globally. My preference, perhaps not open to you, is to start with a language at a higher level of abstraction generally and then, later in the curriculum, work both downward toward the machine and upward from where you start. An OO or pure functional language, if well taught, makes abstraction easier.

However, the biggest recommendation I'd make to address the problems you state is to introduce (at the beginning) unit testing and test driven development (TDD). Find a good testing environment for your language and insist on its use: No Code Without a Failing Test.

I normally start out a course with TDD, combined with giving assignments in an already logically decomposed way. Instead of a large program to write (large is relative, of course), I give them the "top down" decomposition initially that, in C they would be able to implement by writing a lot of functions.

Thus, at the beginning, I relieve them of having to face a blank screen with a large problem and nothing but 'main()' to work with. Later you can continue this, but making the pieces bigger, requiring some decomposition (helper functions) be developed. My mantra for a "too big function" is about five lines. My mantra for "too complex function" is nesting more than two levels. You can break these rules for especially boring code, but not for the interesting logic of a program, or it will be impossible to understand and maintain.

Note that breaking a problem up into parts is how Agile Development (Extreme Programming, Scrum, ...) actually work, with the Customer (in this case you) giving the Team (your students) manageable problems each of which contributes value.

Also, with TDD, you can provide some tests initially along with simple requirements. Teach them to stop programming when the tests pass. Or, write additional tests and make them pass. But the tests are small and simple, usually without internal logic, and the application code needed to make them pass should also be simple function and uses of functions.

$\endgroup$
6
$\begingroup$

I am surprised to hear of State Machines being introduced anywhere early in a curriculum. They are a solution to a particular kind of problem, and wouldn't make much sense outside of it. So, this is an indication to me that the curriculum is getting things backwards.

Humans need to learn things starting with very concrete situations that are graspable. Then they can start to use those concrete ideas as building blocks. Then they can start to reason about the blocks, with a bit of abstraction, then a bit more. You can't start with abstraction, the brain simply does not work that way.

Eventually, after years of nights of good sleep, enough ideas and familiarity will accumulate for motivated people to start to think on their own about things like programming. (And math. Music. Art. Philosophy... Etc.) But it doesn't come easy or fast. Sounds like a song I heard long ago.

If people developed programming in a particular way, maybe that is the best way to teach people about programming? If brains produced those ideas in a particular order, maybe that is how brains work and we should respect that and follow it? You can't push a rope uphill, but pulling on it works every time.

$\endgroup$
  • $\begingroup$ "You gotta pay your dues if you wanna sing the blues, but you know it don't come easy. You don't have to shout or leap about, you can even play them easy." $\endgroup$ – Scott Rowe Feb 15 '19 at 0:52
5
$\begingroup$

In addition to Buffy's excellent suggestions, I might suggest reframing your own thinking. You are thinking and speaking rather abstractly. Little phrases like "a means for mapping an individual's thoughts to formal language" and the question "Should state machines be introduced somehow more explicitly, with better examples" hint to me that you're really thinking about how to get them directly to your way of thinking.

The truth is that abstract ideas are not great starting points for great learning. Abstraction is a way of grouping seemingly disparate ideas to allow us to utilize them more readily or more flexibly. That's actually a key idea!

If we grok this notion of abstraction, then the way to achieve flexible, abstract thinking is to first have fluency with the smaller, disparate concrete ideas. Only after several of these ideas are firmly entrenched and comfortable can we then look at them as system and make successful links between the ideas. (BTW, I am using the word "successful" here as a marker for an abstraction that we can readily utilize.)

This fits in with our lived experience. Typically, we see some small systems, discover (or learn about) how they actually operate the same way, and then expand on the abstraction through new linked ideas over time. When the system works well, rich and complex ideas get boiled down to very simple underlying mechanisms, so that we ultimately can apply the abstraction readily in many different mental domains.

This distillation takes time, and it requires us to have at least a few well-understood pieces before it really kicks in.

Finite State Machines are wonderful, powerful concepts, but these students who haven't figured out when to utilize a function aren't there yet. Even if you show them the abstraction, they aren't mentally positioned to take any real advantage of it.

Therefore, your best bet is to hit the concrete ideas that you want hard. Make sure they have a few of the micro-skills, and provide direct instruction. Then, introduce abstractions slowly as systems are mastered.

One last thought for you: it's absolutely normal for kids to come out with only a middling understanding of the material in a prior course. As frustrating as that can be, we muddle by with half-understandings all the time in many domains. Don't think it beneath you to briefly run over material again, both to remind those who have forgotten, and to give a hand to those who never quite got it the first time. (Keep these kids in your heart when you make your reviews, and the kids who just needed a reminder will be better off for it as well.)

Good luck!

$\endgroup$
  • 2
    $\begingroup$ I always started to understand the previous math core course during the current one. $\endgroup$ – Scott Rowe Feb 20 '19 at 0:24
2
$\begingroup$

I will add to this, choice of language is also important, learning C (and forget C++) and how to program at the same time is to hard (see zone of proximal development).

Even Java is to hard:

  • Explaining public static void Main(string[] args)
  • Explaining that all programs need a main, but don't put anything in it, except new Program().run(); (main does not scale).
  • Don't use static it does not scale, but we have to use it in lesson 1.
  • public/private in lesson 1.
  • void in lesson 1, but don't use it, we will use functions.

May be with Unit Testing we can avoid main, but this is just the start. As an experienced programmer, I can do Java, but I find it exhausting, the amount of extra work that I have to do, just to express the most simple of ideas.

So what to choose

A language with contracts: adding pre/post conditions, will give you a clearer idea of what it is you are trying to do. If you don't know what you are trying to do, then how can you do it.

A language with a clean syntax, no extraneous words (public static void …). = means =, and assignment has its own symbol/keyword.

Is functional, or promotes functional.

A language with good defaults: data-attributes are private by default, methods are public by default.

The programming model, maps well to the theoretical model. E.g. Theoretical Object-oriented model maps to programming model (this is not true of most OO languages), or functional model maps to programming model.

Has a simple exception system (these are often overly complex). Not to hard to understand, but hard to work with.

$\endgroup$
  • 1
    $\begingroup$ COBOL was successful for a reason, yes? $\endgroup$ – Scott Rowe Feb 20 '19 at 0:26
  • $\begingroup$ @ScottRowe Yes, and the reason was, that is all we had. We have learnt a lot since then. There are much better languages, for both teaching and professional programmers. Your argument is like Microsoft is successful for a reason. In this case it is because they used to be cheaper than Unix. Now it is because we follow each other. (perfect market hypothesis, see jimabbondante.files.wordpress.com/2010/02/… and notmanywise.files.wordpress.com/2012/02/caterpillars2.jpg $\endgroup$ – ctrl-alt-delor Feb 20 '19 at 9:32
  • 1
    $\begingroup$ @scottrowe sorry if I suggested that things are getting better. My impression is that there was a flurry of progress in the 1960s, and then not much else. (an exception being N vs NP, sat solvers. I hear there has been much progress in these over the last 20 years, but these are not programming languages (or for beginners). $\endgroup$ – ctrl-alt-delor Feb 21 '19 at 9:23
  • 1
    $\begingroup$ @DanielR.Collins The one by Bret Victor is probably the best. $\endgroup$ – ctrl-alt-delor Dec 14 '20 at 19:29
  • 1
    $\begingroup$ @DanielR.Collins This link describes the video on Bret's web site. I have not seen anything about him from the past 5 years, so I am wondering what he is up to. $\endgroup$ – Scott Rowe Dec 16 '20 at 16:25
2
$\begingroup$

Here's a late answer, but I think has observations for the OP that others haven't highlighted sufficiently.

They moreover have this (to me, crazy) habit of writing huge blocks in main() without splitting anything, and even worse the only way they think about of producing output is writing to the world: they don't contemplate returning from a function. One (brilliant!) boy once asked me with big amazement after my seemingly strong assertion: "oh, so you mean that strlen() is a function?"

I teach in a community college curriculum that uses C++ for the initial two-semester sequence of programming. (Using Gaddis, Starting out with C++: from control structures through objects; i.e., control structures 1st semester, objects 2nd semester). The single biggest hump for our students is to accept modularizing their programs into separate functions, which occurs about halfway into the 1st semester.

I think I broadly feel why they really, really don't want to start doing that. They're legitimately able to get a working program by writing all the statements in main() up to the point. The overhead of learning to add prototype, header, code, call (which of those requires the type? the identifier? both?), parameters, arguments, pass-by-value vs. pass-by-reference, return value, scoping rules, what counts as a good name, etc. is not insignificant to them. If they can just get the immediate assignment working without grappling with that new gauntlet of concepts, then they'd generally prefer to do that. Plus the advantage is really not evident at that point: they're not working in a large team-environment where the division of labor really pays off. They're not hitting any radical increase in complexity where it's infeasible to cram a few more lines into main(). Even the textbook's labs and examples are not super hygienic about this.

So I am aware that I really need to hit this very hard all through the last 1.5 terms of our curriculum. I make modularizing programs an explicit requirement for all programs from that point forward, and reiterate in the directions for all assignments and tests. A hefty point penalty is deducted otherwise (i.e., the entire 20% component for "Readability"). I uniquely spend two days reviewing the concept and justification. I uniquely (in any course I teach) have a slide in shouty 120-point font laying out the demand. The book also seems to know this is the major hurdle, as it has a double-length series of labs for that particular chapter.

In brief: Modularizing the code seems like the single biggest hurdle, and the curriculum really must dedicate significant time to getting students on board with it.

Now, in the OP's case they're really in a major dilemma if they're receiving students at the end of a curriculum and this hasn't happened to date. Teaching the early courses in a context of finite state machines, global variables, and no modularity, is perilously close to causing irreparable brain injury in this regard. It's not entirely clear what the OP's context, requirements, or flexibilities are, but solutions probably need to pick from among the following:

  1. If the OP has any power over the institutional curriculum, change it, so that modularity for future changes and expansions is taught and tested at an early stage.

  2. If the OP does not have any power over the rest of the curriculum, but has significant flexibility in their own course design, then they likely should spend 2+ weeks at the start of the course explicitly teaching students this methodology. It's actually not bad to set that as the major theme of the course on Day 1 in a big banner to get their attention (and keep touching back to that all semester long).

  3. If the OP hasn't any power over the program's overall curriculum, and also lacks any flexibility for what gets taught in their own course (some kind of mandated higher-level curriculum) then they need to bite the bullet and accept that most of the students are simply not going to be able to interface with higher-level concepts, and fail them. Maybe a one-day "review" at the start of the course, and reference to materials on the subject, is the best you can do.

  4. If you have no power over the program sequence, and no flexibility in the individual course design, and are also not allowed to fail most of the students, then what can you do? Resign oneself to giving out fraudulent grades or leave the position, I guess.

There are times when I kind of wish the modularity issue was more up-front, but in my opinion it's even less comprehensible at a point when no task takes the 5-7 statements or so that makes a cognizable function. That is: if initially every function is itself just a single statement then there's no way you can convince students that's not a big waste of time and effort (because it is). So I do think you need to establish data declarations, assignment, conditionals, and loops before you've got enough mechanics that you can legitimately abstract a chunk of functionality into a modular and reusable function.

An anti-example of the latter: there's an open-source work by Busbee, Programming Fundamentals -- A Modular Structured Approach Using C++ that attempts to present modular functions first and foremost. Ch. 1 introduces a function which is a wrapper around the Windows system("pause") statement. But in the need to avoid all the other scoping details at this point, he falls into a trap of using global variables for all the data-passing, and gets stuck with it. I don't think there's any example in the entire text of ever using parameters, local variables, or return values (which is of course absolutely deadly to the cause).

In summary: Somewhere in the curriculum you've got to dig in and spend time clarifying this most-difficult-and-important part of the subject matter (modularizing code into functions). You're not going to get it happening by just saying it once and wishing it to be. You need some initial work developing the basic toolkit in the main function, but then need to transition to modular designs, and the longer this gets delayed the more bad habits will need unlearning later on.

$\endgroup$
  • $\begingroup$ When I was self-teaching in 9th grade on a PDP-11 terminal, I wrote simple programs in BASIC (or a script language... I forget after several decades) to create shapes on the terminal screen using asterisks and spaces. This requires nested loops, and pretty quickly rewards creating functions. So, a "motivating example" will teach the need for proper design. It is simply too hard to copy-paste the code in multiple places, change it, fix bugs and so on. Then functions seem like "a pretty neat idea." Simplifying the code becomes an organizing impulse. Choose projects that build. $\endgroup$ – Scott Rowe Dec 16 '20 at 16:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.