The one bad habit you learn from coding in school is you take a project and then throw it away. So speed does count when it comes to finishing an assignment so you can goof off, go party, etc. And then you never see it again, so who cares if the code sucks as long as it produces the outcome you want.
What you don't learn in school is how to write maintainable code. This is a much different style of writing code, and much more valuable once you go out in the "Real World". I'm not sure if there are CS programs out that start with a small project and then force the students to make massive changes to that project throughout the course of the year, but to me there is immense value to that. Even better would be if they had CS101, CS201, CS301 and the students taking CS201 would be forced to dig up their project from CS101 and work on that, CS301 would work on the code from CS201, etc. It's eye-opening how much you forget about your code after a few weeks, so it would drive home the notion of commenting, writing easily readable code, etc.
> I'm not sure if there are CS programs out that start with a small project and then force the students to make massive changes to that project throughout the course of the year
I studied Software Engineering at Swinburne University in Melbourne Australia, and we did exactly that.
One subject was "Software Maintenance Project" where we took an existing code-base we didn't write (a front-end for laTex for our year, the CVS code base for the next year students) and continually made changes to it. Halfway through the course we had to deal with changing requirements. We were graded on the quality of our code and how well it fit the existing style etc., not how fast we did it.
We also did a full-year final year project with 16 of us where we created a BIG code base from scratch. We decided to use an iterative approach (our choice) and so over the year we ran 6 iterations continually building on what we'd already done.
We also did awesome courses like "Personal Software Process" to teach us discipline in how we personally approach writing code.
EDIT: Sometimes I post to HN "hire/resume" threads about my hire-ability and how I personally think I'm "better" than the average person who studied CS because of things like the above. Normally everyone disagrees and says as long as I can code, it doesn't matter that I studied Software Engineering and am a certified Engineer. It's for reasons like the above I still personally feel I'm stronger than the average CS grad.
FYI, you are stronger than the average grad. People get pretty defensive when it comes to skills; nobody wants to believe they're below average if they're frequenting this site.
My one piece of advice: keep studying and practicing making beautiful software, in and out. Be careful who you take technical advice from; the industry right now is flush with cash, and thus there is a huge incentive to talk more than produce. Projects are often killed as quickly as they're started, causing people not to practice program design.
Even more challenging would be to maintain somebody else's code. Say, at the start of an assignment, each student is randomly assigned a code base from the previous project (we could restrict this set to only include code bases that met the grade). New requirements that weren't a part of the previous project are introduced every time this happens.
If the students are being graded on a curve so they're competing against one another, this would be downright diabolical.
I'm embarrassed to say that I went 15 years before I really learned to work with "other people's code". That's partly because I came to programming as a math/industrial engineering student, where I was really writing code to solve problems and get answers rather than a sustainable, usable product. Even once I started writing code for users, it was generally pretty experimental and research-y, so I usually got a green field project.
In the past few years, I've worked repeatedly with existing code bases to extend or modify functionality (or just fix problems), and it has been an amazing learning experience. Not all of the code has been good, much of it has been questionable. But the process of understanding someone else's code, figuring out how to change it, balancing the harm of forking vs the benefit of improving, dealing with pull requests and merges… there are a number of skills you will only learn from years in this kind of environment.
One huge challenge here is when you don't work in an organization that understands software. People aren't knowledgeable about software are often impressed with green field programmers "jim wrote the whole thing". Truth is, understanding a different code base and getting to the point where you can improve it and contribute back to it is (at least in my experience) far more challenging than just writing something from scratch. It seems that most people who write software are already well aware of this, but it can be a hard sell outside (in fact, you may even find that people seriously question your progress - a green field app there's something nifty and new to demo even day, with existing code bases, there's a lot of confusion and investigation - and it can be very dispiriting to spend days stubbornly trying to understand and diagnose a feature, finally solve it, and realize that the team is disappointed with your progress).
However, the good software teams appear to be well aware of the challenges, and it's an essential learning experience.
I find that a lot of what is taught as the "right way" to make maintainable code isn't actually that effective in practice. Particularly when I need to read someone else's code.
For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.
Instead, I'd propose first writing straight line code, and then abstracting out the common functionality into separate functions as necessary. Casey Muratori (the guy behind Handmade Hero) calls this compression oriented programming [1].
> For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.
It does? Maybe you mean something more extreme when you say "as abstracted as possible" but for /reading code/, I generally find it easier to get the high-level picture and avoid getting bogged down in minute from code that looks like this:
function sendReport(params) {
var dataSource = findDataSource(params);
var data = dataSource.generateData();
var report = reportBuilder.build(params);
sendReportEmail(report, param.recipients, this.template);
}
... versus a flatter one-page implementation with 15 lines of data loading, 20 lines of report, 5 lines of email templating and 15 lines of communicating with the mail server... all sprinkled with variously scoped try..catch blocks. It also helps me focus in on the part of the task I'm really interested in without trying to load the entire system in my head.
For /writing code/, I do agree the reverse is often best: start by doing the simplest thing that works, and then refactor it into something cleaner.
I think that if functionality is repeated it makes sense to abstract it into a function. However, I've recently been reading through a codebase in which every function is about 10 lines long, and there are numerous functions that are only used once. This makes it hard for me to follow the code since I frequently need to chase down the 10 line function that is only called once to see what it is doing and then return to wherever I was in the code.
I don't think I agree with that suggestion. That's like saying a table of contents is not useful if don't read a book multiple times. Certainly the DRY principle provides a good prompt of when to abstract code but it's not the only reason to do it.
Usually I am only interested in specific parts at any given time, either because I only have a particular task to accomplish (e.g. update the email template) or because trying to understand everyone at once would overload my little monkey brain.
If I'm trying to just update the email template then I don't care about the part the loads the data and I'd be wasting my time reading it. Sometimes you can't even tell if an chunk of inline code is relevant without spending a decent bit of energy figuring out what it's doing. Having everything in a single function also makes it easier for lines of unrelated code to become entangled together and thus harder to understand.
Well-structured code gives me a choice; I dive in and see the details if I'm interested, or I can leave it for another day and focus my attention elsewhere.
Certainly though, good tools like IDE are vitally helpful in reducing the friction in peeling back the abstractions when necessary. The nature of some languages though means that the level of tool support available can vary widely, which probably has an effect on different prefered coding styles. In Java, for instance, it's pretty easy to find all the pieces of code that call a particular method, while in Python, it's often not really possible without running the code.
With good naming and comments this shouldn't really be an issue. At my most recent software internship I got to see very abstracted code with many short functions, and realized quickly how much more clear to read and maintainable it was.
Yeah, there's ways to screw up every method of coding, but I find favoring more and shorter functions tends to better outcomes. Obviously you could reductio ad absurdum any code into something like:
function do_task() {
do_step1();
do_step2();
do_step3();
}
function do_step {
step1();
}
function do_step2 {
step2();
}
function do_step3 {
step3();
}
But I've rarely seen that be the problem. It's been far, far more common to see giant functions that are much more understandable when broken up.
I agree somewhat, but context switching has non-zero overhead since it requires that I keep in my short term memory what the previous code was doing as well.
Also, I probably should have been clearer above. Sometimes a short function is the right approach, particularly for something that is going to be done over and over again, but the overall goal is clarity, not short functions.
Anyways, it's not worth getting too hung up on I don't think, it seems my opinion is the minority one.
> For example, the conventional wisdom that functions should be less than a page of text and that they should do one thing and as abstracted as possible actually makes it quite hard to understand what a program does when you aren't familiar with it.
This hasn't been my experience at all. Functions as small as 100 lines can absolutely destroy my comprehension to the point where I need to refactor it into smaller functions to be able to understand it. I'm dealing with one such function (at 200 lines) right now at work.
Well, let me rephrase. I can see what it's doing easily enough. I just have no idea what it's supposed to be doing, on a chunk by chunk basis, because it's all hand rolled search loops and array manipulation.
I also know the final result is incorrect.
I'm sure I'll slap my forehead and wonder why I couldn't see the problem before after I'm done refactoring it.
I, however, do it differently. I start from a high-level of abstraction, and work my way down. At the top level I might have a function called "ImportData". I write that call down, then move to the next line where I write "RunDataAnalysis". At that point, if I decide to move down and implement the second function, I forget about what ImportData does, how it does it, or whether or not I've implemented it.
All I know is that the data structures are populated with the "Imported" data, and proceed to code along with that assumption. Obviously if I test, it won't work, but that's for unit-tests. You do unit-tests, right? Can't do that meaningfully with giant functions, but I digress.
To me, functions are a form of "interface". It delineates responsibility and expectations between different bits of code. I'm not a chaotic and/or artistic programmer and I'm definitely not a genius wunder-programmer that can hold the entire functioning of the program I'm writing in my head at any time. What I can do is systematically break-down a problem into component pieces, negotiate their interaction, and make it solve a problem.
I had a couple classes like that, usually it was more at the semester boundary. E.g. my intro to compilers classes involved writing a compiler for a c-subset. The advanced compilers class built on that compiler by adding optimizations and such. My intro to systems class had a project that was to write a shell. My operating systems class had a project to add job management and pipes to the shell from the systems class. I really liked those projects you felt like you had something pretty substantial when you were done.
A really great addition to any CS curriculum would be a long-running projects course that runs the full four years so that you get experience in managing architecture, growth, and maintenance, instead of just building. Lots of students do that by attaching themselves to open source projects or working internships, which is great, but it's not (as far as I know) an official major part of any curriculum I've heard about.
For Prof. Larry Peterson's networking class, we had to build up a web server over the course of a semester. You built IP, and used that library to build TCP, and then served files over your TCP connection.
It was the first time I realized that bugs in earlier assignments don't magically disappear, and changed my thinking dramatically.
I had the same experience in my compilers class at USF. Each assignment added layers on top of old assignments (lexer -> parser -> type checker -> codegen). I think it's an excellent strategy for helping students understand the importance of maintainability. Also, forcing you to revisit past assignments will help implant the concepts.
A single four year project would be a little harsh... once you realize what you've done wrong, you'd be to too deep in it to amend your ways anymore. Give them a chance to start from scratch a few times...
Realizing what you've done wrong and feeling the pain of figuring out how to amend it is the whole point of the exercise. Maybe the right way to amend it is to rewrite the whole thing, but that may or may not be the best choice, depending on what else you want to get done. Just like the real world!
It occurs to me, reading your comment, that an interesting exercise for a software development school program would be to assign a project for first-year students; then, in second year, assign the students to take the same project from the previous year and modify it.
One problem with that is that you tend to penalize students who didn't do well the first time, and you also make it more difficult to grade because it's not an independent measurement of their progress in a particular class.
Agreed. I feel like the biggest problem with these sorts of assignments for students is that most students are still learning to program - so by the time you "add on" to your first assignment, a lot of them will want/need to re-write it.
That's kind of the point: the assignment is to take an existing codebase (probably a crappy one), figure it out (who remembers what they were thinking a year ago?), and add to it without breaking it.
So then you give every student the same existing codebase (along with the same set of requirements), and as a bonus this helps them learn and adapt something written by a different person.
Saying: "Use the same thing you worked on last quarter" dooms everyone who made wrong foundational choices with their old project to spend most of their time "fighting the last war".
"Saying: "Use the same thing you worked on last quarter" dooms everyone who made wrong foundational choices with their old project to spend most of their time "fighting the last war"."
Not unless you told them before they started the previous year's project that it'll be used further along the line.
I'd just make it one really long subject that spans 3/4 years.
I actually discussed this idea with professors at my university, but the general consensus was that it was too ambitious/complicated to work into the current system.
My undergraduate program at Georgia Tech attempted to do this in CS2335 (Software Practicum). That I recall, our first project was an IM client/server, our second was a client/sever game involving programmable automatons, and our third project was a hybrid of Space War-Asteroids-Trader-Lander-Pirates.
You had the option of building them all completely separately, but you were strongly encouraged to re-use code form earlier revisions, particular for the client-server bits. The class also included labs on Subversion, although nothing on the notion of things like code review. I could imagine teaching the course today with Github and requiring (or at least awarding some credit for) good use of Pull Requests, meaningful code review, etc.
Sadly it looks like the course no longer exists; I have no idea what replaced it.
6.170 at MIT attempted to be a 'real-life' coding lab class, while teaching OO concepts. First you're given an assignment; eg, write a simple physics engine that simulates a pool table, and you're graded on your implementation of this. Next a curve-ball is thrown that you're not supposed to know before hand. Eg. Add gravity and turn it into pinball. (Apparently, it's no longer taught in Java, but a quick glance says the curriculum hasn't fundamentally changed - it's a 'real-world' coding lab - now with git!)
What you don't learn in school is how to write maintainable code. This is a much different style of writing code, and much more valuable once you go out in the "Real World". I'm not sure if there are CS programs out that start with a small project and then force the students to make massive changes to that project throughout the course of the year, but to me there is immense value to that. Even better would be if they had CS101, CS201, CS301 and the students taking CS201 would be forced to dig up their project from CS101 and work on that, CS301 would work on the code from CS201, etc. It's eye-opening how much you forget about your code after a few weeks, so it would drive home the notion of commenting, writing easily readable code, etc.