4
$\begingroup$

I mentor an FRC (FIRST Robotics Competition) team, and they program in java.

The team members who work programming the robot are using an extension1 library which they themselves write.

The library that they write doesn't exactly follow good design concepts. for example, instead of having an interface for describing the functionality of a moving subsystem (a robotic forklift, for instance), they have a class that extends a subsystem, and it contains that functionality.

This is one example, and there are quite a few other examples. I say this to explain that they have quite a bit of code already written, and it would be difficult to make it more abstract and write it with a higher level of design.

So my question is how can I approach the subject, and encourage the students to want to learn and use higher levels of abstractions?

By "approach", I mean bring up the matter, without making it sound as though I think they have to do it, or that their code "won't ever be good unless they do it".

The students can learn it (i.e. they are able and capable of learning those concepts). This question isn't about how to teach it, but rather how to encourage the students to want to learn and apply those concepts

A clarification: I am not their teacher, I am a mentor. I do not teach them, but rather I help them learn (it's informal)

Proper design and abstraction makes code, especially libraries, easier to maintain and easier to use, and also prevents some types of bugs and errors.
That is one of the main points of design patterns, as well as a "side-effect" of following SOLID principles.


1Without going into too much details, I'll say that there is a supplied library for programming the robot, and the team is writing an extension of that library.

$\endgroup$
  • $\begingroup$ Provide the interface, and have them implement it (May be in breach of competition rules, may not work with the existing system). $\endgroup$ – ctrl-alt-delor Feb 7 '18 at 9:35
  • 1
    $\begingroup$ What value is it to them? How will it make the program better? (Don't tell be it is a rule; That is what good programs look like; Show me why it makes the program better.) $\endgroup$ – ctrl-alt-delor Feb 7 '18 at 9:37
4
$\begingroup$

The most fundamental shift in my programing came from my attitude about details.

When I was young I wanted to pull every detail out of everything I touched, pin in down in front of me in one source file and beat it into submission. I could stand in one place and know everything.

After decades of programming my attitude is: the less I know the better. Dealing with a detail? Feh, I let other things deal with that. I already have plenty of stuff to think about right here thank you. I don't need more clutter.

I have shifted from a procedural mind set to a more abstract one.

That sounds fancy but all it means is that no mater where I am in the code, I want to think about only one problem, in only one way, from only one perspective. Any detail that distracts from that is getting tucked away somewhere. I work very hard to make it that easy because I'm that lazy.

There are many ways to try to teach this, problems that invite students to drown themselves in details if they aren't careful. But one guiding principle has helped me see when I'm doing it right. It has many names but the one I'm most fond of is:

Tell. Don't ask.1,2,3,4,5

If I have to ask a question then I have to deal with the answer. Usually that's a detail. The only reason I ask is because I want to do something and I don't have enough information to do it. Why can't I just tell the thing I asked to just do my something? It knows the answer, so it knows what it needs already. I should be able to just tell it "do it now" and it should be able to go do it without making me think about it.

Adopt this attitude and working abstractly becomes very appealing. Without it, abstraction and polymorphism are very annoying because they're getting in the way of you knowing everything. Teach your students to love not knowing.

I don't know. I don't want to know.


How to motivate students to use this?

Don't.

Motivate them to explore it. Which is better is not a settled issue. What you should teach is what problems this style addresses. How to identify situations that create a greater need for it. How to identify situations where it's irrelevant.

I've been studying every programming principle and style I could find for decades now and not one of them is something I would advise someone to adopt without critically analyzing how and whether to apply it in any given situation.

What I do recommend is practicing with them. Until you've used one while solving a real problem all your doing is being a tourist. I've watched people end their career by waving a book around preaching principles without knowing how to solve a problem.

When interviewing prospects I'd rather hear:

"If you follow principle x it may help with this y issue."

than

"You should always follow principle x. It's good design. Professionals always follow good design."

Spare me from fanatics that follow rules without thinking.

Encourage them to learn every principle, pattern, and practice they can. Not because they should always use them, but because when they look at code that already uses them they should understand why the code looks the way it does.

The reality is true professionals spend most of their time wading through crap code trying to get what they need out of it while causing the smallest amount of fuss they can.

The really good ones will spot some tiny little improvement they might be able to get away with while they're here. These "boy scouts" leave the code a little better than they found it. Give me those over the crusaders that want a complete rewrite every time they look at a new code base.

Cultivate in them a skeptical "boy scout" attitude. Give them all the tools but teach them to use them sparingly, wisely, and carefully.

$\endgroup$
  • $\begingroup$ I see what you're saying. However the problem remains: I am asking how can I motivate the students to try and make their code more abstract. Don't get me wrong, I'll definitely use "Tell. Don't ask." . But that's not exactly the problem. $\endgroup$ – ItamarG3 Feb 7 '18 at 16:43
  • $\begingroup$ @ItamarG3 better? $\endgroup$ – candied_orange Feb 8 '18 at 6:05
  • $\begingroup$ Yes. The answer in itself is better, though I personally don't quite agree. But it definitely is better. Thanks :P. A point to note is, again, I am not their teacher, I am a mentor. I don't teach them, I guide them while they do independent work. $\endgroup$ – ItamarG3 Feb 8 '18 at 6:07
  • 2
    $\begingroup$ @ItamarG3 I've been on teams where I was just another coder, had no authority other than being known as a good guy to ask questions, and I still found myself "teaching". If you have a similar ability to influence them please take it just as seriously as a teacher would. This field is less than 100 years old. We're all still learning. Please help where you can. Your menties may just write code that drives my car some day. $\endgroup$ – candied_orange Feb 8 '18 at 6:16
2
$\begingroup$

Based on my past experience working on a FRC team, my main advice would be to be very cautious about pushing students to use more advanced abstractions, design patterns, and whatnot.

The issue is that if you spend a lot of up-front effort designing interfaces and abstraction layers, you can end up painting yourself into a corner, especially if you're new to programming and don't have a good sense of how to anticipate these sorts of things.

For example, take the forklift example you mentioned -- what happens if the students decide half-way through the design season that they don't want to use the forklift design after all, and want to create some sort of arm? Or what if they want to create some kind of kicker thing to try punt a ball of some sort?

Or even with the forklift example -- what if the team switches from using some sort of conveyor-belt design (where the user can control the height of the lift) to some sort of pneumatic based system, where the height ends up being basically binary (low or high)? How do you design an interface that can accommodate both (without stuffing the interface full of different methods that are inconsistently implemented across different subclasses)?

And for all of these, how do you map of these to the user interface, which again the team might want to wildly change over time?

None of these issues are insurmountable of course -- you can work around it by having a clever and flexible set of abstractions, or by having a more solid design phase (and pruning down deviations from the initial plan).

That said, no plan is perfect and the danger is that too-brittle and overly restrictive abstractions can potentially end up crippling the team down the line.

This means you should be careful when pushing for more abstraction -- the danger is that if the abstractions you propose end up hampering the team down the line, they might end up resenting the idea of them and take away entirely the wrong lesson: abstractions are bad, and are not worth the trouble.


So, for that reason, I recommend taking a more "organic" approach. Let students write code, and push them to write it in the simplest way possible. The code initially is probably going to be messy, but that's fine -- if your FRC team is anything like mine, there's probably a fair bit of prototyping in the first week or two (or even up to the last week...).

As you see redundancy, point it out, and push students to refactor it. Try and emphasize this sort of procedural refactoring first.

Next, encourage students to create objects to encapsulate behavior and state. Somebody introduces a new subsystem? Create an object for it. Somebody wants to play with a new UI? Another object. Don't worry about having these objects conform to any kind of interface/extend any object -- getting them to think about subsystems as distinct units/distinct objects would be a good step.

Finally, near the endgame, as the design as settled and people are iterating, start introducing the idea of interfaces. The overall design of your subsystems are probably fixed at this point, and the team is likely focused on iterating. In that case, you can probably now create an interface per each subsystem and create a new subclass per each distinct variation or something.

That way, if the team wants to trial-run one particular variation, it's easy to swap out the corresponding code.

You can maybe facilitate this whole process by having code reviews, perhaps on a weekly basis or something. This lets students still retain ownership of the code while still giving you a mechanism to propose changes or give advice.

This does mean, however, that your advice will (at least initially) be reactive, rather then proactive. However, I don't think that's necessarily a bad thing -- it's good for the students to have ownership of their code, make mistakes, learn from them, etc...

And if the team never really does get to the point where using interfaces and such makes sense, don't bother bringing it up. Realistically, the code they're writing is likely going to be simple, in the grand scheme of things. Don't over-complicate it.

Edit: A little more specifically, I'm fond of the "rule of three" (link 1, link 2).

I would try and drill in this principle into your students: when you see something repeated three or more times, that's a good point to think about refactoring and abstracting. Basically, my opinion is that the hard part is learning when it's appropriate to refactor or abstract. If you can teach them to identify when to abstract, it's comparatively easier to teach them how.

$\endgroup$
0
$\begingroup$

Perhaps it would help them to see the difference between their current code and some better code. Better code might also make their future work on this project easier. The trick is to get them to create the better code themselves without it seeming like they are throwing things away.

There are two skills that you can teach them that you can promise will be useful to them if they carry on in CS. Testing and Refactoring.

When code does what it should but is not as maintainable as it should be, or not as easy to extend, or just ugly, you want to Refactor it. Change its structure without changing its behavior. This is one of the core skills in Agile Software Development.

The problem with actually doing that, however, is to know that when you make a change to the code you haven't changed its actual behavior. So, you need to Test it. But you want those tests anyway so that you have confidence that when you extend (i.e. not refactoring) that you don't break things you wrote earlier.

So, assuming Java, use something like JUnit to create "unit tests" that test the behavior of the public methods of the code. Be sure to test both the main cases and the "edge" cases. These tests capture the semantics of the code, in fact. Ultimately the real-meaning of the code is what it does. Hopefully that matches the intended meaning. Tests can tell you this.

Then, tests in hand and all passing, you can change the code freely. As long as the tests continue to pass you haven't changed the behavior. There are lots of possible refactoring, such as the ones suggested in the above question, but others as well. Simplifying methods by factoring out private helpers. Creating private classes so that you can create helper objects so that each piece of code becomes simpler, etc.

You, as a teacher (or helper) can even make it a game giving prizes for the most effective refactoring, or the most successfully passing tests.


As a pedagogical technique that may be acceptable to the actual instructor in your situation is that you can pair with a student (or triple with a pair) and create some of the tests yourself. I say this may be acceptable, since you aren't providing functionality to their work, but only the framework in which they can extend it. Your example can give them the hang of how to go about it.


This same technique is used in situations in which you have to deal with some old code written by others (a "dusty deck") and don't understand it yourself. Write tests to capture what you think it is doing. If the tests pass then it really is doing that. Once you have understanding you can either refactor or extend, knowing that you didn't break anything.


The effect of this technique should result in the goals suggested in the answer of CandiedOrange here.


Ideally, tests are written along with the code, actually before the code. If the tests already exist and were written to drive the development itself, then the refactoring step is easier. The mantra for developing code from the start is No Code Without A Failing Test. This is called Test Driven Development. It is a fun skill in itself. Write a test that must fail since it has no code to back it up. Let the test capture the intended meaning. Then write just enough application code to make the test pass. Repeat until done.

$\endgroup$

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.